Skip to content

Commit 548e4fd

Browse files
authored
Merge pull request #86 from kushalbakshi/main
Refinements to Prairie View metadata loader
2 parents 9ac41e2 + b34a517 commit 548e4fd

File tree

3 files changed

+73
-56
lines changed

3 files changed

+73
-56
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
44
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
55

6+
## [0.6.0] - 2023-07-26
7+
8+
+ Update - `prairieviewreader.py` -> `prairie_view_loader.py`
9+
+ Update - `get_pv_metadata()` -> `get_prairieview_metadata()`
10+
+ Update - Internal variable names within `prairie_view_loader.py`
11+
612
## [0.5.4] - 2023-05-25
713

814
+ Fix - DANDI URL for uploads where `staging=False`.
@@ -71,6 +77,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
7177

7278
+ Add - Readers for: `ScanImage`, `Suite2p`, `CaImAn`.
7379

80+
[0.6.0]: https://github.com/datajoint/element-interface/releases/tag/0.6.0
7481
[0.5.4]: https://github.com/datajoint/element-interface/releases/tag/0.5.4
7582
[0.5.3]: https://github.com/datajoint/element-interface/releases/tag/0.5.3
7683
[0.5.2]: https://github.com/datajoint/element-interface/releases/tag/0.5.2

element_interface/prairieviewreader.py renamed to element_interface/prairie_view_loader.py

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,107 +5,110 @@
55
import 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

element_interface/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Package metadata"""
22

3-
__version__ = "0.5.4"
3+
__version__ = "0.6.0"

0 commit comments

Comments
 (0)