55import numpy as np
66
77
8- def get_pv_metadata ( pvtiffile : str ) -> dict :
9- """Extract metadata for scans generated by PrairieView acquisition software.
8+ def get_prairieview_metadata ( ome_tif_filepath : str ) -> dict :
9+ """Extract metadata for scans generated by Prairie View acquisition software.
1010
11- The PrairieView software generates one .ome.tif imaging file per frame acquired. The
12- metadata for all frames is contained one .xml file. This function locates the .xml
13- file and generates a dictionary necessary to populate the DataJoint ScanInfo and
14- Field tables. PrairieView works with resonance scanners with a single field.
15- PrairieView does not support bidirectional x and y scanning. ROI information is not
16- contained in the .xml file. All images generated using PrairieView have square
17- dimensions(e.g. 512x512).
11+ The Prairie View software generates one ` .ome.tif` imaging file per frame
12+ acquired. The metadata for all frames is contained in one .xml file. This
13+ function locates the .xml file and generates a dictionary necessary to
14+ populate the DataJoint `ScanInfo` and `Field` tables. Prairie View works
15+ with resonance scanners with a single field. Prairie View does not support
16+ bidirectional x and y scanning. ROI information is not contained in the
17+ `.xml` file. All images generated using Prairie View have square dimensions(e.g. 512x512).
1818
1919 Args:
20- pvtiffile : An absolute path to the .ome.tif image file.
20+ ome_tif_filepath : An absolute path to the .ome.tif image file.
2121
2222 Raises:
2323 FileNotFoundError: No .xml file containing information about the acquired scan
24- was found at path in parent directory at `pvtiffile `.
24+ was found at path in parent directory at `ome_tif_filepath `.
2525
2626 Returns:
2727 metainfo: A dict mapping keys to corresponding metadata values fetched from the
2828 .xml file.
2929 """
3030
3131 # May return multiple xml files. Only need one that contains scan metadata.
32- xml_files = pathlib .Path (pvtiffile ).parent .glob ("*.xml" )
32+ xml_files_list = pathlib .Path (ome_tif_filepath ).parent .glob ("*.xml" )
3333
34- for xml_file in xml_files :
35- tree = ET .parse (xml_file )
36- root = tree .getroot ()
37- if root .find (".//Sequence" ):
34+ for file in xml_files_list :
35+ xml_tree = ET .parse (file )
36+ xml_file = xml_tree .getroot ()
37+ if xml_file .find (".//Sequence" ):
3838 break
3939 else :
4040 raise FileNotFoundError (
41- f"No PrarieView metadata XML file found at { pvtiffile .parent } "
41+ f"No PrarieView metadata .xml file found at { pathlib . Path ( ome_tif_filepath ) .parent } "
4242 )
4343
4444 bidirectional_scan = False # Does not support bidirectional
4545 roi = 0
4646 n_fields = 1 # Always contains 1 field
47- record_start_time = root .find (".//Sequence/[@cycle='1']" ).attrib .get ("time" )
47+ recording_start_time = xml_file .find (".//Sequence/[@cycle='1']" ).attrib .get ("time" )
4848
4949 # Get all channels and find unique values
5050 channel_list = [
5151 int (channel .attrib .get ("channel" ))
52- for channel in root .iterfind (".//Sequence/Frame/File/[@channel]" )
52+ for channel in xml_file .iterfind (".//Sequence/Frame/File/[@channel]" )
5353 ]
5454 n_channels = len (set (channel_list ))
55- n_frames = len (root .findall (".//Sequence/Frame" ))
55+ n_frames = len (xml_file .findall (".//Sequence/Frame" ))
5656 framerate = 1 / float (
57- root .findall ('.//PVStateValue/[@key="framePeriod"]' )[0 ].attrib .get ("value" )
57+ xml_file .findall ('.//PVStateValue/[@key="framePeriod"]' )[0 ].attrib .get ("value" )
5858 ) # rate = 1/framePeriod
5959
6060 usec_per_line = (
6161 float (
62- root .findall (".//PVStateValue/[@key='scanLinePeriod']" )[0 ].attrib .get (
62+ xml_file .findall (".//PVStateValue/[@key='scanLinePeriod']" )[0 ].attrib .get (
6363 "value"
6464 )
6565 )
6666 * 1e6
6767 ) # Convert from seconds to microseconds
6868
69- scan_datetime = datetime .strptime (root .attrib .get ("date" ), "%m/%d/%Y %I:%M:%S %p" )
69+ scan_datetime = datetime .strptime (
70+ xml_file .attrib .get ("date" ), "%m/%d/%Y %I:%M:%S %p"
71+ )
7072
71- total_duration = float (
72- root .findall (".//Sequence/Frame" )[- 1 ].attrib .get ("relativeTime" )
73+ total_scan_duration = float (
74+ xml_file .findall (".//Sequence/Frame" )[- 1 ].attrib .get ("relativeTime" )
7375 )
7476
75- px_height = int (
76- root .findall (".//PVStateValue/[@key='pixelsPerLine']" )[0 ].attrib .get ("value" )
77+ pixel_height = int (
78+ xml_file .findall (".//PVStateValue/[@key='pixelsPerLine']" )[0 ].attrib .get (
79+ "value"
80+ )
7781 )
7882 # All PrairieView-acquired images have square dimensions (512 x 512; 1024 x 1024)
79- px_width = px_height
83+ pixel_width = pixel_height
8084
8185 um_per_pixel = float (
82- root .find (
86+ xml_file .find (
8387 ".//PVStateValue/[@key='micronsPerPixel']/IndexedValue/[@index='XAxis']"
8488 ).attrib .get ("value" )
8589 )
8690
87- um_height = um_width = float (px_height ) * um_per_pixel
91+ um_height = um_width = float (pixel_height ) * um_per_pixel
8892
8993 # x and y coordinate values for the center of the field
9094 x_field = float (
91- root .find (
95+ xml_file .find (
9296 ".//PVStateValue/[@key='currentScanCenter']/IndexedValue/[@index='XAxis']"
9397 ).attrib .get ("value" )
9498 )
9599 y_field = float (
96- root .find (
100+ xml_file .find (
97101 ".//PVStateValue/[@key='currentScanCenter']/IndexedValue/[@index='YAxis']"
98102 ).attrib .get ("value" )
99103 )
100104 if (
101- root .find (
105+ xml_file .find (
102106 ".//Sequence/[@cycle='1']/Frame/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']"
103107 )
104108 is None
105109 ):
106-
107110 z_fields = np .float64 (
108- root .find (
111+ xml_file .find (
109112 ".//PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/SubindexedValue"
110113 ).attrib .get ("value" )
111114 )
@@ -114,46 +117,53 @@ def get_pv_metadata(pvtiffile: str) -> dict:
114117 bidirection_z = False
115118
116119 else :
120+ bidirection_z = (
121+ xml_file .find (".//Sequence" ).attrib .get ("bidirectionalZ" ) == "True"
122+ )
117123
118- bidirection_z = root .find (".//Sequence" ).attrib .get ("bidirectionalZ" ) == "True"
119-
120- # One "Frame" per depth. Gets number of frames in first sequence
124+ # One "Frame" per depth in the .xml file. Gets number of frames in first sequence
121125 planes = [
122126 int (plane .attrib .get ("index" ))
123- for plane in root .findall (".//Sequence/[@cycle='1']/Frame" )
127+ for plane in xml_file .findall (".//Sequence/[@cycle='1']/Frame" )
124128 ]
125129 n_depths = len (set (planes ))
126130
127- z_controllers = root .findall (
131+ z_controllers = xml_file .findall (
128132 ".//Sequence/[@cycle='1']/Frame/[@index='1']/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/SubindexedValue"
129133 )
130- if len (z_controllers ) > 1 :
131134
135+ # If more than one Z-axis controllers are found,
136+ # check which controller is changing z_field depth. Only 1 controller
137+ # must change depths.
138+ if len (z_controllers ) > 1 :
132139 z_repeats = []
133- for controller in root .findall (
134- ".//Sequence/[@cycle='1']/Frame/[@index='1']/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/" ):
140+ for controller in xml_file .findall (
141+ ".//Sequence/[@cycle='1']/Frame/[@index='1']/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/"
142+ ):
135143 z_repeats .append (
136144 [
137145 float (z .attrib .get ("value" ))
138- for z in root .findall (
146+ for z in xml_file .findall (
139147 ".//Sequence/[@cycle='1']/Frame/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/SubindexedValue/[@subindex='{0}']" .format (
140148 controller .attrib .get ("subindex" )
141149 )
142150 )
143151 ]
144152 )
145-
146-
147- controller_assert = [not all (z == z_controller [0 ] for z in z_controller ) for z_controller in z_repeats ]
148-
149- assert sum (controller_assert )== 1 , "Multiple controllers changing z depth is not supported"
153+ controller_assert = [
154+ not all (z == z_controller [0 ] for z in z_controller )
155+ for z_controller in z_repeats
156+ ]
157+ assert (
158+ sum (controller_assert ) == 1
159+ ), "Multiple controllers changing z depth is not supported"
150160
151161 z_fields = z_repeats [controller_assert .index (True )]
152-
162+
153163 else :
154164 z_fields = [
155165 z .attrib .get ("value" )
156- for z in root .findall (
166+ for z in xml_file .findall (
157167 ".//Sequence/[@cycle='1']/Frame/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/[@index='ZAxis']/SubindexedValue/[@subindex='0']"
158168 )
159169 ]
@@ -176,15 +186,15 @@ def get_pv_metadata(pvtiffile: str) -> dict:
176186 bidirectional_z = bidirection_z ,
177187 scan_datetime = scan_datetime ,
178188 usecs_per_line = usec_per_line ,
179- scan_duration = total_duration ,
180- height_in_pixels = px_height ,
181- width_in_pixels = px_width ,
189+ scan_duration = total_scan_duration ,
190+ height_in_pixels = pixel_height ,
191+ width_in_pixels = pixel_width ,
182192 height_in_um = um_height ,
183193 width_in_um = um_width ,
184194 fieldX = x_field ,
185195 fieldY = y_field ,
186196 fieldZ = z_fields ,
187- recording_time = record_start_time ,
197+ recording_time = recording_start_time ,
188198 )
189199
190200 return metainfo
0 commit comments