Browse Source

Refactor

feature/load_slices
Ruben Verweij 6 years ago
parent
commit
c8aef85cd5
3 changed files with 122 additions and 108 deletions
  1. +102
    -0
      nd2reader/common_raw_metadata.py
  2. +12
    -101
      nd2reader/raw_metadata.py
  3. +8
    -7
      tests/test_raw_metadata.py

+ 102
- 0
nd2reader/common_raw_metadata.py View File

@ -0,0 +1,102 @@
import six
import warnings
from nd2reader.common import get_from_dict_if_exists
def parse_if_not_none(to_check, callback):
if to_check is not None:
return callback()
return None
def parse_dimension_text_line(line):
if six.b("Dimensions:") in line:
entries = line.split(six.b("\r\n"))
for entry in entries:
if entry.startswith(six.b("Dimensions:")):
return entry
return None
def parse_roi_shape(shape):
if shape == 3:
return 'rectangle'
elif shape == 9:
return 'circle'
return None
def parse_roi_type(type_no):
if type_no == 4:
return 'stimulation'
elif type_no == 3:
return 'reference'
elif type_no == 2:
return 'background'
return None
def get_loops_from_data(loop_data):
loops = [loop_data]
if six.b('uiPeriodCount') in loop_data and loop_data[six.b('uiPeriodCount')] > 0:
# special ND experiment
if six.b('pPeriod') not in loop_data:
return []
# take the first dictionary element, it contains all loop data
loops = loop_data[six.b('pPeriod')][list(loop_data[six.b('pPeriod')].keys())[0]]
return loops
def guess_sampling_from_loops(duration, loop):
""" In some cases, both keys are not saved. Then try to calculate it.
Args:
duration: the total duration of the loop
loop: the raw loop data
Returns:
float: the guessed sampling interval in milliseconds
"""
number_of_loops = get_from_dict_if_exists('uiCount', loop)
number_of_loops = number_of_loops if number_of_loops is not None and number_of_loops > 0 else 1
interval = duration / number_of_loops
return interval
def determine_sampling_interval(duration, loop):
"""Determines the loop sampling interval in milliseconds
Args:
duration: loop duration in milliseconds
loop: loop dictionary
Returns:
float: the sampling interval in milliseconds
"""
interval = get_from_dict_if_exists('dPeriod', loop)
avg_interval = get_from_dict_if_exists('dAvgPeriodDiff', loop)
if interval is None or interval <= 0:
interval = avg_interval
else:
avg_interval_set = avg_interval is not None and avg_interval > 0
if round(avg_interval) != round(interval) and avg_interval_set:
warnings.warn("Reported average frame interval (%.1f ms) doesn't" +
"match the set interval (%.1f ms)." +
"Using the average now." % (avg_interval,
interval),
RuntimeWarning)
interval = avg_interval
if interval is None or interval <= 0:
# In some cases, both keys are not saved. Then try to calculate it.
interval = guess_sampling_from_loops(duration, loop)
return interval

+ 12
- 101
nd2reader/raw_metadata.py View File

@ -1,9 +1,10 @@
import re import re
from nd2reader.common import read_chunk, read_array, read_metadata, parse_date, get_from_dict_if_exists
import xmltodict import xmltodict
import six import six
import numpy as np import numpy as np
import warnings
from nd2reader.common import read_chunk, read_array, read_metadata, parse_date, get_from_dict_if_exists
from nd2reader.common_raw_metadata import parse_dimension_text_line, parse_if_not_none, parse_roi_shape, parse_roi_type, get_loops_from_data, determine_sampling_interval
class RawMetadata(object): class RawMetadata(object):
@ -36,15 +37,15 @@ class RawMetadata(object):
frames_per_channel = self._parse_total_images_per_channel() frames_per_channel = self._parse_total_images_per_channel()
self._metadata_parsed = { self._metadata_parsed = {
"height": self._parse_if_not_none(self.image_attributes, self._parse_height),
"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),
"height": parse_if_not_none(self.image_attributes, self._parse_height),
"width": parse_if_not_none(self.image_attributes, self._parse_width),
"date": 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": self._parse_frames(), "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": 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('fields_of_view')
@ -64,12 +65,6 @@ class RawMetadata(object):
# if the file is not empty, we always have one of this entry # if the file is not empty, we always have one of this entry
self._metadata_parsed[entry] = [0] self._metadata_parsed[entry] = [0]
@staticmethod
def _parse_if_not_none(to_check, callback):
if to_check is not None:
return callback()
return None
def _parse_width_or_height(self, key): def _parse_width_or_height(self, key):
try: try:
length = self.image_attributes[six.b('SLxImageAttributes')][six.b(key)] length = self.image_attributes[six.b('SLxImageAttributes')][six.b(key)]
@ -181,21 +176,12 @@ class RawMetadata(object):
return dimension_text return dimension_text
for line in textinfo: for line in textinfo:
entry = self._parse_dimension_text_line(line)
entry = parse_dimension_text_line(line)
if entry is not None: if entry is not None:
return entry return entry
return dimension_text return dimension_text
@staticmethod
def _parse_dimension_text_line(line):
if six.b("Dimensions:") in line:
entries = line.split(six.b("\r\n"))
for entry in entries:
if entry.startswith(six.b("Dimensions:")):
return entry
return None
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:
@ -262,8 +248,8 @@ class RawMetadata(object):
"timepoints": [], "timepoints": [],
"positions": [], "positions": [],
"sizes": [], "sizes": [],
"shape": self._parse_roi_shape(raw_roi_dict[six.b('m_sInfo')][six.b('m_uiShapeType')]),
"type": self._parse_roi_type(raw_roi_dict[six.b('m_sInfo')][six.b('m_uiInterpType')])
"shape": parse_roi_shape(raw_roi_dict[six.b('m_sInfo')][six.b('m_uiShapeType')]),
"type": parse_roi_type(raw_roi_dict[six.b('m_sInfo')][six.b('m_uiInterpType')])
} }
for i in range(number_of_timepoints): for i in range(number_of_timepoints):
roi_dict = self._parse_vect_anim(roi_dict, raw_roi_dict[six.b('m_vectAnimParams_%d' % i)]) roi_dict = self._parse_vect_anim(roi_dict, raw_roi_dict[six.b('m_vectAnimParams_%d' % i)])
@ -275,26 +261,6 @@ class RawMetadata(object):
return roi_dict return roi_dict
@staticmethod
def _parse_roi_shape(shape):
if shape == 3:
return 'rectangle'
elif shape == 9:
return 'circle'
return None
@staticmethod
def _parse_roi_type(type_no):
if type_no == 4:
return 'stimulation'
elif type_no == 3:
return 'reference'
elif type_no == 2:
return 'background'
return None
def _parse_vect_anim(self, roi_dict, animation_dict): 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.
@ -346,18 +312,6 @@ class RawMetadata(object):
if six.b('uLoopPars') in raw_data: if six.b('uLoopPars') in raw_data:
self._metadata_parsed['experiment']['loops'] = self._parse_loop_data(raw_data[six.b('uLoopPars')]) self._metadata_parsed['experiment']['loops'] = self._parse_loop_data(raw_data[six.b('uLoopPars')])
@staticmethod
def _get_loops_from_data(loop_data):
loops = [loop_data]
if six.b('uiPeriodCount') in loop_data and loop_data[six.b('uiPeriodCount')] > 0:
# special ND experiment
if six.b('pPeriod') not in loop_data:
return []
# take the first dictionary element, it contains all loop data
loops = loop_data[six.b('pPeriod')][list(loop_data[six.b('pPeriod')].keys())[0]]
return loops
def _parse_loop_data(self, loop_data): def _parse_loop_data(self, loop_data):
"""Parse the experimental loop data """Parse the experimental loop data
@ -368,7 +322,7 @@ class RawMetadata(object):
list: list of the parsed loops list: list of the parsed loops
""" """
loops = self._get_loops_from_data(loop_data)
loops = get_loops_from_data(loop_data)
# take into account the absolute time in ms # take into account the absolute time in ms
time_offset = 0 time_offset = 0
@ -378,7 +332,7 @@ class RawMetadata(object):
for loop in loops: for loop in loops:
# duration of this loop # duration of this loop
duration = get_from_dict_if_exists('dDuration', loop) or 0 duration = get_from_dict_if_exists('dDuration', loop) or 0
interval = self._determine_sampling_interval(duration, loop)
interval = determine_sampling_interval(duration, loop)
# if duration is not saved, infer it # if duration is not saved, infer it
duration = self.get_duration_from_interval_and_loops(duration, interval, loop) duration = self.get_duration_from_interval_and_loops(duration, interval, loop)
@ -419,49 +373,6 @@ class RawMetadata(object):
return duration return duration
@staticmethod
def _determine_sampling_interval(duration, loop):
"""Determines the loop sampling interval in milliseconds
Args:
duration: loop duration in milliseconds
loop: loop dictionary
Returns:
float: the sampling interval in milliseconds
"""
interval = get_from_dict_if_exists('dPeriod', loop)
if interval is None or interval <= 0:
# Use a fallback if it is still not found
interval = get_from_dict_if_exists('dAvgPeriodDiff', loop)
else:
avg_interval = get_from_dict_if_exists('dAvgPeriodDiff', loop)
if round(avg_interval) != round(interval):
warnings.warn("Reported average frame interval (%.1f ms) doesn't match the set interval (%.1f ms). Using the average now." % (avg_interval, interval), RuntimeWarning)
interval = avg_interval
if interval is None or interval <= 0:
# In some cases, both keys are not saved. Then try to calculate it.
interval = RawMetadata._guess_sampling_from_loops(duration, loop)
return interval
@staticmethod
def _guess_sampling_from_loops(duration, loop):
""" In some cases, both keys are not saved. Then try to calculate it.
Args:
duration: the total duration of the loop
loop: the raw loop data
Returns:
float: the guessed sampling interval in milliseconds
"""
number_of_loops = get_from_dict_if_exists('uiCount', loop)
number_of_loops = number_of_loops if number_of_loops is not None and number_of_loops > 0 else 1
interval = duration / number_of_loops
return interval
@property @property
def image_text_info(self): def image_text_info(self):


+ 8
- 7
tests/test_raw_metadata.py View File

@ -4,6 +4,7 @@ import six
from nd2reader.artificial import ArtificialND2 from nd2reader.artificial import ArtificialND2
from nd2reader.label_map import LabelMap from nd2reader.label_map import LabelMap
from nd2reader.raw_metadata import RawMetadata from nd2reader.raw_metadata import RawMetadata
from nd2reader.common_raw_metadata import parse_roi_shape, parse_roi_type
class TestRawMetadata(unittest.TestCase): class TestRawMetadata(unittest.TestCase):
@ -14,15 +15,15 @@ class TestRawMetadata(unittest.TestCase):
self.metadata = RawMetadata(self.nd2.file_handle, self.label_map) self.metadata = RawMetadata(self.nd2.file_handle, self.label_map)
def test_parse_roi_shape(self): def test_parse_roi_shape(self):
self.assertEqual(self.metadata._parse_roi_shape(3), 'rectangle')
self.assertEqual(self.metadata._parse_roi_shape(9), 'circle')
self.assertIsNone(self.metadata._parse_roi_shape(-1))
self.assertEqual(parse_roi_shape(3), 'rectangle')
self.assertEqual(parse_roi_shape(9), 'circle')
self.assertIsNone(parse_roi_shape(-1))
def test_parse_roi_type(self): def test_parse_roi_type(self):
self.assertEqual(self.metadata._parse_roi_type(3), 'reference')
self.assertEqual(self.metadata._parse_roi_type(2), 'background')
self.assertEqual(self.metadata._parse_roi_type(4), 'stimulation')
self.assertIsNone(self.metadata._parse_roi_type(-1))
self.assertEqual(parse_roi_type(3), 'reference')
self.assertEqual(parse_roi_type(2), 'background')
self.assertEqual(parse_roi_type(4), 'stimulation')
self.assertIsNone(parse_roi_type(-1))
def test_dict(self): def test_dict(self):
self.assertTrue(type(self.metadata.__dict__) is dict) self.assertTrue(type(self.metadata.__dict__) is dict)


Loading…
Cancel
Save