From ce29f90f20e3f4336af98ccaab8fa05b1306153a Mon Sep 17 00:00:00 2001 From: Gabriele Girelli Date: Thu, 13 Aug 2020 19:51:26 +0200 Subject: [PATCH 1/4] Update reader.py --- nd2reader/reader.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/nd2reader/reader.py b/nd2reader/reader.py index 8246125..e711ca3 100644 --- a/nd2reader/reader.py +++ b/nd2reader/reader.py @@ -13,16 +13,12 @@ class ND2Reader(FramesSequenceND): class_priority = 12 - def __init__(self, filename): + def __init__(self, fh): super(ND2Reader, self).__init__() - if not filename.endswith(".nd2"): - raise InvalidFileType("The file %s you want to read with nd2reader does not have extension .nd2." % filename) + self._fh = fh + self.filename = "" - self.filename = filename - - # first use the parser to parse the file - self._fh = open(filename, "rb") self._parser = Parser(self._fh) # Setup metadata @@ -37,6 +33,16 @@ class ND2Reader(FramesSequenceND): # Other properties self._timesteps = None + @staticmethod + def from_file(filename): + if not filename.endswith(".nd2"): + raise InvalidFileType("The file %s you want to read with nd2reader does not have extension .nd2." % filename) + + nd2r = ND2Reader(open(filename, "rb")) + nd2r.filename = filename + + return nd2r + @classmethod def class_exts(cls): """Let PIMS open function use this reader for opening .nd2 files From 0b8ae27dde3ddbcc4896e76a33d3541702087c6c Mon Sep 17 00:00:00 2001 From: Gabriele Girelli Date: Mon, 31 Aug 2020 10:32:38 +0200 Subject: [PATCH 2/4] Allowed for fh to be either str or IO, and formatted with black. --- nd2reader/reader.py | 90 +++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/nd2reader/reader.py b/nd2reader/reader.py index e711ca3..5e524eb 100644 --- a/nd2reader/reader.py +++ b/nd2reader/reader.py @@ -14,41 +14,46 @@ class ND2Reader(FramesSequenceND): class_priority = 12 def __init__(self, fh): + """ + Arguments: + fh {str} -- absolute path to .nd2 file + fh {IO} -- input buffer handler (opened with "rb" mode) + """ super(ND2Reader, self).__init__() - self._fh = fh - self.filename = "" + if isinstance(fh, str): + if not fh.endswith(".nd2"): + raise InvalidFileType( + ("The file %s you want to read with nd2reader" % fh) + + " does not have extension .nd2." + ) - self._parser = Parser(self._fh) + self = ND2Reader(open(fh, "rb")) + self.filename = fh + else: + self._fh = fh + self.filename = "" - # Setup metadata - self.metadata = self._parser.metadata + self._parser = Parser(self._fh) - # Set data type - self._dtype = self._parser.get_dtype_from_metadata() + # Setup metadata + self.metadata = self._parser.metadata - # Setup the axes - self._setup_axes() + # Set data type + self._dtype = self._parser.get_dtype_from_metadata() - # Other properties - self._timesteps = None + # Setup the axes + self._setup_axes() - @staticmethod - def from_file(filename): - if not filename.endswith(".nd2"): - raise InvalidFileType("The file %s you want to read with nd2reader does not have extension .nd2." % filename) + # Other properties + self._timesteps = None - nd2r = ND2Reader(open(filename, "rb")) - nd2r.filename = filename - - return nd2r - @classmethod def class_exts(cls): """Let PIMS open function use this reader for opening .nd2 files """ - return {'nd2'} | super(ND2Reader, cls).class_exts() + return {"nd2"} | super(ND2Reader, cls).class_exts() def close(self): """Correctly close the file handle @@ -125,22 +130,24 @@ class ND2Reader(FramesSequenceND): @property def frame_rate(self): """The (average) frame rate - + Returns: float: the (average) frame rate in frames per second """ total_duration = 0.0 - for loop in self.metadata['experiment']['loops']: - total_duration += loop['duration'] + for loop in self.metadata["experiment"]["loops"]: + total_duration += loop["duration"] if total_duration == 0: total_duration = self.timesteps[-1] if total_duration == 0: - raise ValueError('Total measurement duration could not be determined from loops') + raise ValueError( + "Total measurement duration could not be determined from loops" + ) - return self.metadata['num_frames'] / (total_duration/1000.0) + return self.metadata["num_frames"] / (total_duration / 1000.0) def _get_metadata_property(self, key, default=None): if self.metadata is None: @@ -158,12 +165,22 @@ class ND2Reader(FramesSequenceND): """Setup the xyctz axes, iterate over t axis by default """ - self._init_axis_if_exists('x', self._get_metadata_property("width", default=0)) - self._init_axis_if_exists('y', self._get_metadata_property("height", default=0)) - self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])), min_size=2) - self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[]))) - self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])), min_size=2) - self._init_axis_if_exists('v', len(self._get_metadata_property("fields_of_view", default=[])), min_size=2) + self._init_axis_if_exists("x", self._get_metadata_property("width", default=0)) + self._init_axis_if_exists("y", self._get_metadata_property("height", default=0)) + self._init_axis_if_exists( + "c", len(self._get_metadata_property("channels", default=[])), min_size=2 + ) + self._init_axis_if_exists( + "t", len(self._get_metadata_property("frames", default=[])) + ) + self._init_axis_if_exists( + "z", len(self._get_metadata_property("z_levels", default=[])), min_size=2 + ) + self._init_axis_if_exists( + "v", + len(self._get_metadata_property("fields_of_view", default=[])), + min_size=2, + ) if len(self.sizes) == 0: raise EmptyFileError("No axes were found for this .nd2 file.") @@ -171,7 +188,7 @@ class ND2Reader(FramesSequenceND): # provide the default self.iter_axes = self._guess_default_iter_axis() - self._register_get_frame(self.get_frame_2D, 'yx') + self._register_get_frame(self.get_frame_2D, "yx") def _init_axis_if_exists(self, axis, size, min_size=1): if size >= min_size: @@ -183,7 +200,7 @@ class ND2Reader(FramesSequenceND): Returns: the axis to iterate over """ - priority = ['t', 'z', 'c', 'v'] + priority = ["t", "z", "c", "v"] found_axes = [] for axis in priority: try: @@ -208,6 +225,9 @@ class ND2Reader(FramesSequenceND): if self._timesteps is not None and len(self._timesteps) > 0: return self._timesteps - self._timesteps = np.array(list(self._parser._raw_metadata.acquisition_times), dtype=np.float) * 1000.0 + self._timesteps = ( + np.array(list(self._parser._raw_metadata.acquisition_times), dtype=np.float) + * 1000.0 + ) return self._timesteps From 481ce67e34b51cdb2b95bd0baa442f9f9210ee0e Mon Sep 17 00:00:00 2001 From: Gabriele Girelli Date: Mon, 31 Aug 2020 10:40:47 +0200 Subject: [PATCH 3/4] Implemented test. --- tests/test_reader.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/test_reader.py b/tests/test_reader.py index cf0f22c..c28399d 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -16,18 +16,27 @@ class TestReader(unittest.TestCase): def test_extension(self): self.assertTrue('nd2' in ND2Reader.class_exts()) + def cmp_two_readers(self, r1, r2): + attributes = r1.data['image_attributes']['SLxImageAttributes'] + self.assertEqual(r2.metadata['width'], attributes['uiWidth']) + self.assertEqual(r2.metadata['height'], attributes['uiHeight']) + + self.assertEqual(r2.metadata['width'], r2.sizes['x']) + self.assertEqual(r2.metadata['height'], r2.sizes['y']) + + self.assertEqual(r2.pixel_type, np.float64) + self.assertEqual(r2.iter_axes, ['t']) + def test_init_and_init_axes(self): with ArtificialND2('test_data/test_nd2_reader.nd2') as artificial: with ND2Reader('test_data/test_nd2_reader.nd2') as reader: - attributes = artificial.data['image_attributes']['SLxImageAttributes'] - self.assertEqual(reader.metadata['width'], attributes['uiWidth']) - self.assertEqual(reader.metadata['height'], attributes['uiHeight']) + self.cmp_two_readers(artificial, reader) - self.assertEqual(reader.metadata['width'], reader.sizes['x']) - self.assertEqual(reader.metadata['height'], reader.sizes['y']) - - self.assertEqual(reader.pixel_type, np.float64) - self.assertEqual(reader.iter_axes, ['t']) + def test_init_from_handler(self): + with ArtificialND2('test_data/test_nd2_reader.nd2') as artificial: + with open('test_data/test_nd2_reader.nd2', "rb") as FH: + with ND2Reader(FH) as reader: + self.cmp_two_readers(artificial, reader) def test_init_empty_file(self): with ArtificialND2('test_data/empty.nd2', skip_blocks=['label_map_marker']): From fdc10ca56e7a6ee504ac593075429ae71bc5b9b3 Mon Sep 17 00:00:00 2001 From: Gabriele Girelli Date: Mon, 31 Aug 2020 10:40:59 +0200 Subject: [PATCH 4/4] Fixed new init method --- nd2reader/reader.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/nd2reader/reader.py b/nd2reader/reader.py index 5e524eb..f97e1a0 100644 --- a/nd2reader/reader.py +++ b/nd2reader/reader.py @@ -11,6 +11,7 @@ class ND2Reader(FramesSequenceND): This is the main class: use this to process your .nd2 files. """ + _fh = None class_priority = 12 def __init__(self, fh): @@ -27,26 +28,24 @@ class ND2Reader(FramesSequenceND): ("The file %s you want to read with nd2reader" % fh) + " does not have extension .nd2." ) + fh = open(fh, "rb") - self = ND2Reader(open(fh, "rb")) - self.filename = fh - else: - self._fh = fh - self.filename = "" + self._fh = fh + self.filename = "" - self._parser = Parser(self._fh) + self._parser = Parser(self._fh) - # Setup metadata - self.metadata = self._parser.metadata + # Setup metadata + self.metadata = self._parser.metadata - # Set data type - self._dtype = self._parser.get_dtype_from_metadata() + # Set data type + self._dtype = self._parser.get_dtype_from_metadata() - # Setup the axes - self._setup_axes() + # Setup the axes + self._setup_axes() - # Other properties - self._timesteps = None + # Other properties + self._timesteps = None @classmethod def class_exts(cls):