Browse Source

Cleanup docstrings to use google python docs

feature/load_slices
Ruben Verweij 7 years ago
parent
commit
5f863a3ca5
8 changed files with 95 additions and 208 deletions
  1. +1
    -1
      docs
  2. +28
    -29
      nd2reader/common.py
  3. +4
    -2
      nd2reader/exceptions.py
  4. +1
    -2
      nd2reader/label_map.py
  5. +20
    -74
      nd2reader/parser.py
  6. +21
    -47
      nd2reader/raw_metadata.py
  7. +18
    -30
      nd2reader/reader.py
  8. +2
    -23
      sphinx/conf.py

+ 1
- 1
docs

@ -1 +1 @@
Subproject commit db1df91ec6d52b25ac2a2368fdef6b18277e8961
Subproject commit 3db501b0c85343425c6e8269dcfce5b0ca28c845

+ 28
- 29
nd2reader/common.py View File

@ -7,11 +7,13 @@ from nd2reader.exceptions import InvalidVersionError
def get_version(fh):
"""
Determines what version the ND2 is.
"""Determines what version the ND2 is.
Args:
fh: File handle of the .nd2 file
:param fh: an open file handle to the ND2
:type fh: file
Returns:
tuple: Major and minor version
"""
# the first 16 bytes seem to have no meaning, so we skip them
@ -23,11 +25,13 @@ def get_version(fh):
def parse_version(data):
"""
Parses a string with the version data in it.
"""Parses a string with the version data in it.
:param data: the 19th through 54th byte of the ND2, representing the version
:type data: unicode
Args:
data (unicode): the 19th through 54th byte of the ND2, representing the version
Returns:
tuple: Major and minor version
"""
match = re.search(r"""^ND2 FILE SIGNATURE CHUNK NAME01!Ver(?P<major>\d)\.(?P<minor>\d)$""", data)
@ -40,14 +44,14 @@ def parse_version(data):
def read_chunk(fh, chunk_location):
"""
Reads a piece of data given the location of its 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
Args:
fh: an open file handle to the ND2
chunk_location (int): location to read
:rtype: bytes
Returns:
bytes: the data at the chunk location
"""
if chunk_location is None:
@ -106,10 +110,10 @@ def _parse_char_array(data):
def parse_date(text_info):
"""
The date and time when acquisition began.
"""The date and time when acquisition began.
:rtype: datetime.datetime() or None
Returns:
datetime: The date and time when acquisition began.
"""
for line in text_info.values():
@ -127,8 +131,10 @@ def parse_date(text_info):
def _parse_metadata_item(data, cursor_position):
"""
Reads hierarchical data, analogous to a Python dict.
"""Reads hierarchical data, analogous to a Python dict.
Returns:
dict: the metadata item
"""
new_count, length = struct.unpack("<IQ", data.read(12))
@ -141,8 +147,7 @@ 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.
"""ND2s use various codes to indicate different data types, which we translate here.
"""
parser = {1: _parse_unsigned_char,
@ -157,8 +162,7 @@ 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.
"""
if data is None:
@ -188,12 +192,7 @@ def read_metadata(data, count):
def _add_to_metadata(metadata, name, value):
"""
Add the name value pair to the metadata dict
:param metadata:
:param name:
:param value:
:return:
"""Add the name value pair to the metadata dict
"""
if name not in metadata.keys():
metadata[name] = value


+ 4
- 2
nd2reader/exceptions.py View File

@ -1,5 +1,6 @@
class InvalidVersionError(Exception):
"""
"""Unknown version.
We don't know how to parse the version of ND2 that we were given.
"""
@ -7,7 +8,8 @@ class InvalidVersionError(Exception):
class NoImageError(Exception):
"""
"""No image found.
Some apparent images in ND2s are just completely blank placeholders. These are used when the number of images per
cycle are unequal (e.g. if you take fluorescent images every 2 minutes, and bright field images every minute).


+ 1
- 2
nd2reader/label_map.py View File

@ -4,8 +4,7 @@ import re
class LabelMap(object):
"""
Contains pointers to metadata. This might only be valid for V3 files.
"""Contains pointers to metadata. This might only be valid for V3 files.
"""
def __init__(self, raw_binary_data):


+ 20
- 74
nd2reader/parser.py View File

@ -13,7 +13,9 @@ from nd2reader.raw_metadata import RawMetadata
class Parser(object):
""" Parses ND2 files and creates a Metadata and driver object. """
"""Parses ND2 files and creates a Metadata and driver object.
"""
CHUNK_HEADER = 0xabeceda
CHUNK_MAP_START = six.b("ND2 FILEMAP SIGNATURE NAME 0001!")
CHUNK_MAP_END = six.b("ND2 CHUNK MAP SIGNATURE 0000001!")
@ -21,10 +23,6 @@ class Parser(object):
supported_file_versions = {(3, None): True}
def __init__(self, fh):
"""
:type fh: file
"""
self._fh = fh
self._label_map = None
self._raw_metadata = None
@ -37,10 +35,8 @@ class Parser(object):
self._parse_metadata()
def calculate_image_properties(self, index):
"""
Calculate FOV, channels and z_levels
:param index:
:return:
"""Calculate FOV, channels and z_levels
"""
field_of_view = self._calculate_field_of_view(index)
channel = self._calculate_channel(index)
@ -56,9 +52,6 @@ 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.
:type index: int
:rtype: Image or None
"""
field_of_view, channel, z_level = self.calculate_image_properties(index)
channel_offset = index % len(self.metadata["channels"])
@ -73,17 +66,8 @@ 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.
"""Attempts to get Image based on attributes alone.
:type frame_number: int
:type field_of_view: int
:type channel_name: str
:type z_level: int
:type height: int
:type width: int
:rtype: Image or None
"""
image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level)
try:
@ -96,18 +80,17 @@ class Parser(object):
@staticmethod
def get_dtype_from_metadata():
"""
Determine the data type from the metadata.
"""Determine the data type from the metadata.
For now, always use float64 to prevent unexpected overflow errors when manipulating the data (calculating sums/
means/etc.)
:return:
"""
return np.float64
def _check_version_supported(self):
"""
Checks if the ND2 file version is supported by this reader.
:return:
"""Checks if the ND2 file version is supported by this reader.
"""
major_version, minor_version = get_version(self._fh)
supported = self.supported_file_versions.get((major_version, minor_version)) or \
@ -119,8 +102,7 @@ class Parser(object):
return supported
def _parse_metadata(self):
"""
Reads all metadata and instantiates the Metadata object.
"""Reads all metadata and instantiates the Metadata object.
"""
# Retrieve raw metadata from the label mapping
@ -134,8 +116,6 @@ 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.
:rtype: LabelMap
"""
self._fh.seek(-8, 2)
chunk_map_start_location = struct.unpack("Q", self._fh.read(8))[0]
@ -144,33 +124,23 @@ class Parser(object):
return LabelMap(raw_text)
def _calculate_field_of_view(self, index):
"""
Determines what field of view was being imaged for a given image.
:type index: int
:rtype: int
"""Determines what field of view was being imaged for a given image.
"""
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"])
def _calculate_channel(self, index):
"""
Determines what channel a particular image is.
:type index: int
:rtype: str
"""Determines what channel a particular image is.
"""
return self.metadata["channels"][index % 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.
"""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.
:type index: int
:rtype: int
"""
return self.metadata["z_levels"][int(
((index - (index % len(self.metadata["channels"]))) / len(self.metadata["channels"])) % len(
@ -180,12 +150,6 @@ class Parser(object):
"""
Images are grouped together if they share the same time index, field of view, and z-level.
:type frame_number: int
:type fov: int
:type z_level: int
:rtype: int
"""
return frame_number * len(self.metadata["fields_of_view"]) * len(self.metadata["z_levels"]) + (
fov * len(self.metadata["z_levels"]) + z_level)
@ -194,12 +158,6 @@ class Parser(object):
"""
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
: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"]))
@ -210,22 +168,11 @@ 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.
:rtype: dict
"""
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.
:param image_group_number: groups are made of images with the same time index, field of view and z-level
:type image_group_number: int
:param channel_offset: the offset in the array where the bytes for this image are found
:type channel_offset: int
:rtype: (int, Image)
:raises: NoImageError
"""Reads the raw bytes and the timestamp of an image.
"""
chunk = self._label_map.get_image_data_location(image_group_number)
@ -254,8 +201,7 @@ class Parser(object):
raise NoImageError
def _get_frame_metadata(self):
"""
Get the metadata for one frame
:return:
"""Get the metadata for one frame
"""
return self.metadata

+ 21
- 47
nd2reader/raw_metadata.py View File

@ -24,16 +24,14 @@ class RawMetadata(object):
@property
def __dict__(self):
"""
Returns the parsed metadata in dictionary form
:return:
"""Returns the parsed metadata in dictionary form
"""
return self.get_parsed_metadata()
def get_parsed_metadata(self):
"""
Returns the parsed metadata in dictionary form
:return:
""" Returns the parsed metadata in dictionary form
"""
if self._metadata_parsed is not None:
@ -63,8 +61,6 @@ class RawMetadata(object):
These are labels created by the NIS Elements user. Typically they may a short description of the filter cube
used (e.g. "bright field", "GFP", etc.)
:rtype: list
"""
channels = []
metadata = self.image_metadata_sequence[six.b('SLxPictureMetadata')][six.b('sPicturePlanes')]
@ -90,25 +86,19 @@ class RawMetadata(object):
in the image data, so we have to calculate it. There probably is something somewhere, since
NIS Elements can figure it out, but we haven't found it yet.
:rtype: list
"""
return self._parse_dimension(r""".*?XY\((\d+)\).*?""")
def _parse_frames(self):
"""
The number of cycles.
:rtype: list
"""The number of cycles.
"""
return self._parse_dimension(r""".*?T'?\((\d+)\).*?""")
def _parse_z_levels(self):
"""
The different levels in the Z-plane. Just a sequence from 0 to n.
"""The different levels in the Z-plane.
:rtype: list
Just a sequence from 0 to n.
"""
return self._parse_dimension(r""".*?Z\((\d+)\).*?""")
@ -119,8 +109,6 @@ class RawMetadata(object):
Sometimes certain elements don't exist, or change their data type randomly. However, the human-readable text
is always there and in the same exact format, so we just parse that instead.
:rtype: str
"""
dimension_text = six.b("")
textinfo = self.image_text_info[six.b('SLxImageTextInfo')].values()
@ -135,13 +123,6 @@ class RawMetadata(object):
return dimension_text
def _parse_dimension(self, pattern):
"""
:param pattern: a valid regex pattern
:type pattern: str
:rtype: list of int
"""
dimension_text = self._parse_dimension_text()
if six.PY3:
dimension_text = dimension_text.decode("utf8")
@ -152,18 +133,16 @@ class RawMetadata(object):
return list(range(count))
def _parse_total_images_per_channel(self):
"""
The total number of images per channel. Warning: this may be inaccurate as it includes "gap" images.
"""The total number of images per channel.
:rtype: int
Warning: this may be inaccurate as it includes "gap" images.
"""
return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiSequenceCount')]
def _parse_roi_metadata(self):
"""
Parse the raw ROI metadata.
:return:
"""Parse the raw ROI metadata.
"""
if self.roi_metadata is None or not six.b('RoiMetadata_v1') in self.roi_metadata:
return
@ -180,11 +159,10 @@ class RawMetadata(object):
self._metadata_parsed['rois'] = roi_objects
def _parse_roi(self, raw_roi_dict):
"""
Extract the vector animation parameters from the ROI.
"""Extract the vector animation parameters from the ROI.
This includes the position and size at the given timepoints.
:param raw_roi_dict:
:return:
"""
number_of_timepoints = raw_roi_dict[six.b('m_vectAnimParams_Size')]
@ -226,10 +204,9 @@ 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.
:param animation_dict:
:return:
"""Parses a ROI vector animation object and adds it to the global list of timepoints and positions.
"""
roi_dict["timepoints"].append(animation_dict[six.b('m_dTimeMs')])
@ -251,9 +228,8 @@ class RawMetadata(object):
return roi_dict
def _parse_experiment_metadata(self):
"""
Parse the metadata of the ND experiment
:return:
"""Parse the metadata of the ND experiment
"""
if not six.b('SLxExperiment') in self.image_metadata:
return
@ -274,10 +250,8 @@ class RawMetadata(object):
self._metadata_parsed['experiment'] = experimental_data
def _parse_loop_data(self, loop_data):
"""
Parse the experimental loop data
:param loop_data:
:return:
"""Parse the experimental loop data
"""
loops = [loop_data]
if six.b('uiPeriodCount') in loop_data and loop_data[six.b('uiPeriodCount')] > 0:


+ 18
- 30
nd2reader/reader.py View File

@ -4,8 +4,8 @@ import numpy as np
class ND2Reader(FramesSequenceND):
"""
PIMS wrapper for the ND2 parser
"""PIMS wrapper for the ND2 parser
"""
def __init__(self, filename):
@ -26,25 +26,21 @@ class ND2Reader(FramesSequenceND):
@classmethod
def class_exts(cls):
"""
So PIMS open function can use this reader for opening .nd2 files
:return:
"""Let PIMS open function use this reader for opening .nd2 files
"""
return {'nd2'} | super(ND2Reader, cls).class_exts()
def close(self):
"""
Correctly close the file handle
:return:
"""Correctly close the file handle
"""
if self._fh is not None:
self._fh.close()
def get_frame(self, i):
"""
Return one frame
:param i:
:return:
"""Return one frame
"""
fetch_all_channels = 'c' in self.bundle_axes
@ -54,9 +50,8 @@ class ND2Reader(FramesSequenceND):
return self.get_frame_2D(self.default_coords['c'], i, self.default_coords['z'])
def _get_frame_all_channels(self, i):
"""
Get all color channels for this frame
:return:
"""Get all color channels for this frame
"""
frames = None
for c in range(len(self.metadata["channels"])):
@ -68,28 +63,22 @@ class ND2Reader(FramesSequenceND):
return frames
def get_frame_2D(self, c, t, z):
"""
Gets a given frame using the parser
:param c:
:param t:
:param z:
:return:
"""Gets a given frame using the parser
"""
c_name = self.metadata["channels"][c]
return self._parser.get_image_by_attributes(t, 0, c_name, z, self.metadata["height"], self.metadata["width"])
@property
def pixel_type(self):
"""
Return the pixel data type
:return:
"""Return the pixel data type
"""
return self._dtype
def _setup_axes(self):
"""
Setup the xyctz axes, iterate over t axis by default
:return:
"""Setup the xyctz axes, iterate over t axis by default
"""
self._init_axis('x', self.metadata["width"])
self._init_axis('y', self.metadata["height"])
@ -101,9 +90,8 @@ class ND2Reader(FramesSequenceND):
self.iter_axes = 't'
def get_timesteps(self):
"""
Get the timesteps of the experiment
:return:
"""Get the timesteps of the experiment
"""
timesteps = np.array([])
current_time = 0.0


+ 2
- 23
sphinx/conf.py View File

@ -3,28 +3,6 @@
from recommonmark.parser import CommonMarkParser
#
# nd2reader documentation build configuration file, created by
# sphinx-quickstart on Mon Mar 6 21:17:58 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, '/home/ruben/PycharmProjects/nd2reader/nd2reader')
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@ -36,7 +14,8 @@ from recommonmark.parser import CommonMarkParser
# ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode']
'sphinx.ext.viewcode',
'sphinx.ext.napoleon']
# Add any paths that contain templates here, relative to this directory.
templates_path = []


Loading…
Cancel
Save