@ -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 ) :