Browse Source

Added parsing for basic ROI metadata.

feature/load_slices
Ruben Verweij 7 years ago
parent
commit
6f800c49f6
4 changed files with 92 additions and 2 deletions
  1. +1
    -0
      nd2reader/main.py
  2. +63
    -0
      nd2reader/model/roi.py
  3. +1
    -0
      nd2reader/parser/base.py
  4. +27
    -2
      nd2reader/parser/v3.py

+ 1
- 0
nd2reader/main.py View File

@ -14,6 +14,7 @@ class Nd2(object):
major_version, minor_version = get_version(self._fh)
self._parser = get_parser(self._fh, major_version, minor_version)
self._metadata = self._parser.metadata
self._roi_metadata = self._parser.roi_metadata
def __repr__(self):
return "\n".join(["<ND2 %s>" % self._filename,


+ 63
- 0
nd2reader/model/roi.py View File

@ -0,0 +1,63 @@
import six
import numpy as np
class Roi(object):
"""
A ND2 ROI representation.
Coordinates are the center coordinates of the ROI in (x, y, z) order.
Sizes are the sizes of the ROI in (x, y, z) order.
Shapes are represented by numbers, defined by constants in this class.
All these properties can be set for multiple timepoints.
"""
SHAPE_RECTANGLE = 3
SHAPE_CIRCLE = 9
def __init__(self, raw_roi_dict):
self.timepoints = []
self.positions = []
self.sizes = []
self.shapes = []
self._extract_vect_anims(raw_roi_dict)
def _extract_vect_anims(self, raw_roi_dict):
"""
Extract the vector animation parameters from the ROI.
This includes the position and size at the given timepoints.
:param raw_roi_dict:
:return:
"""
number_of_timepoints = raw_roi_dict[six.b('m_vectAnimParams_Size')]
for i in range(number_of_timepoints):
shape = raw_roi_dict[six.b('m_sInfo')][six.b('m_uiShapeType')]
self._parse_vect_anim(raw_roi_dict[six.b('m_vectAnimParams_%d') % i], shape)
# convert to NumPy arrays
self.timepoints = np.array(self.timepoints, dtype=np.float)
self.positions = np.array(self.positions, dtype=np.float)
self.sizes = np.array(self.sizes, dtype=np.float)
self.shapes = np.array(self.shapes, dtype=np.uint)
def _parse_vect_anim(self, animation_dict, shape):
"""
Parses a ROI vector animation object and adds it to the global list of timepoints and positions.
:param animation_dict:
:return:
"""
self.timepoints.append(animation_dict[six.b('m_dTimeMs')])
self.positions.append((animation_dict[six.b('m_dCenterX')],
animation_dict[six.b('m_dCenterY')],
animation_dict[six.b('m_dCenterZ')]))
size_dict = animation_dict[six.b('m_sBoxShape')]
self.sizes.append((size_dict[six.b('m_dSizeX')],
size_dict[six.b('m_dSizeY')],
size_dict[six.b('m_dSizeZ')]))
self.shapes.append(shape)
def is_circle(self, timepoint_id=0):
return self.shapes[timepoint_id] == self.SHAPE_CIRCLE
def is_rectangle(self, timepoint_id=0):
return self.shapes[timepoint_id] == self.SHAPE_RECTANGLE

+ 1
- 0
nd2reader/parser/base.py View File

@ -6,6 +6,7 @@ class BaseParser(object):
self._fh = fh
self.camera_metadata = None
self.metadata = None
self.roi_metadata = None
@abstractproperty
def driver(self):


+ 27
- 2
nd2reader/parser/v3.py View File

@ -3,6 +3,7 @@
from datetime import datetime
from nd2reader.model.metadata import Metadata
from nd2reader.model.label import LabelMap
from nd2reader.model.roi import Roi
from nd2reader.parser.base import BaseParser
from nd2reader.driver.v3 import V3Driver
from nd2reader.common.v3 import read_chunk, read_array, read_metadata
@ -18,6 +19,7 @@ def ignore_missing(func):
return func(*args, **kwargs)
except:
return None
return wrapper
@ -142,6 +144,7 @@ class V3Parser(BaseParser):
self._label_map = self._build_label_map()
self.raw_metadata = V3RawMetadata(self._fh, self._label_map)
self._parse_metadata()
self._parse_roi_metadata()
@property
def driver(self):
@ -165,7 +168,8 @@ class V3Parser(BaseParser):
total_images_per_channel = self._parse_total_images_per_channel(self.raw_metadata)
channels = self._parse_channels(self.raw_metadata)
pixel_microns = self.raw_metadata.image_calibration.get(six.b('SLxCalibration'), {}).get(six.b('dCalibration'))
self.metadata = Metadata(height, width, channels, date, fields_of_view, frames, z_levels, total_images_per_channel, pixel_microns)
self.metadata = Metadata(height, width, channels, date, fields_of_view, frames, z_levels,
total_images_per_channel, pixel_microns)
def _parse_date(self, raw_metadata):
"""
@ -205,7 +209,8 @@ class V3Parser(BaseParser):
channels = []
metadata = raw_metadata.image_metadata_sequence[six.b('SLxPictureMetadata')][six.b('sPicturePlanes')]
try:
validity = raw_metadata.image_metadata[six.b('SLxExperiment')][six.b('ppNextLevelEx')][six.b('')][0][six.b('ppNextLevelEx')][six.b('')][0][six.b('pItemValid')]
validity = raw_metadata.image_metadata[six.b('SLxExperiment')][six.b('ppNextLevelEx')][six.b('')][0][
six.b('ppNextLevelEx')][six.b('')][0][six.b('pItemValid')]
except (KeyError, TypeError):
# If none of the channels have been deleted, there is no validity list, so we just make one
validity = [True for _ in metadata]
@ -303,6 +308,26 @@ class V3Parser(BaseParser):
"""
return raw_metadata.image_attributes[six.b('SLxImageAttributes')][six.b('uiSequenceCount')]
def _parse_roi_metadata(self):
"""
Parse the raw ROI metadata.
:return:
"""
if not six.b('RoiMetadata_v1') in self.raw_metadata.roi_metadata:
self.roi_metadata = None
return
raw_roi_data = self.raw_metadata.roi_metadata[six.b('RoiMetadata_v1')]
number_of_rois = raw_roi_data[six.b('m_vectGlobal_Size')]
roi_objects = []
for i in range(number_of_rois):
current_roi = raw_roi_data[six.b('m_vectGlobal_%d' % i)]
roi_objects.append(Roi(current_roi))
self.roi_metadata = roi_objects
def _build_label_map(self):
"""
Every label ends with an exclamation point, however, we can't directly search for those to find all the labels


Loading…
Cancel
Save