From d19a9c305b832bbd4cdb186ea01c8f71d1e7c357 Mon Sep 17 00:00:00 2001 From: Ruben Verweij Date: Thu, 16 Feb 2017 15:46:07 +0100 Subject: [PATCH] Refactor, add metadata to Frame objects, support PIMS open function --- nd2reader/legacy.py | 3 +-- nd2reader/nd2reader.py | 49 ++++++++++++++++++++++++++++-------- nd2reader/parser/__init__.py | 2 +- nd2reader/parser/parser.py | 37 ++++++++++++++++++++++++++- nd2reader/version.py | 32 ----------------------- tests/driver/version.py | 2 +- 6 files changed, 78 insertions(+), 47 deletions(-) delete mode 100644 nd2reader/version.py diff --git a/nd2reader/legacy.py b/nd2reader/legacy.py index 5727d7b..2273161 100644 --- a/nd2reader/legacy.py +++ b/nd2reader/legacy.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from nd2reader.parser import get_parser -from nd2reader.version import get_version +from nd2reader.parser import get_parser, get_version import six diff --git a/nd2reader/nd2reader.py b/nd2reader/nd2reader.py index 3fbca48..a2bf1ca 100644 --- a/nd2reader/nd2reader.py +++ b/nd2reader/nd2reader.py @@ -3,7 +3,6 @@ import numpy as np from nd2reader.exc import NoImageError from nd2reader.parser import get_parser -from nd2reader.version import get_version import six @@ -17,12 +16,28 @@ class ND2Reader(FramesSequenceND): # first use the parser to parse the file self._fh = open(filename, "rb") - major_version, minor_version = get_version(self._fh) - self._parser = get_parser(self._fh, major_version, minor_version) + self._parser = get_parser(self._fh) self._metadata = self._parser.metadata self._roi_metadata = self._parser.roi_metadata # Set data type + self._dtype = self._get_dtype_from_metadata() + + # Setup the axes + self._init_axis('x', self._metadata.width) + self._init_axis('y', self._metadata.height) + self._init_axis('c', len(self._metadata.channels)) + self._init_axis('t', len(self._metadata.frames)) + self._init_axis('z', len(self._metadata.z_levels)) + + # provide the default + self.iter_axes = 't' + + def _get_dtype_from_metadata(self): + """ + Determine the data type from the metadata. + :return: + """ bit_depth = self._parser.raw_metadata.image_attributes[six.b('SLxImageAttributes')][six.b('uiBpcInMemory')] if bit_depth <= 16: self._dtype = np.float16 @@ -31,12 +46,11 @@ class ND2Reader(FramesSequenceND): else: self._dtype = np.float64 - # Setup the axes - self._init_axis('x', self._metadata.width) - self._init_axis('y', self._metadata.height) - self._init_axis('c', len(self._metadata.channels)) - self._init_axis('t', len(self._metadata.frames)) - self._init_axis('z', len(self._metadata.z_levels)) + return self._dtype + + @classmethod + def class_exts(cls): + return {'nd2'} | super(ND2Reader, cls).class_exts() def close(self): if self._fh is not None: @@ -57,7 +71,22 @@ class ND2Reader(FramesSequenceND): except (TypeError, NoImageError): return Frame([]) else: - return Frame(image, frame_no=image.frame_number) + return Frame(image, frame_no=image.frame_number, metadata=self._get_frame_metadata()) + + def _get_frame_metadata(self): + """ + Get the metadata for one frame + :return: + """ + frame_metadata = { + "height": self._metadata.height, + "width": self._metadata.width, + "date": self._metadata.date, + "pixel_microns": self._metadata.pixel_microns, + "rois": self._roi_metadata + } + + return frame_metadata @property def pixel_type(self): diff --git a/nd2reader/parser/__init__.py b/nd2reader/parser/__init__.py index 24ce42d..738a615 100644 --- a/nd2reader/parser/__init__.py +++ b/nd2reader/parser/__init__.py @@ -1 +1 @@ -from . parser import get_parser +from . parser import get_parser, get_version, parse_version diff --git a/nd2reader/parser/parser.py b/nd2reader/parser/parser.py index d2027d5..0cd79ae 100644 --- a/nd2reader/parser/parser.py +++ b/nd2reader/parser/parser.py @@ -1,8 +1,9 @@ from nd2reader.parser.v3 import V3Parser +import re from nd2reader.exc import InvalidVersionError -def get_parser(fh, major_version, minor_version): +def get_parser(fh): """ Picks the appropriate parser based on the ND2 version. @@ -13,8 +14,42 @@ def get_parser(fh, major_version, minor_version): :rtype: a parser object """ + major_version, minor_version = get_version(fh) parsers = {(3, None): V3Parser} parser = parsers.get((major_version, minor_version)) or parsers.get((major_version, None)) if not parser: raise InvalidVersionError("No parser is available for that version.") return parser(fh) + + +def get_version(fh): + """ + Determines what version the ND2 is. + + :param fh: an open file handle to the ND2 + :type fh: file + + """ + # the first 16 bytes seem to have no meaning, so we skip them + fh.seek(16) + + # the next 38 bytes contain the string that we want to parse. Unlike most of the ND2, this is in UTF-8 + data = fh.read(38).decode("utf8") + return parse_version(data) + + +def parse_version(data): + """ + Parses a string with the version data in it. + + :param data: the 19th through 54th byte of the ND2, representing the version + :type data: unicode + + """ + match = re.search(r"""^ND2 FILE SIGNATURE CHUNK NAME01!Ver(?P\d)\.(?P\d)$""", data) + + if match: + # We haven't seen a lot of ND2s but the ones we have seen conform to this + return int(match.group('major')), int(match.group('minor')) + + raise InvalidVersionError("The version of the ND2 you specified is not supported.") diff --git a/nd2reader/version.py b/nd2reader/version.py deleted file mode 100644 index cdd4b82..0000000 --- a/nd2reader/version.py +++ /dev/null @@ -1,32 +0,0 @@ -import re -from nd2reader.exc import InvalidVersionError - - -def get_version(fh): - """ - Determines what version the ND2 is. - - :param fh: an open file handle to the ND2 - :type fh: file - - """ - # the first 16 bytes seem to have no meaning, so we skip them - fh.seek(16) - # the next 38 bytes contain the string that we want to parse. Unlike most of the ND2, this is in UTF-8 - data = fh.read(38).decode("utf8") - return parse_version(data) - - -def parse_version(data): - """ - Parses a string with the version data in it. - - :param data: the 19th through 54th byte of the ND2, representing the version - :type data: unicode - - """ - match = re.search(r"""^ND2 FILE SIGNATURE CHUNK NAME01!Ver(?P\d)\.(?P\d)$""", data) - if match: - # We haven't seen a lot of ND2s but the ones we have seen conform to this - return int(match.group('major')), int(match.group('minor')) - raise InvalidVersionError("The version of the ND2 you specified is not supported.") diff --git a/tests/driver/version.py b/tests/driver/version.py index c894b61..787e8df 100644 --- a/tests/driver/version.py +++ b/tests/driver/version.py @@ -1,5 +1,5 @@ import unittest -from nd2reader.version import parse_version +from nd2reader.parser import parse_version class VersionTests(unittest.TestCase):