diff --git a/nd2reader/imreader/v2.py b/nd2reader/driver/v2.py similarity index 100% rename from nd2reader/imreader/v2.py rename to nd2reader/driver/v2.py diff --git a/nd2reader/imreader/v3.py b/nd2reader/driver/v3.py similarity index 81% rename from nd2reader/imreader/v3.py rename to nd2reader/driver/v3.py index 9285e1b..ea00527 100644 --- a/nd2reader/imreader/v3.py +++ b/nd2reader/driver/v3.py @@ -7,10 +7,14 @@ import six from nd2reader.model.image import Image -class V3ImageReader(object): - def __init__(self, metadata): - self._metadata = metadata +class V3Driver(object): + CHUNK_HEADER = 0xabeceda + def __init__(self, metadata, label_map, file_handle): + self._metadata = metadata + self._label_map = label_map + self._file_handle = file_handle + def _calculate_field_of_view(self, frame_number): images_per_cycle = len(self._metadata.z_levels) * len(self._metadata.channels) return int((frame_number - (frame_number % images_per_cycle)) / images_per_cycle) % len(self._metadata.fields_of_view) @@ -46,6 +50,7 @@ class V3ImageReader(object): frame_number = self._calculate_frame_number(image_group_number, fov, z_level) timestamp, image = self._get_raw_image_data(image_group_number, channel_offset, self._metadata.height, self._metadata.width) image.add_params(timestamp, frame_number, fov, channel, z_level) + return image @property def _channel_offset(self): @@ -92,3 +97,21 @@ class V3ImageReader(object): if np.any(image_data): return timestamp, Image(image_data) return None + + def _read_chunk(self, chunk_location): + """ + Gets the data for a given chunk pointer + + :rtype: bytes + + """ + self._file_handle.seek(chunk_location) + # The chunk metadata is always 16 bytes long + chunk_metadata = self._file_handle.read(16) + header, relative_offset, data_length = struct.unpack("IIQ", chunk_metadata) + if header != V3Driver.CHUNK_HEADER: + raise ValueError("The ND2 file seems to be corrupted.") + # We start at the location of the chunk metadata, skip over the metadata, and then proceed to the + # start of the actual data field, which is at some arbitrary place after the metadata. + self._file_handle.seek(chunk_location + 16 + relative_offset) + return self._file_handle.read(data_length) diff --git a/nd2reader/imreader/__init__.py b/nd2reader/imreader/__init__.py deleted file mode 100644 index 16b2735..0000000 --- a/nd2reader/imreader/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from nd2reader.driver.driver import get_driver diff --git a/nd2reader/model/image.py b/nd2reader/model/image.py index 5637870..f51d586 100644 --- a/nd2reader/model/image.py +++ b/nd2reader/model/image.py @@ -7,6 +7,13 @@ class Image(np.ndarray): def __new__(cls, array): return np.asarray(array).view(cls) + def __init__(self, array): + self._timestamp = None + self._frame_number = None + self._field_of_view = None + self._channel = None + self._z_level = None + def add_params(self, timestamp, frame_number, field_of_view, channel, z_level): """ A wrapper around the raw pixel data of an image. @@ -29,16 +36,6 @@ class Image(np.ndarray): self._channel = channel self._z_level = z_level - def __repr__(self): - 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, - ]) - @property def height(self): return self.shape[1] diff --git a/nd2reader/model/metadata.py b/nd2reader/model/metadata.py index cf7d140..838dcd7 100644 --- a/nd2reader/model/metadata.py +++ b/nd2reader/model/metadata.py @@ -1,12 +1,22 @@ class Metadata(object): """ A simple container for ND2 metadata. """ - def __init__(self, channels, date, fields_of_view, frames, z_levels): + def __init__(self, height, width, channels, date, fields_of_view, frames, z_levels): + self._height = height + self._width = width self._channels = channels self._date = date self._fields_of_view = fields_of_view self._frames = frames self._z_levels = z_levels + @property + def height(self): + return self._height + + @property + def width(self): + return self._width + @property def date(self): """ diff --git a/nd2reader/parser/v3.py b/nd2reader/parser/v3.py index c1f9a0f..fa1cf89 100644 --- a/nd2reader/parser/v3.py +++ b/nd2reader/parser/v3.py @@ -4,6 +4,7 @@ import array from datetime import datetime from nd2reader.model.metadata import Metadata from nd2reader.parser.base import BaseParser +from nd2reader.driver.v3 import V3Driver import re import six import struct @@ -19,6 +20,7 @@ class V3Parser(BaseParser): self._filename = filename self._fh = None self._metadata = None + self._label_map = None @property def metadata(self): @@ -28,7 +30,7 @@ class V3Parser(BaseParser): @property def driver(self): - raise NotImplementedError + return V3Driver(self.metadata, self._label_map, self._get_file_handle()) def _parse_metadata(self): """ @@ -36,19 +38,21 @@ class V3Parser(BaseParser): """ metadata_dict = {} - label_map = self._build_label_map() - for label in label_map.keys(): + self._label_map = self._build_label_map() + for label in self._label_map.keys(): if label.endswith(six.b("LV!")) or six.b("LV|") in label: - data = self._read_chunk(label_map[label]) + data = self._read_chunk(self._label_map[label]) stop = label.index(six.b("LV")) metadata_dict[label[:stop]] = self._read_metadata(data, 1) + height = metadata_dict[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiHeight')] + width = metadata_dict[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiWidth')] channels = self._parse_channels(metadata_dict) - date = self._parse_fields_of_view(metadata_dict) + date = self._parse_date(metadata_dict) fields_of_view = self._parse_fields_of_view(metadata_dict) frames = self._parse_frames(metadata_dict) z_levels = self._parse_z_levels(metadata_dict) - self._metadata = Metadata(channels, date, fields_of_view, frames, z_levels) + self._metadata = Metadata(height, width, channels, date, fields_of_view, frames, z_levels) def _parse_date(self, metadata_dict): """