diff --git a/nd2reader/exceptions.py b/nd2reader/exceptions.py index 55c598b..cb1b1fb 100644 --- a/nd2reader/exceptions.py +++ b/nd2reader/exceptions.py @@ -6,17 +6,6 @@ class InvalidVersionError(Exception): """ pass - -class NoImageError(Exception): - """No image found. - - Some apparent images in ND2s are just completely blank placeholders. These are used when the number of images per - cycle are unequal (e.g. if you take fluorescent images every 2 minutes, and bright field images every minute). - - """ - pass - - class EmptyFileError(Exception): """This .nd2 file seems to be empty. diff --git a/nd2reader/parser.py b/nd2reader/parser.py index de7e7c3..baea4e8 100644 --- a/nd2reader/parser.py +++ b/nd2reader/parser.py @@ -3,11 +3,12 @@ import struct import array import six +import warnings from pims.base_frames import Frame import numpy as np from nd2reader.common import get_version, read_chunk -from nd2reader.exceptions import InvalidVersionError, NoImageError +from nd2reader.exceptions import InvalidVersionError from nd2reader.label_map import LabelMap from nd2reader.raw_metadata import RawMetadata @@ -72,7 +73,7 @@ class Parser(object): try: timestamp, image = self._get_raw_image_data(image_group_number, channel_offset, self.metadata["height"], self.metadata["width"]) - except (TypeError, NoImageError): + except (TypeError): return Frame([], frame_no=frame_number, metadata=self._get_frame_metadata()) else: return image @@ -101,7 +102,7 @@ class Parser(object): try: timestamp, raw_image_data = self._get_raw_image_data(image_group_number, channel, height, width) - except (TypeError, NoImageError): + except (TypeError): return Frame([], frame_no=frame_number, metadata=self._get_frame_metadata()) else: return raw_image_data @@ -140,6 +141,7 @@ class Parser(object): self._label_map = self._build_label_map() self._raw_metadata = RawMetadata(self._fh, self._label_map) self.metadata = self._raw_metadata.__dict__ + self.acquisition_times = self._raw_metadata.acquisition_times def _build_label_map(self): """ @@ -283,7 +285,12 @@ class Parser(object): if np.any(image_data): return timestamp, Frame(image_data, metadata=self._get_frame_metadata()) - raise NoImageError + # If a blank "gap" image is encountered, generate an array of corresponding height and width to avoid + # errors with ND2-files with missing frames. Array is filled with nan to reflect that data is missing. + else: + empty_frame = np.full((height, width), np.nan) + warnings.warn('ND2 file contains gap frames which are represented by np.nan-filled arrays; to convert to zeros use e.g. np.nan_to_num(array)') + return timestamp, Frame(empty_frame, metadata=self._get_frame_metadata()) def _get_frame_metadata(self): """Get the metadata for one frame diff --git a/nd2reader/raw_metadata.py b/nd2reader/raw_metadata.py index 66f3fb3..321136d 100644 --- a/nd2reader/raw_metadata.py +++ b/nd2reader/raw_metadata.py @@ -43,9 +43,10 @@ class RawMetadata(object): "fields_of_view": self._parse_fields_of_view(), "frames": self._parse_frames(), "z_levels": self._parse_z_levels(), + "z_coordinates": parse_if_not_none(self.z_data, self._parse_z_coordinates), "total_images_per_channel": frames_per_channel, "channels": self._parse_channels(), - "pixel_microns": parse_if_not_none(self.image_calibration, self._parse_calibration), + "pixel_microns": parse_if_not_none(self.image_calibration, self._parse_calibration) } self._set_default_if_not_empty('fields_of_view') @@ -161,6 +162,14 @@ class RawMetadata(object): """ return self._parse_dimension(r""".*?Z\((\d+)\).*?""") + def _parse_z_coordinates(self): + """The coordinate in micron for all z planes. + + Returns: + list: the z coordinates in micron + """ + return self.z_data.tolist() + def _parse_dimension_text(self): """While there are metadata values that represent a lot of what we want to capture, they seem to be unreliable. Sometimes certain elements don't exist, or change their data type randomly. However, the human-readable text @@ -193,7 +202,7 @@ class RawMetadata(object): if not match: return [] count = int(match.group(1)) - return list(range(count)) + return range(count) def _parse_total_images_per_channel(self): """The total number of images per channel. @@ -492,7 +501,12 @@ class RawMetadata(object): Returns: dict: z_data """ - return read_array(self._fh, 'double', self._label_map.z_data) + try: + return read_array(self._fh, 'double', self._label_map.z_data) + except ValueError: + # Depending on the file format/exact settings, this value is + # sometimes saved as float instead of double + return read_array(self._fh, 'float', self._label_map.z_data) @property def roi_metadata(self): diff --git a/nd2reader/reader.py b/nd2reader/reader.py index fe263d7..714dc0e 100644 --- a/nd2reader/reader.py +++ b/nd2reader/reader.py @@ -52,6 +52,11 @@ class ND2Reader(FramesSequenceND): except KeyError: return 0 + def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0, v=0): + """Fallback function for backwards compatibility + """ + return get_frame_vczyx(v=v, c=c, t=t, z=z, x=x, y=y) + def get_frame_vczyx(self, v=None, c=None, t=None, z=None, x=None, y=None): x = self.metadata["width"] if x <= 0 else x y = self.metadata["height"] if y <= 0 else y