From 3d387a23c1b635995665f870726802f069b60e71 Mon Sep 17 00:00:00 2001 From: Jim Rybarski Date: Sun, 17 May 2015 04:59:41 +0000 Subject: [PATCH] resolves #4 --- nd2reader/__init__.py | 49 ++++++++++++++++++------------------- nd2reader/model/__init__.py | 43 ++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/nd2reader/__init__.py b/nd2reader/__init__.py index 4e7409b..c99a1b6 100644 --- a/nd2reader/__init__.py +++ b/nd2reader/__init__.py @@ -10,7 +10,7 @@ import struct log = logging.getLogger(__name__) log.addHandler(logging.StreamHandler()) -log.setLevel(logging.WARN) +log.setLevel(logging.DEBUG) class Nd2(Nd2Parser): @@ -19,12 +19,6 @@ class Nd2(Nd2Parser): self._use_image_sets = image_sets def __iter__(self): - if self._use_image_sets: - return self.image_sets() - else: - return self.images() - - def images(self): for i in range(self._image_count): for fov in range(self.field_of_view_count): for z_level in range(self.z_level_count): @@ -33,6 +27,7 @@ class Nd2(Nd2Parser): if image.is_valid: yield image + @property def image_sets(self): for time_index in xrange(self.time_index_count): image_set = ImageSet() @@ -42,7 +37,7 @@ class Nd2(Nd2Parser): image = self.get_image(time_index, fov, channel_name, z_level) if image.is_valid: image_set.add(image) - yield image_set + yield image_set def get_image(self, time_index, fov, channel_name, z_level): image_set_number = self._calculate_image_set_number(time_index, fov, z_level) @@ -83,23 +78,22 @@ class Nd2(Nd2Parser): @property def absolute_start(self): - if self._absolute_start is None: - for line in self.metadata['ImageTextInfo']['SLxImageTextInfo'].values(): - absolute_start_12 = None - absolute_start_24 = None - # ND2s seem to randomly switch between 12- and 24-hour representations. - try: - absolute_start_24 = datetime.strptime(line, "%m/%d/%Y %H:%M:%S") - except ValueError: - pass - try: - absolute_start_12 = datetime.strptime(line, "%m/%d/%Y %I:%M:%S %p") - except ValueError: - pass - if not absolute_start_12 and not absolute_start_24: - continue - self._absolute_start = absolute_start_12 if absolute_start_12 else absolute_start_24 - return self._absolute_start + for line in self.metadata['ImageTextInfo']['SLxImageTextInfo'].values(): + absolute_start_12 = None + absolute_start_24 = None + # ND2s seem to randomly switch between 12- and 24-hour representations. + try: + absolute_start_24 = datetime.strptime(line, "%m/%d/%Y %H:%M:%S") + except ValueError: + pass + try: + absolute_start_12 = datetime.strptime(line, "%m/%d/%Y %I:%M:%S %p") + except ValueError: + pass + if not absolute_start_12 and not absolute_start_24: + continue + return absolute_start_12 if absolute_start_12 else absolute_start_24 + raise ValueError("This ND2 has no recorded start time. This is probably a bug.") @property def channel_count(self): @@ -182,3 +176,8 @@ class Nd2(Nd2Parser): 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) + + + +for image_set in Nd2("FYLM-141111-001.nd2").image_sets: + print(image_set.get("", 1).data) diff --git a/nd2reader/model/__init__.py b/nd2reader/model/__init__.py index 64bf398..d660b3c 100644 --- a/nd2reader/model/__init__.py +++ b/nd2reader/model/__init__.py @@ -1,3 +1,4 @@ +import collections import numpy as np import logging @@ -22,10 +23,10 @@ class Image(object): @property def timestamp(self): """ - The number of seconds after the beginning of the acquisition that the image was taken. Note that for a given field - of view and z-level offset, if you have images of multiple channels, they will all be given the same timestamp. - No, this doesn't make much sense. But that's how ND2s are structured, so if your experiment depends on millisecond - accuracy, you need to find an alternative imaging system. + The number of seconds after the beginning of the acquisition that the image was taken. Note that for a given + field of view and z-level offset, if you have images of multiple channels, they will all be given the same + timestamp. No, this doesn't make much sense. But that's how ND2s are structured, so if your experiment depends + on millisecond accuracy, you need to find an alternative imaging system. """ return self._timestamp / 1000.0 @@ -47,6 +48,16 @@ class Image(object): @property def is_valid(self): + """ + Not every image stored in an ND2 is a real image! If you take 4 images at one field of view and 2 at another + in a repeating cycle, there will be 4 images at BOTH field of view. The 2 non-images are the same size as all + the other images, only pure black (i.e. every pixel has a value of zero). + + This is probably an artifact of some algorithm in NIS Elements determining the maximum number of possible + images and pre-allocating the space with zeros. Regardless of why they exit, we can't tell that they're + not actual images until we examine the data. If every pixel value is exactly 0, it's a gap image. + + """ return np.any(self._raw_data) @@ -57,15 +68,27 @@ class ImageSet(object): """ def __init__(self): - self._images = [] + self._images = collections.defaultdict(dict) + + def get(self, channel="", z_level=0): + """ + Retrieve an image with a given channel and z-level. For most users, z_level will always be 0. + + """ + try: + image = self._images[channel][z_level] + except KeyError: + return None + else: + return image + + def __len__(self): + """ The number of images in the image set. """ + return sum([len(channel) for channel in self._images.values()]) def add(self, image): """ :type image: nd2reader.model.Image() """ - self._images.append(image) - - def __iter__(self): - for image in self._images: - yield image \ No newline at end of file + self._images[image.channel][image.z_level] = image \ No newline at end of file