diff --git a/nd2reader/parser.py b/nd2reader/parser.py index aabd156..8f52a2f 100644 --- a/nd2reader/parser.py +++ b/nd2reader/parser.py @@ -29,7 +29,7 @@ class Parser(object): self.metadata = None # First check the file version - self._check_version_supported() + self.supported = self._check_version_supported() # Parse the metadata self._parse_metadata() diff --git a/nd2reader/raw_metadata.py b/nd2reader/raw_metadata.py index aa0496a..a6fcde8 100644 --- a/nd2reader/raw_metadata.py +++ b/nd2reader/raw_metadata.py @@ -34,15 +34,15 @@ class RawMetadata(object): return self._metadata_parsed self._metadata_parsed = { - "height": self.image_attributes[six.b('SLxImageAttributes')][six.b('uiHeight')], - "width": self.image_attributes[six.b('SLxImageAttributes')][six.b('uiWidth')], - "date": parse_date(self.image_text_info[six.b('SLxImageTextInfo')]), + "height": self._parse_height(), + "width": self._parse_width(), + "date": self._parse_date(), "fields_of_view": self._parse_fields_of_view(), "frames": self._parse_frames(), "z_levels": self._parse_z_levels(), "total_images_per_channel": self._parse_total_images_per_channel(), "channels": self._parse_channels(), - "pixel_microns": self.image_calibration.get(six.b('SLxCalibration'), {}).get(six.b('dCalibration')), + "pixel_microns": self._parse_calibration(), } self._metadata_parsed['num_frames'] = len(self._metadata_parsed['frames']) @@ -52,6 +52,26 @@ class RawMetadata(object): return self._metadata_parsed + def _parse_height(self): + if self.image_attributes is not None: + return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiHeight')] + return None + + def _parse_width(self): + if self.image_attributes is not None: + return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiWidth')] + return None + + def _parse_date(self): + if self.image_text_info is not None: + return parse_date(self.image_text_info[six.b('SLxImageTextInfo')]) + return None + + def _parse_calibration(self): + if self.image_calibration is not None: + return self.image_calibration.get(six.b('SLxCalibration'), {}).get(six.b('dCalibration')) + return None + def _parse_channels(self): """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.) @@ -60,6 +80,9 @@ class RawMetadata(object): list: the color channels """ channels = [] + if self.image_metadata_sequence is None: + return channels + metadata = self.image_metadata_sequence[six.b('SLxPictureMetadata')][six.b('sPicturePlanes')] try: validity = self.image_metadata[six.b('SLxExperiment')][six.b('ppNextLevelEx')][six.b('')][0][ @@ -108,6 +131,9 @@ class RawMetadata(object): """ dimension_text = six.b("") + if self.image_text_info is None: + return dimension_text + textinfo = self.image_text_info[six.b('SLxImageTextInfo')].values() for line in textinfo: @@ -121,6 +147,8 @@ class RawMetadata(object): def _parse_dimension(self, pattern): dimension_text = self._parse_dimension_text() + if dimension_text is None: + return [0] if six.PY3: dimension_text = dimension_text.decode("utf8") match = re.match(pattern, dimension_text) @@ -135,6 +163,8 @@ class RawMetadata(object): Warning: this may be inaccurate as it includes 'gap' images. """ + if self.image_attributes is None: + return None return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiSequenceCount')] def _parse_roi_metadata(self): @@ -241,7 +271,7 @@ class RawMetadata(object): """Parse the metadata of the ND experiment """ - if not six.b('SLxExperiment') in self.image_metadata: + if self.image_metadata is None or six.b('SLxExperiment') not in self.image_metadata: return raw_data = self.image_metadata[six.b('SLxExperiment')] diff --git a/tests/test_common.py b/tests/test_common.py index 1ed6fd4..8f5ac83 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -11,6 +11,10 @@ class TestCommon(unittest.TestCase): dir_path = path.dirname(path.realpath(__file__)) self.test_file = path.join(dir_path, 'test_data/test.nd2') + def create_test_nd2(self): + with ArtificialND2(self.test_file) as artificial: + artificial.close() + def test_parse_version_2(self): data = 'ND2 FILE SIGNATURE CHUNK NAME01!Ver2.2' actual = parse_version(data) @@ -24,8 +28,7 @@ class TestCommon(unittest.TestCase): self.assertTupleEqual(actual, expected) def test_get_version_from_file(self): - with ArtificialND2(self.test_file) as artificial: - artificial.close() + self.create_test_nd2() with open(self.test_file, 'rb') as fh: version_tuple = get_version(fh) diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..bfb8f81 --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,21 @@ +import unittest +from os import path +from nd2reader.artificial import ArtificialND2 +from nd2reader.parser import Parser + + +class TestParser(unittest.TestCase): + def create_test_nd2(self): + with ArtificialND2(self.test_file) as artificial: + artificial.close() + + def setUp(self): + dir_path = path.dirname(path.realpath(__file__)) + self.test_file = path.join(dir_path, 'test_data/test.nd2') + + def test_can_open_test_file(self): + self.create_test_nd2() + + with open(self.test_file, 'rb') as fh: + parser = Parser(fh) + self.assertTrue(parser.supported)