Skip to content

Commit 2ac1cce

Browse files
authored
Merge pull request #44 from kushalbakshi/main
PrairieView Reader Added
2 parents ead8d50 + afac09d commit 2ac1cce

File tree

3 files changed

+151
-2
lines changed

3 files changed

+151
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
44

5+
## [0.3.0] - 2022-10-7
6+
+ Add - Function `prairieviewreader` to parse metadata from Bruker PrarieView acquisition system
7+
58
## 0.2.1 - 2022-07-13
69
+ Add - Adopt `black` formatting
710
+ Add - Code of Conduct
@@ -19,4 +22,6 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
1922
+ Change - Rename the package `element-data-loader` to `element-interface`.
2023

2124
## 0.1.0a0 - 2021-06-21
22-
+ Add - Readers for: `ScanImage`, `Suite2p`, `CaImAn`.
25+
+ Add - Readers for: `ScanImage`, `Suite2p`, `CaImAn`.
26+
27+
[0.3.0]: https://github.com/datajoint/element-interface/releases/tag/0.3.0
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import pathlib
2+
import xml.etree.ElementTree as ET
3+
from datetime import datetime
4+
import numpy as np
5+
6+
7+
def get_pv_metadata(pvtiffile):
8+
"""Extract metadata for calcium imaging scans generated by Bruker Systems PrairieView acquisition software.
9+
10+
The PrairieView software generates one .ome.tif imaging file per frame acquired. The metadata for all frames is contained one .xml file. This function locates the .xml file and generates a dictionary necessary to populate the DataJoint ScanInfo and Field tables.
11+
12+
PrairieView works with resonance scanners with a single field.
13+
14+
PrairieView does not support bidirectional x and y scanning.
15+
16+
ROI information is not contained in the .xml file.
17+
18+
All images generated using PrairieView have square dimensions (e.g. 512x512).
19+
20+
21+
Args:
22+
pvtiffile: An absolute path to the .ome.tif image file.
23+
24+
Raises:
25+
FileNotFoundError: No .xml file containing information about the acquired scan was found at path in parent directory at `pvtiffile`.
26+
27+
Returns:
28+
metainfo: A dict mapping keys to corresponding metadata values fetched from the .xml file.
29+
"""
30+
31+
# May return multiple xml files. Only need one that contains scan metadata.
32+
xml_files = pathlib.Path(pvtiffile).parent.glob("*.xml")
33+
34+
for xml_file in xml_files:
35+
tree = ET.parse(xml_file)
36+
root = tree.getroot()
37+
if root.find(".//Sequence"):
38+
break
39+
else:
40+
raise FileNotFoundError(
41+
f"No PrarieView metadata XML file found at {pvtiffile.parent}"
42+
)
43+
44+
bidirectional_scan = False # Does not support bidirectional
45+
46+
n_fields = 1 # Always contains 1 field
47+
48+
# Get all channels and find unique values
49+
channel_list = [
50+
int(channel.attrib.get("channel"))
51+
for channel in root.iterfind(".//Sequence/Frame/File/[@channel]")
52+
]
53+
n_channels = len(set(channel_list))
54+
55+
# One "Frame" per depth. Gets number of frames in first sequence
56+
planes = [
57+
int(plane.attrib.get("index"))
58+
for plane in root.findall(".//Sequence/[@cycle='1']/Frame")
59+
]
60+
n_depths = len(set(planes))
61+
62+
n_frames = len(root.findall(".//Sequence/Frame"))
63+
64+
roi = 1
65+
# x and y coordinate values for the center of the field
66+
x_field = float(
67+
root.find(
68+
".//PVStateValue/[@key='currentScanCenter']/IndexedValue/[@index='XAxis']"
69+
).attrib.get("value")
70+
)
71+
y_field = float(
72+
root.find(
73+
".//PVStateValue/[@key='currentScanCenter']/IndexedValue/[@index='YAxis']"
74+
).attrib.get("value")
75+
)
76+
77+
framerate = 1 / float(
78+
root.findall('.//PVStateValue/[@key="framePeriod"]')[0].attrib.get("value")) # rate = 1/framePeriod
79+
80+
usec_per_line = float(
81+
root.findall(".//PVStateValue/[@key='scanLinePeriod']")[0].attrib.get("value")) * 1e6 # Convert from seconds to microseconds
82+
83+
scan_datetime = datetime.strptime(
84+
root.attrib.get("date"), "%m/%d/%Y %I:%M:%S %p")
85+
86+
total_duration = float(
87+
root.findall(".//Sequence/Frame")[-1].attrib.get("relativeTime")
88+
)
89+
90+
bidirection_z = bool(
91+
root.find(".//Sequence").attrib.get("bidirectionalZ"))
92+
93+
px_height = int(
94+
root.findall(
95+
".//PVStateValue/[@key='pixelsPerLine']")[0].attrib.get("value")
96+
)
97+
# All PrairieView-acquired images have square dimensions (512 x 512; 1024 x 1024)
98+
px_width = px_height
99+
100+
um_per_pixel = float(
101+
root.find(
102+
".//PVStateValue/[@key='micronsPerPixel']/IndexedValue/[@index='XAxis']"
103+
).attrib.get("value")
104+
)
105+
106+
um_height = um_width = float(px_height) * um_per_pixel
107+
108+
z_min = float(root.findall(
109+
".//Sequence/[@cycle='1']/Frame/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/SubindexedValue/[@subindex='0']"
110+
)[0].attrib.get("value"))
111+
z_max = float(root.findall(
112+
".//Sequence/[@cycle='1']/Frame/PVStateShard/PVStateValue/[@key='positionCurrent']/SubindexedValues/SubindexedValue/[@subindex='0']"
113+
)[-1].attrib.get("value"))
114+
z_step = float(root.find(
115+
".//PVStateShard/PVStateValue/[@key='micronsPerPixel']/IndexedValue/[@index='ZAxis']"
116+
).attrib.get("value"))
117+
z_fields = np.arange(z_min, z_max + 1, z_step)
118+
assert z_fields.size == n_depths
119+
120+
metainfo = dict(
121+
num_fields=n_fields,
122+
num_channels=n_channels,
123+
num_planes=n_depths,
124+
num_frames=n_frames,
125+
num_rois=roi,
126+
x_pos=None,
127+
y_pos=None,
128+
z_pos=None,
129+
frame_rate=framerate,
130+
bidirectional=bidirectional_scan,
131+
bidirectional_z=bidirection_z,
132+
scan_datetime=scan_datetime,
133+
usecs_per_line=usec_per_line,
134+
scan_duration=total_duration,
135+
height_in_pixels=px_height,
136+
width_in_pixels=px_width,
137+
height_in_um=um_height,
138+
width_in_um=um_width,
139+
fieldX=x_field,
140+
fieldY=y_field,
141+
fieldZ=z_fields,
142+
)
143+
144+
return metainfo

element_interface/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""Package metadata"""
2-
__version__ = "0.2.1"
2+
__version__ = "0.3.0"

0 commit comments

Comments
 (0)