diff --git a/nd2reader/model/__init__.py b/nd2reader/model/__init__.py index 55d57c3..bd547a1 100644 --- a/nd2reader/model/__init__.py +++ b/nd2reader/model/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import collections import numpy as np import logging @@ -7,6 +9,25 @@ log = logging.getLogger(__name__) class Image(object): def __init__(self, timestamp, raw_array, field_of_view, channel, z_level, height, width): + """ + A wrapper around the raw pixel data of an image. + + :param timestamp: The number of milliseconds after the beginning of the acquisition that this image was taken. + :type timestamp: int + :param raw_array: The raw sequence of bytes that represents the image. + :type raw_array: array.array() + :param field_of_view: The label for the place in the XY-plane where this image was taken. + :type field_of_view: int + :param channel: The name of the color of this image + :type channel: str + :param z_level: The label for the location in the Z-plane where this image was taken. + :type z_level: int + :param height: The height of the image in pixels. + :type height: int + :param width: The width of the image in pixels. + :type width: int + + """ self._timestamp = timestamp self._raw_data = raw_array self._field_of_view = field_of_view @@ -25,8 +46,28 @@ class Image(object): "Z-Level: %s" % self.z_level, ]) + @property + def data(self): + """ + The actual image data. + + :rtype np.array() + + """ + if self._data is None: + # The data is just a 1-dimensional array originally. + # We convert it to a 2D image here. + self._data = np.reshape(self._raw_data, (self._height, self._width)) + return self._data + @property def field_of_view(self): + """ + Which of the fixed locations this image was taken at. + + :rtype int: + + """ return self._field_of_view @property @@ -37,56 +78,69 @@ class Image(object): 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. + :rtype float: + """ return self._timestamp / 1000.0 @property def channel(self): + """ + The name of the filter used to acquire this image. These are user-supplied in NIS Elements. + + :rtype str: + + """ return self._channel @property def z_level(self): - return self._z_level + """ + The vertical offset of the image. These are simple integers starting from 0, where the 0 is the lowest + z-level and each subsequent level incremented by 1. - @property - def data(self): - if self._data is None: - # The data is just a flat, 1-dimensional array. We convert it to a 2D image here. - self._data = np.reshape(self._raw_data, (self._height, self._width)) - return self._data + For example, if you acquired images at -3 µm, 0 µm, and +3 µm, your z-levels would be: + + -3 µm: 0 + 0 µm: 1 + +3 µm: 2 + + :rtype int: + + """ + return self._z_level class ImageSet(object): """ - A group of images that share the same timestamp. NIS Elements doesn't store a unique timestamp for every - image, rather, it stores one for each set of images that share the same field of view and z-axis level. + A group of images that were taken at roughly the same time. """ def __init__(self): self._images = collections.defaultdict(dict) + def __len__(self): + """ The number of images in the image set. """ + return sum([len(channel) for channel in self._images.values()]) + def __repr__(self): return "\n".join(["", "Image count: %s" % len(self)]) - def get(self, channel="", z_level=0): + 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 + :type channel: str + :type z_level: int - def __len__(self): - """ The number of images in the image set. """ - return sum([len(channel) for channel in self._images.values()]) + """ + return self._images.get(channel).get(z_level) def add(self, image): """ + Stores an image. + :type image: nd2reader.model.Image() """