Browse Source

#66 interface refactoring is complete except for comments

feature/load_slices
jim 9 years ago
parent
commit
c096653a92
3 changed files with 64 additions and 68 deletions
  1. +15
    -4
      nd2reader/driver/v3.py
  2. +43
    -41
      nd2reader/interface.py
  3. +6
    -23
      nd2reader/model/image.py

+ 15
- 4
nd2reader/driver/v3.py View File

@ -6,6 +6,7 @@ import numpy as np
import re import re
import struct import struct
import six import six
from nd2reader.model.image import Image
class Nd2Parser(object): class Nd2Parser(object):
@ -29,12 +30,22 @@ class Nd2Parser(object):
self._dimension_text = None self._dimension_text = None
self._fields_of_view = None self._fields_of_view = None
self._label_map = {} self._label_map = {}
self.metadata = {}
self._metadata = {}
self._read_map() self._read_map()
self._time_indexes = None self._time_indexes = None
self._parse_metadata() self._parse_metadata()
self._z_levels = None self._z_levels = None
def get_image(self, index):
channel_offset = index % len(self._metadata.channels)
fov = self._calculate_field_of_view(index)
channel = self._calculate_channel(index)
z_level = self._calculate_z_level(index)
image_group_number = int(index / len(self._metadata.channels))
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)
@property @property
def absolute_start(self): def absolute_start(self):
""" """
@ -144,7 +155,7 @@ class Nd2Parser(object):
self._fh = open(self._filename, "rb") self._fh = open(self._filename, "rb")
return self._fh return self._fh
def _get_raw_image_data(self, image_group_number, channel_offset):
def _get_raw_image_data(self, image_group_number, channel_offset, height, width):
""" """
Reads the raw bytes and the timestamp of an image. Reads the raw bytes and the timestamp of an image.
@ -167,13 +178,13 @@ class Nd2Parser(object):
# The images for the various channels are interleaved within the same array. For example, the second image # The images for the various channels are interleaved within the same array. For example, the second image
# of a four image group will be composed of bytes 2, 6, 10, etc. If you understand why someone would design # of a four image group will be composed of bytes 2, 6, 10, etc. If you understand why someone would design
# a data structure that way, please send the author of this library a message. # a data structure that way, please send the author of this library a message.
image_data = image_group_data[image_data_start::len(self.channels)]
image_data = np.reshape(image_group_data[image_data_start::len(self.channels)], (height, width))
# Skip images that are all zeros! This is important, since NIS Elements creates blank "gap" images if you # Skip images that are all zeros! This is important, since NIS Elements creates blank "gap" images if you
# don't have the same number of images each cycle. We discovered this because we only took GFP images every # don't have the same number of images each cycle. We discovered this because we only took GFP images every
# other cycle to reduce phototoxicity, but NIS Elements still allocated memory as if we were going to take # other cycle to reduce phototoxicity, but NIS Elements still allocated memory as if we were going to take
# them every cycle. # them every cycle.
if np.any(image_data): if np.any(image_data):
return timestamp, image_data
return timestamp, Image(image_data)
return None return None
@property @property


+ 43
- 41
nd2reader/interface.py View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from nd2reader.model import Image, ImageGroup
from nd2reader.model import ImageGroup
from nd2reader.driver import get_driver from nd2reader.driver import get_driver
from nd2reader.driver.version import get_version from nd2reader.driver.version import get_version
import six
import warnings
class Nd2(object): class Nd2(object):
@ -12,13 +12,14 @@ class Nd2(object):
""" """
def __init__(self, filename): def __init__(self, filename):
self._filename = filename
version = get_version(filename) version = get_version(filename)
self._driver = get_driver(filename, version) self._driver = get_driver(filename, version)
self._metadata = self._driver.get_metadata() self._metadata = self._driver.get_metadata()
def __repr__(self): def __repr__(self):
return "\n".join(["<ND2 %s>" % self._driver._filename,
"Created: %s" % self._driver.absolute_start,
return "\n".join(["<ND2 %s>" % self._filename,
"Created: %s" % self.date,
"Image size: %sx%s (HxW)" % (self.height, self.width), "Image size: %sx%s (HxW)" % (self.height, self.width),
"Frames: %s" % len(self.frames), "Frames: %s" % len(self.frames),
"Channels: %s" % ", ".join(["'%s'" % str(channel) for channel in self.channels]), "Channels: %s" % ", ".join(["'%s'" % str(channel) for channel in self.channels]),
@ -43,32 +44,18 @@ class Nd2(object):
>>> nd2 = Nd2("my_images.nd2") >>> nd2 = Nd2("my_images.nd2")
>>> image = nd2[16] # gets 17th frame >>> image = nd2[16] # gets 17th frame
>>> for image in nd2[100:200]: # iterate over the 100th to 200th images >>> for image in nd2[100:200]: # iterate over the 100th to 200th images
>>> do_something(image.data)
>>> do_something(image)
>>> for image in nd2[::-1]: # iterate backwards >>> for image in nd2[::-1]: # iterate backwards
>>> do_something(image.data)
>>> do_something(image)
>>> for image in nd2[37:422:17]: # do something super weird if you really want to >>> for image in nd2[37:422:17]: # do something super weird if you really want to
>>> do_something(image.data)
>>> do_something(image)
:type item: int or slice :type item: int or slice
:rtype: nd2reader.model.Image() or generator :rtype: nd2reader.model.Image() or generator
""" """
if isinstance(item, int): if isinstance(item, int):
try:
channel_offset = item % len(self.channels)
fov = self._calculate_field_of_view(item)
channel = self._calculate_channel(item)
z_level = self._calculate_z_level(item)
image_group_number = int(item / len(self.channels))
frame_number = self._calculate_frame_number(image_group_number, fov, z_level)
timestamp, raw_image_data = self._get_raw_image_data(image_group_number, channel_offset)
image = Image(timestamp, frame_number, raw_image_data, fov, channel, z_level, self.height, self.width)
except (TypeError, ValueError):
return None
except KeyError:
raise IndexError("Invalid frame number.")
else:
return image
return self._driver.get_image(item)
elif isinstance(item, slice): elif isinstance(item, slice):
return self._slice(item.start, item.stop, item.step) return self._slice(item.start, item.stop, item.step)
raise IndexError raise IndexError
@ -80,7 +67,7 @@ class Nd2(object):
:type start: int :type start: int
:type stop: int :type stop: int
:type step: int :type step: int
:rtype: nd2reader.model.Image() or None
:rtype: nd2reader.model.Image()
""" """
start = start if start is not None else 0 start = start if start is not None else 0
@ -101,15 +88,37 @@ class Nd2(object):
:return: model.ImageSet() :return: model.ImageSet()
""" """
for time_index in self.time_indexes:
image_set = ImageGroup()
warnings.warn("nd2.image_sets will be removed from the nd2reader library in the near future.", DeprecationWarning)
for frame in self.frames:
image_group = ImageGroup()
for fov in self.fields_of_view: for fov in self.fields_of_view:
for channel_name in self.channels: for channel_name in self.channels:
for z_level in self.z_levels: for z_level in self.z_levels:
image = self.get_image(time_index, fov, channel_name, z_level)
image = self.get_image(frame, fov, channel_name, z_level)
if image is not None: if image is not None:
image_set.add(image)
yield image_set
image_group.add(image)
yield image_group
@property
def date(self):
return self._metadata.date
@property
def z_levels(self):
return self._metadata.z_levels
@property
def fields_of_view(self):
return self._metadata.fields_of_view
@property
def channels(self):
return self._metadata.channels
@property
def frames(self):
return self._metadata.frames
@property @property
def height(self): def height(self):
@ -118,7 +127,7 @@ class Nd2(object):
:rtype: int :rtype: int
""" """
return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiHeight')]
return self._metadata.height
@property @property
def width(self): def width(self):
@ -127,12 +136,11 @@ class Nd2(object):
:rtype: int :rtype: int
""" """
return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiWidth')]
return self._metadata.width
def get_image(self, frame_number, field_of_view, channel_name, z_level): def get_image(self, frame_number, field_of_view, channel_name, z_level):
""" """
Returns an Image if data exists for the given parameters, otherwise returns None. In general, you should avoid
using this method unless you're very familiar with the structure of ND2 files.
Returns an Image if data exists for the given parameters, otherwise returns None.
:type frame_number: int :type frame_number: int
:param field_of_view: the label for the place in the XY-plane where this image was taken. :param field_of_view: the label for the place in the XY-plane where this image was taken.
@ -141,14 +149,8 @@ class Nd2(object):
:type channel_name: str :type channel_name: str
:param z_level: the label for the location in the Z-plane where this image was taken. :param z_level: the label for the location in the Z-plane where this image was taken.
:type z_level: int :type z_level: int
:rtype: nd2reader.model.Image() or None
:rtype: nd2reader.model.Image()
""" """
image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level)
try:
timestamp, raw_image_data = self._get_raw_image_data(image_group_number, self._channel_offset[channel_name])
image = Image(timestamp, frame_number, raw_image_data, field_of_view, channel_name, z_level, self.height, self.width)
except TypeError:
return None
else:
return image
return self._driver.get_image_by_attributes(frame_number, field_of_view, channel_name, z_level)

+ 6
- 23
nd2reader/model/image.py View File

@ -3,8 +3,11 @@
import numpy as np import numpy as np
class Image(object):
def __init__(self, timestamp, frame_number, raw_array, field_of_view, channel, z_level, height, width):
class Image(np.ndarray):
def __new__(cls, array):
return np.asarray(array).view(cls)
def add_params(self, timestamp, frame_number, field_of_view, channel, z_level):
""" """
A wrapper around the raw pixel data of an image. A wrapper around the raw pixel data of an image.
@ -12,8 +15,6 @@ class Image(object):
:type timestamp: int :type timestamp: int
:param timestamp: The number of milliseconds after the beginning of the acquisition that this image was taken. :param timestamp: The number of milliseconds after the beginning of the acquisition that this image was taken.
:type timestamp: int :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. :param field_of_view: The label for the place in the XY-plane where this image was taken.
:type field_of_view: int :type field_of_view: int
:param channel: The name of the color of this image :param channel: The name of the color of this image
@ -28,17 +29,13 @@ class Image(object):
""" """
self._timestamp = timestamp self._timestamp = timestamp
self._frame_number = int(frame_number) self._frame_number = int(frame_number)
self._raw_data = raw_array
self._field_of_view = field_of_view self._field_of_view = field_of_view
self._channel = channel self._channel = channel
self._z_level = z_level self._z_level = z_level
self._height = height
self._width = width
self._data = None
def __repr__(self): def __repr__(self):
return "\n".join(["<ND2 Image>", return "\n".join(["<ND2 Image>",
"%sx%s (HxW)" % (self._height, self._width),
"%sx%s (HxW)" % (self.height, self.width),
"Timestamp: %s" % self.timestamp, "Timestamp: %s" % self.timestamp,
"Frame: %s" % self._frame_number, "Frame: %s" % self._frame_number,
"Field of View: %s" % self.field_of_view, "Field of View: %s" % self.field_of_view,
@ -46,20 +43,6 @@ class Image(object):
"Z-Level: %s" % self.z_level, "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 @property
def field_of_view(self): def field_of_view(self):
""" """


Loading…
Cancel
Save