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 struct
import six
from nd2reader.model.image import Image
class Nd2Parser(object):
@ -29,12 +30,22 @@ class Nd2Parser(object):
self._dimension_text = None
self._fields_of_view = None
self._label_map = {}
self.metadata = {}
self._metadata = {}
self._read_map()
self._time_indexes = None
self._parse_metadata()
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
def absolute_start(self):
"""
@ -144,7 +155,7 @@ class Nd2Parser(object):
self._fh = open(self._filename, "rb")
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.
@ -167,13 +178,13 @@ class Nd2Parser(object):
# 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
# 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
# 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
# them every cycle.
if np.any(image_data):
return timestamp, image_data
return timestamp, Image(image_data)
return None
@property


+ 43
- 41
nd2reader/interface.py View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from nd2reader.model import Image, ImageGroup
from nd2reader.model import ImageGroup
from nd2reader.driver import get_driver
from nd2reader.driver.version import get_version
import six
import warnings
class Nd2(object):
@ -12,13 +12,14 @@ class Nd2(object):
"""
def __init__(self, filename):
self._filename = filename
version = get_version(filename)
self._driver = get_driver(filename, version)
self._metadata = self._driver.get_metadata()
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),
"Frames: %s" % len(self.frames),
"Channels: %s" % ", ".join(["'%s'" % str(channel) for channel in self.channels]),
@ -43,32 +44,18 @@ class Nd2(object):
>>> nd2 = Nd2("my_images.nd2")
>>> image = nd2[16] # gets 17th frame
>>> 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
>>> do_something(image.data)
>>> do_something(image)
>>> 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
:rtype: nd2reader.model.Image() or generator
"""
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):
return self._slice(item.start, item.stop, item.step)
raise IndexError
@ -80,7 +67,7 @@ class Nd2(object):
:type start: int
:type stop: int
:type step: int
:rtype: nd2reader.model.Image() or None
:rtype: nd2reader.model.Image()
"""
start = start if start is not None else 0
@ -101,15 +88,37 @@ class Nd2(object):
: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 channel_name in self.channels:
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:
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
def height(self):
@ -118,7 +127,7 @@ class Nd2(object):
:rtype: int
"""
return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiHeight')]
return self._metadata.height
@property
def width(self):
@ -127,12 +136,11 @@ class Nd2(object):
: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):
"""
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
: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
:param z_level: the label for the location in the Z-plane where this image was taken.
: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
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.
@ -12,8 +15,6 @@ class Image(object):
:type timestamp: int
: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
@ -28,17 +29,13 @@ class Image(object):
"""
self._timestamp = timestamp
self._frame_number = int(frame_number)
self._raw_data = raw_array
self._field_of_view = field_of_view
self._channel = channel
self._z_level = z_level
self._height = height
self._width = width
self._data = None
def __repr__(self):
return "\n".join(["<ND2 Image>",
"%sx%s (HxW)" % (self._height, self._width),
"%sx%s (HxW)" % (self.height, self.width),
"Timestamp: %s" % self.timestamp,
"Frame: %s" % self._frame_number,
"Field of View: %s" % self.field_of_view,
@ -46,20 +43,6 @@ 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):
"""


Loading…
Cancel
Save