Browse Source

#110 added many comments, fixed parameter names where frame_number was incorrectly used in place of index

zolfa-add_slices_loading
jim 9 years ago
parent
commit
cc2a49ac6d
3 changed files with 105 additions and 16 deletions
  1. +5
    -1
      nd2reader/common/v3.py
  2. +61
    -11
      nd2reader/driver/v3.py
  3. +39
    -4
      nd2reader/interface.py

+ 5
- 1
nd2reader/common/v3.py View File

@ -3,7 +3,11 @@ import struct
def read_chunk(fh, chunk_location):
"""
Gets the data for a given chunk pointer
Reads a piece of data given the location of its pointer.
:param fh: an open file handle to the ND2
:param chunk_location: a pointer
:type chunk_location: int
:rtype: bytes


+ 61
- 11
nd2reader/driver/v3.py View File

@ -10,38 +10,88 @@ from nd2reader.exc import NoImageError
class V3Driver(object):
"""
Accesses images from ND2 files made with NIS Elements 4.x. Confusingly, files of this type have a version number of 3.0+.
"""
def __init__(self, metadata, label_map, file_handle):
"""
:param metadata: a Metadata object
:param label_map: a raw dictionary of pointers to image locations
:param file_handle: an open file handle to the ND2
"""
self._metadata = metadata
self._label_map = label_map
self._file_handle = file_handle
def _calculate_field_of_view(self, frame_number):
def _calculate_field_of_view(self, index):
"""
Determines what field of view was being imaged for a given image.
:type index: int
:rtype: int
"""
images_per_cycle = len(self._metadata.z_levels) * len(self._metadata.channels)
return int((frame_number - (frame_number % images_per_cycle)) / images_per_cycle) % len(self._metadata.fields_of_view)
return int((index - (index % images_per_cycle)) / images_per_cycle) % len(self._metadata.fields_of_view)
def _calculate_channel(self, index):
"""
Determines what channel a particular image is.
:type index: int
:rtype: str
"""
return self._metadata.channels[index % len(self._metadata.channels)]
def _calculate_channel(self, frame_number):
return self._metadata.channels[frame_number % len(self._metadata.channels)]
def _calculate_z_level(self, index):
"""
Determines the plane in the z-axis a given image was taken in. In the future, this will be replaced with the actual offset in micrometers.
def _calculate_z_level(self, frame_number):
return self._metadata.z_levels[int(((frame_number - (frame_number % len(self._metadata.channels))) / len(self._metadata.channels)) % len(self._metadata.z_levels))]
:type index: int
:rtype: int
"""
return self._metadata.z_levels[int(((index - (index % len(self._metadata.channels))) / len(self._metadata.channels)) % len(self._metadata.z_levels))]
def _calculate_image_group_number(self, time_index, fov, z_level):
def _calculate_image_group_number(self, frame_number, fov, z_level):
"""
Images are grouped together if they share the same time index, field of view, and z-level.
:type time_index: int
:type frame_number: int
:type fov: int
:type z_level: int
:rtype: int
"""
return time_index * len(self._metadata.fields_of_view) * len(self._metadata.z_levels) + (fov * len(self._metadata.z_levels) + z_level)
return frame_number * len(self._metadata.fields_of_view) * len(self._metadata.z_levels) + (fov * len(self._metadata.z_levels) + z_level)
def _calculate_frame_number(self, image_group_number, field_of_view, z_level):
"""
Images are in the same frame if they share the same group number and field of view and are taken sequentially.
:type image_group_number: int
:type field_of_view: int
:type z_level: int
def _calculate_frame_number(self, image_group_number, fov, z_level):
return (image_group_number - (fov * len(self._metadata.z_levels) + z_level)) / (len(self._metadata.fields_of_view) * len(self._metadata.z_levels))
:rtype: int
"""
return (image_group_number - (field_of_view * len(self._metadata.z_levels) + z_level)) / (len(self._metadata.fields_of_view) * len(self._metadata.z_levels))
def get_image(self, index):
"""
Creates an Image object and adds its metadata, based on the index, which is simply the order in which the image was acquired. May return None if the ND2 contains
multiple channels and not all were taken in each cycle (for example, if you take bright field images every minute, and GFP images every five minutes, there will be some
indexes that do not contain an image. The reason for this is complicated, but suffice it to say that we hope to eliminate this possibility in future releases. For now,
you'll need to check if your image is None if you're doing anything as described above.
:type index: int
:rtype: Image or None
"""
channel_offset = index % len(self._metadata.channels)
fov = self._calculate_field_of_view(index)
channel = self._calculate_channel(index)


+ 39
- 4
nd2reader/interface.py View File

@ -82,28 +82,62 @@ class Nd2(object):
@property
def date(self):
"""
The date and time that the acquisition began. Not guaranteed to have been recorded.
:rtype: datetime.datetime() or None
"""
return self._metadata.date
@property
def z_levels(self):
"""
A list of integers that represent the different levels on the Z-axis that images were taken. Currently this is just a list of numbers from 0 to N.
For example, an ND2 where images were taken at -3µm, 0µm, and +5µm from a set position would be represented by 0, 1 and 2, respectively. ND2s do store the actual
offset of each image in micrometers and in the future this will hopefully be available. For now, however, you will have to match up the order yourself.
:return: list of int
"""
return self._metadata.z_levels
@property
def fields_of_view(self):
"""
A list of integers representing the various stage locations, in the order they were taken in the first round of acquisition.
:return: list of int
"""
return self._metadata.fields_of_view
@property
def channels(self):
"""
A list of channel (i.e. wavelength) names. These are set by the user in NIS Elements.
:return: list of str
"""
return self._metadata.channels
@property
def frames(self):
"""
A list of integers representing groups of images. ND2s consider images to be part of the same frame if they are in the same field of view and don't have the same channel.
So if you take a bright field and GFP image at four different fields of view over and over again, you'll have 8 images and 4 frames per cycle.
:return: list of int
"""
return self._metadata.frames
@property
def height(self):
"""
:return: height of each image, in pixels
The height of each image in pixels.
:rtype: int
"""
@ -112,7 +146,8 @@ class Nd2(object):
@property
def width(self):
"""
:return: width of each image, in pixels
The width of each image in pixels.
:rtype: int
"""
@ -120,7 +155,7 @@ class Nd2(object):
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.
Attempts to return the image with the unique combination of given attributes. None will be returned if a match is not found.
:type frame_number: int
:param field_of_view: the label for the place in the XY-plane where this image was taken.
@ -130,7 +165,7 @@ class Nd2(object):
: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()
:rtype: nd2reader.model.Image() or None
"""
return self._driver.get_image_by_attributes(frame_number, field_of_view, channel_name, z_level, self.height, self.width)


Loading…
Cancel
Save