Browse Source

Issue #2: clean up axes handling, guess a better default axis

feature/load_slices
Ruben Verweij 8 years ago
parent
commit
d4e505779d
5 changed files with 62 additions and 71 deletions
  1. +7
    -0
      nd2reader/exceptions.py
  2. +19
    -4
      nd2reader/raw_metadata.py
  3. +35
    -8
      nd2reader/reader.py
  4. +0
    -27
      tests/test_legacy.py
  5. +1
    -32
      tests/test_reader.py

+ 7
- 0
nd2reader/exceptions.py View File

@ -15,3 +15,10 @@ class NoImageError(Exception):
""" """
pass pass
class EmptyFileError(Exception):
"""This .nd2 file seems to be empty.
Raised if no axes are found in the file.
"""

+ 19
- 4
nd2reader/raw_metadata.py View File

@ -39,13 +39,15 @@ class RawMetadata(object):
"width": self._parse_if_not_none(self.image_attributes, self._parse_width), "width": self._parse_if_not_none(self.image_attributes, self._parse_width),
"date": self._parse_if_not_none(self.image_text_info, self._parse_date), "date": self._parse_if_not_none(self.image_text_info, self._parse_date),
"fields_of_view": self._parse_fields_of_view(), "fields_of_view": self._parse_fields_of_view(),
"frames": np.arange(0, frames_per_channel, 1),
"frames": self._parse_frames(),
"z_levels": self._parse_z_levels(), "z_levels": self._parse_z_levels(),
"total_images_per_channel": frames_per_channel, "total_images_per_channel": frames_per_channel,
"channels": self._parse_channels(), "channels": self._parse_channels(),
"pixel_microns": self._parse_if_not_none(self.image_calibration, self._parse_calibration), "pixel_microns": self._parse_if_not_none(self.image_calibration, self._parse_calibration),
} }
self._set_default_if_not_empty('fields_of_view')
self._set_default_if_not_empty('frames')
self._metadata_parsed['num_frames'] = len(self._metadata_parsed['frames']) self._metadata_parsed['num_frames'] = len(self._metadata_parsed['frames'])
self._parse_roi_metadata() self._parse_roi_metadata()
@ -53,6 +55,11 @@ class RawMetadata(object):
return self._metadata_parsed return self._metadata_parsed
def _set_default_if_not_empty(self, entry):
if len(self._metadata_parsed[entry]) == 0 and self._metadata_parsed['total_images_per_channel'] > 0:
# if the file is not empty, we always have one of this entry
self._metadata_parsed[entry] = [0]
@staticmethod @staticmethod
def _parse_if_not_none(to_check, callback): def _parse_if_not_none(to_check, callback):
if to_check is not None: if to_check is not None:
@ -74,6 +81,14 @@ class RawMetadata(object):
def _parse_calibration(self): def _parse_calibration(self):
return self.image_calibration.get(six.b('SLxCalibration'), {}).get(six.b('dCalibration')) return self.image_calibration.get(six.b('SLxCalibration'), {}).get(six.b('dCalibration'))
def _parse_frames(self):
"""The number of cycles.
Returns:
list: list of all the frame numbers
"""
return self._parse_dimension(r""".*?T'?\((\d+)\).*?""")
def _parse_channels(self): def _parse_channels(self):
"""These are labels created by the NIS Elements user. Typically they may a short description of the filter cube """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.) used (e.g. 'bright field', 'GFP', etc.)
@ -154,12 +169,12 @@ class RawMetadata(object):
def _parse_dimension(self, pattern): def _parse_dimension(self, pattern):
dimension_text = self._parse_dimension_text() dimension_text = self._parse_dimension_text()
if dimension_text is None: if dimension_text is None:
return [0]
return []
if six.PY3: if six.PY3:
dimension_text = dimension_text.decode("utf8") dimension_text = dimension_text.decode("utf8")
match = re.match(pattern, dimension_text) match = re.match(pattern, dimension_text)
if not match: if not match:
return [0]
return []
count = int(match.group(1)) count = int(match.group(1))
return list(range(count)) return list(range(count))
@ -170,7 +185,7 @@ class RawMetadata(object):
""" """
if self.image_attributes is None: if self.image_attributes is None:
return None
return 0
return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiSequenceCount')] return self.image_attributes[six.b('SLxImageAttributes')][six.b('uiSequenceCount')]
def _parse_roi_metadata(self): def _parse_roi_metadata(self):


+ 35
- 8
nd2reader/reader.py View File

@ -1,4 +1,6 @@
from pims import FramesSequenceND, Frame from pims import FramesSequenceND, Frame
from nd2reader.exceptions import EmptyFileError
from nd2reader.parser import Parser from nd2reader.parser import Parser
import numpy as np import numpy as np
@ -99,17 +101,42 @@ class ND2Reader(FramesSequenceND):
"""Setup the xyctz axes, iterate over t axis by default """Setup the xyctz axes, iterate over t axis by default
""" """
self._init_axis('x', self._get_metadata_property("width", default=0))
self._init_axis('y', self._get_metadata_property("height", default=0))
self._init_axis('c', len(self._get_metadata_property("channels", default=[])))
self._init_axis('t', len(self._get_metadata_property("frames", default=[])))
self._init_axis_if_exists('x', self._get_metadata_property("width", default=0))
self._init_axis_if_exists('y', self._get_metadata_property("height", default=0))
self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])))
self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[])))
self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])))
z_levels = len(self._get_metadata_property("z_levels", default=[]))
if z_levels > 1:
self._init_axis('z', z_levels)
if len(self.sizes) == 0:
raise EmptyFileError("No axes were found for this .nd2 file.")
# provide the default # provide the default
self.iter_axes = 't'
self.iter_axes = self._guess_default_iter_axis()
def _init_axis_if_exists(self, axis, size):
if size > 0:
self._init_axis(axis, size)
def _guess_default_iter_axis(self):
"""
Guesses the default axis to iterate over based on axis sizes.
Returns:
the axis to iterate over
"""
priority = ['t', 'z', 'c']
found_axes = []
for axis in priority:
try:
current_size = self.sizes[axis]
except KeyError:
continue
if current_size > 1:
return axis
found_axes.append(axis)
return found_axes[0]
def get_timesteps(self): def get_timesteps(self):
"""Get the timesteps of the experiment """Get the timesteps of the experiment


+ 0
- 27
tests/test_legacy.py View File

@ -1,27 +0,0 @@
import unittest
from os import path
from nd2reader.artificial import ArtificialND2
from nd2reader.common import check_or_make_dir
from nd2reader.legacy import Nd2
class TestLegacy(unittest.TestCase):
def create_test_nd2(self):
with ArtificialND2(self.test_file) as artificial:
artificial.close()
def setUp(self):
dir_path = path.dirname(path.realpath(__file__))
check_or_make_dir(path.join(dir_path, 'test_data/'))
self.test_file = path.join(dir_path, 'test_data/test.nd2')
def test_can_open_test_file(self):
self.create_test_nd2()
with Nd2(self.test_file) as reader:
self.assertEqual(reader.width, 0)
self.assertEqual(reader.height, 0)
self.assertEqual(len(reader.z_levels), 1)
self.assertEqual(len(reader.channels), 0)
self.assertEqual(len(reader.frames), 0)

+ 1
- 32
tests/test_reader.py View File

@ -4,42 +4,11 @@ import numpy as np
from nd2reader.artificial import ArtificialND2 from nd2reader.artificial import ArtificialND2
from nd2reader.common import check_or_make_dir from nd2reader.common import check_or_make_dir
from nd2reader.exceptions import EmptyFileError
from nd2reader.parser import Parser from nd2reader.parser import Parser
from nd2reader.reader import ND2Reader from nd2reader.reader import ND2Reader
class TestReader(unittest.TestCase): class TestReader(unittest.TestCase):
def create_test_nd2(self):
with ArtificialND2(self.test_file) as artificial:
artificial.close()
def setUp(self):
dir_path = path.dirname(path.realpath(__file__))
check_or_make_dir(path.join(dir_path, 'test_data/'))
self.test_file = path.join(dir_path, 'test_data/test.nd2')
def test_can_open_test_file(self):
self.create_test_nd2()
with ND2Reader(self.test_file) as reader:
self.assertEqual(reader.filename, self.test_file)
self.assertEqual(reader.pixel_type, np.float64)
self.assertEqual(reader.sizes['x'], 0)
self.assertEqual(reader.sizes['y'], 0)
self.assertFalse('z' in reader.sizes)
self.assertEqual(reader.sizes['c'], 0)
self.assertEqual(reader.sizes['t'], 0)
def test_extension(self): def test_extension(self):
self.assertTrue('nd2' in ND2Reader.class_exts()) self.assertTrue('nd2' in ND2Reader.class_exts())
def test_get_metadata_property(self):
self.create_test_nd2()
with ND2Reader(self.test_file) as reader:
self.assertIsNone(reader._get_metadata_property('kljdf'))
self.assertEqual(reader._get_metadata_property('kljdf', 'default'), 'default')
def test_get_parser(self):
self.create_test_nd2()
with ND2Reader(self.test_file) as reader:
self.assertTrue(type(reader.parser) is Parser)

Loading…
Cancel
Save