From 55a3222e4f42338dfbbf3bc8f87f10711b350c4d Mon Sep 17 00:00:00 2001 From: jim Date: Thu, 17 Sep 2015 15:32:44 -0500 Subject: [PATCH] resolves #81: images now include the frame number (formerly called the time index). This tells you what order a particular image appeared in its field of view, which is useful when you're considering fields of view as independent regions of interest. Also fixed a bug where the number of image cycles (which should be renamed to "frames") was incorrect for some nd2s. --- nd2reader/__init__.py | 12 ++++++------ nd2reader/model/__init__.py | 12 ++++++++++-- nd2reader/parser.py | 11 +++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/nd2reader/__init__.py b/nd2reader/__init__.py index e01360a..cf19770 100644 --- a/nd2reader/__init__.py +++ b/nd2reader/__init__.py @@ -58,8 +58,9 @@ class Nd2(Nd2Parser): channel = self._calculate_channel(item) z_level = self._calculate_z_level(item) image_group_number = int(item / len(self.channels)) + frame_number = self._calculate_frame_number(image_group_number, fov, z_level) timestamp, raw_image_data = self._get_raw_image_data(image_group_number, channel_offset) - image = Image(timestamp, raw_image_data, fov, channel, z_level, self.height, self.width) + image = Image(timestamp, frame_number, raw_image_data, fov, channel, z_level, self.height, self.width) except (TypeError, ValueError): return None except KeyError: @@ -126,13 +127,12 @@ class Nd2(Nd2Parser): """ return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiWidth')] - def get_image(self, time_index, field_of_view, channel_name, z_level): + def get_image(self, frame_number, field_of_view, channel_name, z_level): """ Returns an Image if data exists for the given parameters, otherwise returns None. In general, you should avoid using this method unless you're very familiar with the structure of ND2 files. - :param time_index: the frame number - :type time_index: int + :type frame_number: int :param field_of_view: the label for the place in the XY-plane where this image was taken. :type field_of_view: int :param channel_name: the name of the color of this image @@ -142,10 +142,10 @@ class Nd2(Nd2Parser): :rtype: nd2reader.model.Image() or None """ - image_group_number = self._calculate_image_group_number(time_index, field_of_view, z_level) + image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level) try: timestamp, raw_image_data = self._get_raw_image_data(image_group_number, self._channel_offset[channel_name]) - image = Image(timestamp, raw_image_data, field_of_view, channel_name, z_level, self.height, self.width) + image = Image(timestamp, frame_number, raw_image_data, field_of_view, channel_name, z_level, self.height, self.width) except TypeError: return None else: diff --git a/nd2reader/model/__init__.py b/nd2reader/model/__init__.py index bd547a1..df1ff5f 100644 --- a/nd2reader/model/__init__.py +++ b/nd2reader/model/__init__.py @@ -8,10 +8,12 @@ log = logging.getLogger(__name__) class Image(object): - def __init__(self, timestamp, raw_array, field_of_view, channel, z_level, height, width): + def __init__(self, timestamp, frame_number, raw_array, field_of_view, channel, z_level, height, width): """ A wrapper around the raw pixel data of an image. + :param timestamp: The frame number relative to the . + :type timestamp: int :param timestamp: The number of milliseconds after the beginning of the acquisition that this image was taken. :type timestamp: int :param raw_array: The raw sequence of bytes that represents the image. @@ -29,6 +31,7 @@ class Image(object): """ self._timestamp = timestamp + self._frame_number = int(frame_number) self._raw_data = raw_array self._field_of_view = field_of_view self._channel = channel @@ -41,6 +44,7 @@ class Image(object): return "\n".join(["", "%sx%s (HxW)" % (self._height, self._width), "Timestamp: %s" % self.timestamp, + "Frame: %s" % self._frame_number, "Field of View: %s" % self.field_of_view, "Channel: %s" % self.channel, "Z-Level: %s" % self.z_level, @@ -83,6 +87,10 @@ class Image(object): """ return self._timestamp / 1000.0 + @property + def frame_number(self): + return self._frame_number + @property def channel(self): """ @@ -144,4 +152,4 @@ class ImageSet(object): :type image: nd2reader.model.Image() """ - self._images[image.channel][image.z_level] = image \ No newline at end of file + self._images[image.channel][image.z_level] = image diff --git a/nd2reader/parser.py b/nd2reader/parser.py index f00ec01..08d41ee 100644 --- a/nd2reader/parser.py +++ b/nd2reader/parser.py @@ -69,7 +69,7 @@ class Nd2Parser(object): These are labels created by the NIS Elements user. Typically they may a short description of the filter cube used (e.g. "bright field", "GFP", etc.) - :rtype: str + :rtype: list """ if not self._channels: @@ -97,7 +97,7 @@ class Nd2Parser(object): in the image data, so we have to calculate it. There probably is something somewhere, since NIS Elements can figure it out, but we haven't found it yet. - :rtype: int + :rtype: list """ if self._fields_of_view is None: @@ -109,7 +109,7 @@ class Nd2Parser(object): """ The number of cycles. - :rtype: int + :rtype: list """ if self._time_indexes is None: @@ -121,7 +121,7 @@ class Nd2Parser(object): """ The different levels in the Z-plane. Just a sequence from 0 to n. - :rtype: int + :rtype: list """ if self._z_levels is None: @@ -214,6 +214,9 @@ class Nd2Parser(object): """ return time_index * len(self.fields_of_view) * len(self.z_levels) + (fov * len(self.z_levels) + z_level) + def _calculate_frame_number(self, image_group_number, fov, z_level): + return (image_group_number - (fov * len(self.z_levels) + z_level)) / (len(self.fields_of_view) * len(self.z_levels)) + @property def _channel_offset(self): """