Browse Source

docstrings

feature/load_slices
Ruben Verweij 7 years ago
parent
commit
dab0b4aca8
6 changed files with 400 additions and 10 deletions
  1. +1
    -1
      docs
  2. +44
    -5
      nd2reader/common.py
  3. +123
    -0
      nd2reader/label_map.py
  4. +76
    -1
      nd2reader/parser.py
  5. +131
    -3
      nd2reader/raw_metadata.py
  6. +25
    -0
      nd2reader/reader.py

+ 1
- 1
docs

@ -1 +1 @@
Subproject commit 3db501b0c85343425c6e8269dcfce5b0ca28c845
Subproject commit 248bbb1aa20c11fe1c82c092ce68a3d9526156d2

+ 44
- 5
nd2reader/common.py View File

@ -69,6 +69,16 @@ def read_chunk(fh, chunk_location):
def read_array(fh, kind, chunk_location):
"""
Args:
fh:
kind:
chunk_location:
Returns:
"""
kinds = {'double': 'd',
'int': 'i',
'float': 'f'}
@ -110,10 +120,13 @@ def _parse_char_array(data):
def parse_date(text_info):
"""The date and time when acquisition began.
"""
The date and time when acquisition began.
Args:
text_info:
Returns:
datetime: The date and time when acquisition began.
"""
for line in text_info.values():
@ -133,8 +146,11 @@ def parse_date(text_info):
def _parse_metadata_item(data, cursor_position):
"""Reads hierarchical data, analogous to a Python dict.
Args:
data:
cursor_position:
Returns:
dict: the metadata item
"""
new_count, length = struct.unpack("<IQ", data.read(12))
@ -149,6 +165,13 @@ def _parse_metadata_item(data, cursor_position):
def _get_value(data, data_type, cursor_position):
"""ND2s use various codes to indicate different data types, which we translate here.
Args:
data:
data_type:
cursor_position:
Returns:
"""
parser = {1: _parse_unsigned_char,
2: _parse_unsigned_int,
@ -162,7 +185,14 @@ def _get_value(data, data_type, cursor_position):
def read_metadata(data, count):
"""Iterates over each element some section of the metadata and parses it.
"""
Iterates over each element some section of the metadata and parses it.
Args:
data:
count:
Returns:
"""
if data is None:
@ -192,7 +222,16 @@ def read_metadata(data, count):
def _add_to_metadata(metadata, name, value):
"""Add the name value pair to the metadata dict
"""
Add the name value pair to the metadata dict
Args:
metadata:
name:
value:
Returns:
"""
if name not in metadata.keys():
metadata[name] = value


+ 123
- 0
nd2reader/label_map.py View File

@ -12,6 +12,11 @@ class LabelMap(object):
self._image_data = {}
def image_attributes(self):
"""
Returns:
"""
return self._get_location(six.b("ImageAttributesLV!"))
def _get_location(self, label):
@ -27,18 +32,41 @@ class LabelMap(object):
@property
def image_text_info(self):
"""
Returns:
"""
return self._get_location(six.b("ImageTextInfoLV!"))
@property
def image_metadata(self):
"""
Returns:
"""
return self._get_location(six.b("ImageMetadataLV!"))
@property
def image_metadata_sequence(self):
# there is always only one of these, even though it has a pipe followed by a zero, which is how they do indexes
"""
Returns:
"""
return self._get_location(six.b("ImageMetadataSeqLV|0!"))
def get_image_data_location(self, index):
"""
Args:
index:
Returns:
"""
if not self._image_data:
regex = re.compile(six.b("""ImageDataSeq\|(\d+)!"""))
for match in regex.finditer(self._data):
@ -49,76 +77,171 @@ class LabelMap(object):
@property
def image_calibration(self):
"""
Returns:
"""
return self._get_location(six.b("ImageCalibrationLV|0!"))
@property
def image_attributes(self):
"""
Returns:
"""
return self._get_location(six.b("ImageAttributesLV!"))
@property
def x_data(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|X!"))
@property
def y_data(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|Y!"))
@property
def z_data(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|Z!"))
@property
def roi_metadata(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|RoiMetadata_v1!"))
@property
def pfs_status(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|PFS_STATUS!"))
@property
def pfs_offset(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|PFS_OFFSET!"))
@property
def guid(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|GUIDStore!"))
@property
def description(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|CustomDescriptionV1_0!"))
@property
def camera_exposure_time(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|Camera_ExposureTime1!"))
@property
def camera_temp(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|CameraTemp1!"))
@property
def acquisition_times(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|AcqTimesCache!"))
@property
def acquisition_times_2(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|AcqTimes2Cache!"))
@property
def acquisition_frames(self):
"""
Returns:
"""
return self._get_location(six.b("CustomData|AcqFramesCache!"))
@property
def lut_data(self):
"""
Returns:
"""
return self._get_location(six.b("CustomDataVar|LUTDataV1_0!"))
@property
def grabber_settings(self):
"""
Returns:
"""
return self._get_location(six.b("CustomDataVar|GrabberCameraSettingsV1_0!"))
@property
def custom_data(self):
"""
Returns:
"""
return self._get_location(six.b("CustomDataVar|CustomDataV2_0!"))
@property
def app_info(self):
"""
Returns:
"""
return self._get_location(six.b("CustomDataVar|AppInfo_V1_0!"))

+ 76
- 1
nd2reader/parser.py View File

@ -37,6 +37,12 @@ class Parser(object):
def calculate_image_properties(self, index):
"""Calculate FOV, channels and z_levels
Args:
index(int): the index (which is simply the order in which the image was acquired)
Returns:
tuple: tuple of the field of view, the channel and the z level
"""
field_of_view = self._calculate_field_of_view(index)
channel = self._calculate_channel(index)
@ -52,6 +58,12 @@ class Parser(object):
eliminate this possibility in future releases. For now, you'll need to check if your image is None if you're
doing anything out of the ordinary.
Args:
index(int): the index (which is simply the order in which the image was acquired)
Returns:
Frame: the image
"""
field_of_view, channel, z_level = self.calculate_image_properties(index)
channel_offset = index % len(self.metadata["channels"])
@ -66,7 +78,18 @@ class Parser(object):
return image
def get_image_by_attributes(self, frame_number, field_of_view, channel_name, z_level, height, width):
"""Attempts to get Image based on attributes alone.
"""Gets an image based on its attributes alone
Args:
frame_number: the frame number
field_of_view: the field of view
channel_name: the color channel name
z_level: the z level
height: the height of the image
width: the width of the image
Returns:
Frame: the requested image
"""
image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level)
@ -91,6 +114,12 @@ class Parser(object):
def _check_version_supported(self):
"""Checks if the ND2 file version is supported by this reader.
Returns:
bool: True on supported
Raises:
InvalidVersionError: Raises an error if the version is unsupported
"""
major_version, minor_version = get_version(self._fh)
supported = self.supported_file_versions.get((major_version, minor_version)) or \
@ -116,6 +145,9 @@ class Parser(object):
as some of the bytes contain the value 33, which is the ASCII code for "!". So we iteratively find each label,
grab the subsequent data (always 16 bytes long), advance to the next label and repeat.
Returns:
LabelMap: the computed label map
"""
self._fh.seek(-8, 2)
chunk_map_start_location = struct.unpack("Q", self._fh.read(8))[0]
@ -126,6 +158,11 @@ class Parser(object):
def _calculate_field_of_view(self, index):
"""Determines what field of view was being imaged for a given image.
Args:
index(int): the index (which is simply the order in which the image was acquired)
Returns:
int: the field of view
"""
images_per_cycle = len(self.metadata["z_levels"]) * len(self.metadata["channels"])
return int((index - (index % images_per_cycle)) / images_per_cycle) % len(self.metadata["fields_of_view"])
@ -133,6 +170,12 @@ class Parser(object):
def _calculate_channel(self, index):
"""Determines what channel a particular image is.
Args:
index(int): the index (which is simply the order in which the image was acquired)
Returns:
string: the name of the color channel
"""
return self.metadata["channels"][index % len(self.metadata["channels"])]
@ -141,6 +184,12 @@ class Parser(object):
In the future, this will be replaced with the actual offset in micrometers.
Args:
index(int): the index (which is simply the order in which the image was acquired)
Returns:
The z level
"""
return self.metadata["z_levels"][int(
((index - (index % len(self.metadata["channels"]))) / len(self.metadata["channels"])) % len(
@ -150,6 +199,14 @@ class Parser(object):
"""
Images are grouped together if they share the same time index, field of view, and z-level.
Args:
frame_number:
fov:
z_level:
Returns:
int: the image group number
"""
return frame_number * len(self.metadata["fields_of_view"]) * len(self.metadata["z_levels"]) + (
fov * len(self.metadata["z_levels"]) + z_level)
@ -158,6 +215,13 @@ class Parser(object):
"""
Images are in the same frame if they share the same group number and field of view and are taken sequentially.
Args:
image_group_number:
field_of_view:
z_level:
Returns:
"""
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"]))
@ -168,12 +232,23 @@ class Parser(object):
Image data is interleaved for each image set. That is, if there are four images in a set, the first image
will consist of pixels 1, 5, 9, etc, the second will be pixels 2, 6, 10, and so forth.
Returns:
dict: the channel offset for each channel
"""
return {channel: n for n, channel in enumerate(self.metadata["channels"])}
def _get_raw_image_data(self, image_group_number, channel_offset, height, width):
"""Reads the raw bytes and the timestamp of an image.
Args:
image_group_number:
channel_offset:
height:
width:
Returns:
"""
chunk = self._label_map.get_image_data_location(image_group_number)
data = read_chunk(self._fh, chunk)


+ 131
- 3
nd2reader/raw_metadata.py View File

@ -7,7 +7,25 @@ import numpy as np
def ignore_missing(func):
"""
Args:
func:
Returns:
"""
def wrapper(*args, **kwargs):
"""
Args:
*args:
**kwargs:
Returns:
"""
try:
return func(*args, **kwargs)
except:
@ -17,6 +35,10 @@ def ignore_missing(func):
class RawMetadata(object):
"""
RawMetadata class parses and stores the raw metadata that is read from the binary file in dict format
"""
def __init__(self, fh, label_map):
self._fh = fh
self._label_map = label_map
@ -163,6 +185,11 @@ class RawMetadata(object):
This includes the position and size at the given timepoints.
Args:
raw_roi_dict:
Returns:
"""
number_of_timepoints = raw_roi_dict[six.b('m_vectAnimParams_Size')]
@ -204,8 +231,14 @@ class RawMetadata(object):
return None
def _parse_vect_anim(self, roi_dict, animation_dict):
"""Parses a ROI vector animation object and adds it to the global list of timepoints and positions.
"""
Parses a ROI vector animation object and adds it to the global list of timepoints and positions.
Args:
roi_dict:
animation_dict:
Returns:
"""
roi_dict["timepoints"].append(animation_dict[six.b('m_dTimeMs')])
@ -249,8 +282,15 @@ class RawMetadata(object):
self._metadata_parsed['experiment'] = experimental_data
def _parse_loop_data(self, loop_data):
"""Parse the experimental loop data
@staticmethod
def _parse_loop_data(loop_data):
"""
Parse the experimental loop data
Args:
loop_data:
Returns:
"""
loops = [loop_data]
@ -296,81 +336,160 @@ class RawMetadata(object):
@property
@ignore_missing
def image_text_info(self):
"""
Returns:
"""
return read_metadata(read_chunk(self._fh, self._label_map.image_text_info), 1)
@property
@ignore_missing
def image_metadata_sequence(self):
"""
Returns:
"""
return read_metadata(read_chunk(self._fh, self._label_map.image_metadata_sequence), 1)
@property
@ignore_missing
def image_calibration(self):
"""
Returns:
"""
return read_metadata(read_chunk(self._fh, self._label_map.image_calibration), 1)
@property
@ignore_missing
def image_attributes(self):
"""
Returns:
"""
return read_metadata(read_chunk(self._fh, self._label_map.image_attributes), 1)
@property
@ignore_missing
def x_data(self):
"""
Returns:
"""
return read_array(self._fh, 'double', self._label_map.x_data)
@property
@ignore_missing
def y_data(self):
"""
Returns:
"""
return read_array(self._fh, 'double', self._label_map.y_data)
@property
@ignore_missing
def z_data(self):
"""
Returns:
"""
return read_array(self._fh, 'double', self._label_map.z_data)
@property
@ignore_missing
def roi_metadata(self):
"""
Returns:
"""
return read_metadata(read_chunk(self._fh, self._label_map.roi_metadata), 1)
@property
@ignore_missing
def pfs_status(self):
"""
Returns:
"""
return read_array(self._fh, 'int', self._label_map.pfs_status)
@property
@ignore_missing
def pfs_offset(self):
"""
Returns:
"""
return read_array(self._fh, 'int', self._label_map.pfs_offset)
@property
@ignore_missing
def camera_exposure_time(self):
"""
Returns:
"""
return read_array(self._fh, 'double', self._label_map.camera_exposure_time)
@property
@ignore_missing
def lut_data(self):
"""
Returns:
"""
return xmltodict.parse(read_chunk(self._fh, self._label_map.lut_data))
@property
@ignore_missing
def grabber_settings(self):
"""
Returns:
"""
return xmltodict.parse(read_chunk(self._fh, self._label_map.grabber_settings))
@property
@ignore_missing
def custom_data(self):
"""
Returns:
"""
return xmltodict.parse(read_chunk(self._fh, self._label_map.custom_data))
@property
@ignore_missing
def app_info(self):
"""
Returns:
"""
return xmltodict.parse(read_chunk(self._fh, self._label_map.app_info))
@property
@ignore_missing
def camera_temp(self):
"""
Yields:
float: the temperature
"""
camera_temp = read_array(self._fh, 'double', self._label_map.camera_temp)
if camera_temp:
for temp in map(lambda x: round(x * 100.0, 2), camera_temp):
@ -379,6 +498,10 @@ class RawMetadata(object):
@property
@ignore_missing
def acquisition_times(self):
"""
Yields:
float: the acquisition time
"""
acquisition_times = read_array(self._fh, 'double', self._label_map.acquisition_times)
if acquisition_times:
for acquisition_time in map(lambda x: x / 1000.0, acquisition_times):
@ -387,5 +510,10 @@ class RawMetadata(object):
@property
@ignore_missing
def image_metadata(self):
"""
Returns:
"""
if self._label_map.image_metadata:
return read_metadata(read_chunk(self._fh, self._label_map.image_metadata), 1)

+ 25
- 0
nd2reader/reader.py View File

@ -41,6 +41,12 @@ class ND2Reader(FramesSequenceND):
def get_frame(self, i):
"""Return one frame
Args:
i: The frame number
Returns:
numpy.ndarray: The requested frame
"""
fetch_all_channels = 'c' in self.bundle_axes
@ -52,6 +58,12 @@ class ND2Reader(FramesSequenceND):
def _get_frame_all_channels(self, i):
"""Get all color channels for this frame
Args:
i: The frame number
Returns:
numpy.ndarray: The requested frame, with all color channels.
"""
frames = None
for c in range(len(self.metadata["channels"])):
@ -65,6 +77,14 @@ class ND2Reader(FramesSequenceND):
def get_frame_2D(self, c, t, z):
"""Gets a given frame using the parser
Args:
c: The color channel number
t: The frame number
z: The z stack number
Returns:
numpy.ndarray: The requested frame
"""
c_name = self.metadata["channels"][c]
return self._parser.get_image_by_attributes(t, 0, c_name, z, self.metadata["height"], self.metadata["width"])
@ -73,6 +93,8 @@ class ND2Reader(FramesSequenceND):
def pixel_type(self):
"""Return the pixel data type
Returns: the pixel data type
"""
return self._dtype
@ -92,6 +114,9 @@ class ND2Reader(FramesSequenceND):
def get_timesteps(self):
"""Get the timesteps of the experiment
Returns:
np.ndarray: an array of times in milliseconds.
"""
timesteps = np.array([])
current_time = 0.0


Loading…
Cancel
Save