From 7fcf2a5740ece7589b65a7fe96ba22fff1fa8195 Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 26 Apr 2015 18:51:41 -0500 Subject: [PATCH] #11 - partial refactor --- nd2reader/__init__.py | 84 ++++++++- nd2reader/model/__init__.py | 174 +----------------- nd2reader/{service/__init__.py => reader.py} | 182 ++----------------- 3 files changed, 94 insertions(+), 346 deletions(-) rename nd2reader/{service/__init__.py => reader.py} (60%) diff --git a/nd2reader/__init__.py b/nd2reader/__init__.py index 264eb55..92281c7 100644 --- a/nd2reader/__init__.py +++ b/nd2reader/__init__.py @@ -1,18 +1,25 @@ +# -*- coding: utf-8 -*- + +from collections import namedtuple +from nd2reader.model import Channel import logging -from nd2reader.service import BaseNd2 from nd2reader.model import Image, ImageSet +from nd2reader.reader import Nd2FileReader + +chunk = namedtuple('Chunk', ['location', 'length']) +field_of_view = namedtuple('FOV', ['number', 'x', 'y', 'z', 'pfs_offset']) log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) -class Nd2(BaseNd2): +class Nd2(Nd2FileReader): def __init__(self, filename): super(Nd2, self).__init__(filename) def get_image(self, time_index, fov, channel_name, z_level): image_set_number = self._calculate_image_set_number(time_index, fov, z_level) - timestamp, raw_image_data = self._reader.get_raw_image_data(image_set_number, self.channel_offset[channel_name]) + timestamp, raw_image_data = self.get_raw_image_data(image_set_number, self.channel_offset[channel_name]) return Image(timestamp, raw_image_data, fov, channel_name, z_level, self.height, self.width) def __iter__(self): @@ -44,4 +51,73 @@ class Nd2(BaseNd2): image = self.get_image(timepoint, field_of_view, channel_name, z_level) if image.is_valid: image_set.add(image) - yield image_set \ No newline at end of file + yield image_set + + self._channel_offset = None + + @property + def height(self): + """ + :return: height of each image, in pixels + + """ + return self._metadata['ImageAttributes']['SLxImageAttributes']['uiHeight'] + + @property + def width(self): + """ + :return: width of each image, in pixels + + """ + return self._metadata['ImageAttributes']['SLxImageAttributes']['uiWidth'] + + @property + def channels(self): + metadata = self._metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes'] + try: + validity = self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx'][''][0]['ppNextLevelEx'][''][0]['pItemValid'] + except KeyError: + # If none of the channels have been deleted, there is no validity list, so we just make one + validity = [True for i in metadata] + # Channel information is contained in dictionaries with the keys a0, a1...an where the number + # indicates the order in which the channel is stored. So by sorting the dicts alphabetically + # we get the correct order. + for (label, chan), valid in zip(sorted(metadata['sPlaneNew'].items()), validity): + if not valid: + continue + name = chan['sDescription'] + exposure_time = metadata['sSampleSetting'][label]['dExposureTime'] + camera = metadata['sSampleSetting'][label]['pCameraSetting']['CameraUserName'] + yield Channel(name, camera, exposure_time) + + @property + def channel_names(self): + """ + A convenience method for getting an alphabetized list of channel names. + + :return: list[str] + + """ + for channel in sorted(self.channels, key=lambda x: x.name): + yield channel.name + + @property + def _image_count(self): + return self._metadata['ImageAttributes']['SLxImageAttributes']['uiSequenceCount'] + + @property + def _sequence_count(self): + return self._metadata['ImageEvents']['RLxExperimentRecord']['uiCount'] + + @property + def channel_offset(self): + if self._channel_offset is None: + self._channel_offset = {} + for n, channel in enumerate(self.channels): + self._channel_offset[channel.name] = n + return self._channel_offset + + def _calculate_image_set_number(self, time_index, fov, z_level): + return time_index * self.field_of_view_count * self.z_level_count + (fov * self.z_level_count + z_level) + + diff --git a/nd2reader/model/__init__.py b/nd2reader/model/__init__.py index 1ae84e4..39227a1 100644 --- a/nd2reader/model/__init__.py +++ b/nd2reader/model/__init__.py @@ -1,10 +1,6 @@ import numpy as np import skimage.io import logging -from io import BytesIO -import array -import struct - log = logging.getLogger(__name__) @@ -96,172 +92,4 @@ class Image(object): def show(self): skimage.io.imshow(self.data) - skimage.io.show() - - -class MetadataItem(object): - def __init__(self, start, data): - self._datatype = ord(data[start]) - self._label_length = 2 * ord(data[start + 1]) - self._data = data - - @property - def is_valid(self): - return self._datatype > 0 - - @property - def key(self): - return self._data[2:self._label_length].decode("utf16").encode("utf8") - - @property - def length(self): - return self._length - - @property - def data_start(self): - return self._label_length + 2 - - @property - def _body(self): - """ - All data after the header. - - """ - return self._data[self.data_start:] - - def _get_bytes(self, count): - return self._data[self.data_start: self.data_start + count] - - @property - def value(self): - parser = {1: self._parse_unsigned_char, - 2: self._parse_unsigned_int, - 3: self._parse_unsigned_int, - 5: self._parse_unsigned_long, - 6: self._parse_double, - 8: self._parse_string, - 9: self._parse_char_array, - 11: self._parse_metadata_item - } - return parser[self._datatype]() - - def _parse_unsigned_char(self): - self._length = 1 - return self._unpack("B", self._get_bytes(self._length)) - - def _parse_unsigned_int(self): - self._length = 4 - return self._unpack("I", self._get_bytes(self._length)) - - def _parse_unsigned_long(self): - self._length = 8 - return self._unpack("Q", self._get_bytes(self._length)) - - def _parse_double(self): - self._length = 8 - return self._unpack("d", self._get_bytes(self._length)) - - def _parse_string(self): - # the string is of unknown length but ends at the first instance of \x00\x00 - stop = self._body.index("\x00\x00") - self._length = stop - return self._body[:stop - 1].decode("utf16").encode("utf8") - - def _parse_char_array(self): - array_length = self._unpack("Q", self._get_bytes(8)) - self._length = array_length + 8 - return array.array("B", self._body[8:array_length]) - - def _parse_metadata_item(self): - count, length = struct.unpack("