pyvcell 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyvcell/__init__.py +0 -0
- pyvcell/foo.py +17 -0
- pyvcell/simdata/__init__.py +0 -0
- pyvcell/simdata/main.py +34 -0
- pyvcell/simdata/mesh.py +224 -0
- pyvcell/simdata/postprocessing.py +191 -0
- pyvcell/simdata/simdata_models.py +292 -0
- pyvcell/simdata/vtk/__init__.py +0 -0
- pyvcell/simdata/vtk/fv_mesh_mapping.py +222 -0
- pyvcell/simdata/vtk/vismesh.py +149 -0
- pyvcell/simdata/vtk/vtkmesh_chombo.py +98 -0
- pyvcell/simdata/vtk/vtkmesh_fv.py +61 -0
- pyvcell/simdata/vtk/vtkmesh_mb.py +72 -0
- pyvcell/simdata/vtk/vtkmesh_utils.py +322 -0
- pyvcell/simdata/zarr_writer.py +127 -0
- pyvcell/solvers/__init__.py +0 -0
- pyvcell/solvers/fvsolver.py +20 -0
- pyvcell-0.0.1.dist-info/LICENSE +21 -0
- pyvcell-0.0.1.dist-info/METADATA +75 -0
- pyvcell-0.0.1.dist-info/RECORD +21 -0
- pyvcell-0.0.1.dist-info/WHEEL +4 -0
pyvcell/__init__.py
ADDED
|
File without changes
|
pyvcell/foo.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
def foo(bar: str) -> str:
|
|
2
|
+
"""Summary line.
|
|
3
|
+
|
|
4
|
+
Extended description of function.
|
|
5
|
+
|
|
6
|
+
Args:
|
|
7
|
+
bar: Description of input argument.
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
Description of return value
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
return bar
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__": # pragma: no cover
|
|
17
|
+
pass
|
|
File without changes
|
pyvcell/simdata/main.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from pyvcell.simdata.mesh import CartesianMesh
|
|
6
|
+
from pyvcell.simdata.simdata_models import DataFunctions, PdeDataSet
|
|
7
|
+
from pyvcell.simdata.zarr_writer import write_zarr
|
|
8
|
+
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command(name="vc_to_zarr", help="Convert a VCell FiniteVolume simulation dataset to Zarr")
|
|
13
|
+
def n5_to_zarr(
|
|
14
|
+
sim_data_dir: Path = typer.Argument(..., help="path to vcell dataset directory"),
|
|
15
|
+
sim_id: int = typer.Argument(..., help="simulation id (e.g. 946368938)"),
|
|
16
|
+
job_id: int = typer.Argument(..., help="job id (e.g. 0"),
|
|
17
|
+
zarr_path: Path = typer.Argument(..., help="path to zarr dataset to write to"),
|
|
18
|
+
) -> None:
|
|
19
|
+
pde_dataset = PdeDataSet(base_dir=sim_data_dir, log_filename=f"SimID_{sim_id}_{job_id}_.log")
|
|
20
|
+
pde_dataset.read()
|
|
21
|
+
data_functions = DataFunctions(function_file=sim_data_dir / f"SimID_{sim_id}_{job_id}_.functions")
|
|
22
|
+
data_functions.read()
|
|
23
|
+
mesh = CartesianMesh(mesh_file=sim_data_dir / f"SimID_{sim_id}_{job_id}_.mesh")
|
|
24
|
+
mesh.read()
|
|
25
|
+
|
|
26
|
+
write_zarr(pde_dataset=pde_dataset, data_functions=data_functions, mesh=mesh, zarr_dir=zarr_path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> None:
|
|
30
|
+
app()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
pyvcell/simdata/mesh.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import zlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from pyvcell.simdata.vtk.vismesh import Box3D
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CartesianMesh:
|
|
10
|
+
"""
|
|
11
|
+
reads the .mesh file and extracts the mesh information
|
|
12
|
+
|
|
13
|
+
Example .mesh file:
|
|
14
|
+
Version 1.2
|
|
15
|
+
CartesianMesh {
|
|
16
|
+
// X Y Z
|
|
17
|
+
Size 71 71 25
|
|
18
|
+
Extent 74.239999999999995 74.239999999999995 26
|
|
19
|
+
Origin 0 0 0
|
|
20
|
+
|
|
21
|
+
VolumeRegionsMapSubvolume {
|
|
22
|
+
6
|
|
23
|
+
//VolRegID SubvolID Volume
|
|
24
|
+
0 0 124767.54117864356 //ec
|
|
25
|
+
1 1 14855.904388351477 //cytosol
|
|
26
|
+
2 1 1.2185460680272107 //cytosol
|
|
27
|
+
3 1 1.2185460680272107 //cytosol
|
|
28
|
+
4 1 1.2185460680272107 //cytosol
|
|
29
|
+
5 2 3673.9163951019395 //Nucleus
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
MembraneRegionsMapVolumeRegion {
|
|
33
|
+
5
|
|
34
|
+
//MemRegID VolReg1 VolReg2 Surface
|
|
35
|
+
0 1 0 4512.8782874369472
|
|
36
|
+
1 2 0 1.7113582585034091
|
|
37
|
+
2 3 0 1.7113582585033937
|
|
38
|
+
3 4 0 1.711358258503394
|
|
39
|
+
4 5 1 1306.5985272332098
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
VolumeElementsMapVolumeRegion {
|
|
43
|
+
126025 Compressed
|
|
44
|
+
789CEDDD8D72DBC81100612389DFFF9573572A5912B9BF2066A66176B32A57B12CE22B8022E5DD11
|
|
45
|
+
F5EB9799999999999999999999999999999999999999999999999999999999999999999999999999
|
|
46
|
+
...
|
|
47
|
+
3333338B8F3625C09A5BE069281EE2BC0BC543D530FA907034666666666666666666666666666666
|
|
48
|
+
6666666666666666666666667F67FF07ABF56A9C
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
MembraneElements {
|
|
52
|
+
7817
|
|
53
|
+
//Indx Vol1 Vol2 Conn0 Conn1 Conn2 Conn3 MemRegID
|
|
54
|
+
0 6710 11751 5 507 493 1 0
|
|
55
|
+
1 6711 11752 6 0 494 510 0
|
|
56
|
+
2 6771 11812 10 524 503 3 0
|
|
57
|
+
3 6772 11813 11 2 505 527 0
|
|
58
|
+
4 6780 11821 16 533 508 5 0
|
|
59
|
+
....
|
|
60
|
+
7808 109155 104114 7807 7805 7792 7806 4
|
|
61
|
+
7809 104179 104180 7810 7551 7798 7811 4
|
|
62
|
+
7810 104251 104180 7812 7551 7809 -1 4
|
|
63
|
+
7811 109221 104180 -1 7809 7799 7813 4
|
|
64
|
+
7812 104252 104181 7815 7553 7810 -1 4
|
|
65
|
+
7813 109222 104181 -1 7811 7800 7816 4
|
|
66
|
+
7814 104183 104182 7815 7556 7802 7816 4
|
|
67
|
+
7815 104253 104182 7814 7554 7812 -1 4
|
|
68
|
+
7816 109223 104182 -1 7813 7801 7814 4
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
mesh_file: Path
|
|
74
|
+
size: list[int] # [x, y, z]
|
|
75
|
+
extent: list[float] # [x, y, z]
|
|
76
|
+
origin: list[float] # [x, y, z]
|
|
77
|
+
volume_regions: list[tuple[int, int, float, str]] # list of tuples (vol_reg_id, subvol_id, volume, domain_name)
|
|
78
|
+
membrane_regions: list[tuple[int, int, int, float]] # list of tuples (mem_reg_id, vol_reg1, vol_reg2, surface)
|
|
79
|
+
|
|
80
|
+
# membrane_element[m,:] = [idx, vol1, vol2, conn0, conn1, conn2, conn3, mem_reg_id]
|
|
81
|
+
membrane_elements: np.ndarray # shape (num_membrane_elements, 8)
|
|
82
|
+
|
|
83
|
+
# volume_region_map[m] = vol_reg_id
|
|
84
|
+
volume_region_map: np.ndarray # shape (size[0] * size[1] * size[2],)
|
|
85
|
+
|
|
86
|
+
def __init__(self, mesh_file: Path) -> None:
|
|
87
|
+
self.mesh_file = mesh_file
|
|
88
|
+
self.size = []
|
|
89
|
+
self.extent = []
|
|
90
|
+
self.origin = []
|
|
91
|
+
self.volume_regions = []
|
|
92
|
+
self.membrane_regions = []
|
|
93
|
+
# self.membrane_elements
|
|
94
|
+
self.volume_region_map = np.array([], dtype=np.uint8)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def dimension(self) -> int:
|
|
98
|
+
if self.size[1] == 1 and self.size[2] == 1:
|
|
99
|
+
return 1
|
|
100
|
+
elif self.size[2] == 1:
|
|
101
|
+
return 2
|
|
102
|
+
else:
|
|
103
|
+
return 3
|
|
104
|
+
|
|
105
|
+
def read(self) -> None:
|
|
106
|
+
# read file as lines and parse
|
|
107
|
+
with self.mesh_file.open("r") as f:
|
|
108
|
+
# get line enumerator from f
|
|
109
|
+
|
|
110
|
+
iter_lines = iter(f.readlines())
|
|
111
|
+
|
|
112
|
+
if next(iter_lines) != "Version 1.2\n":
|
|
113
|
+
raise RuntimeError("Expected 'Version 1.2' at the beginning of the file")
|
|
114
|
+
if next(iter_lines) != "CartesianMesh {\n":
|
|
115
|
+
raise RuntimeError("Expected 'CartesianMesh {' after version")
|
|
116
|
+
if next(iter_lines) != "\t// X Y Z\n":
|
|
117
|
+
raise RuntimeError("Expected coordinate comment line")
|
|
118
|
+
|
|
119
|
+
size_line = next(iter_lines).split()
|
|
120
|
+
if size_line[0] == "Size":
|
|
121
|
+
self.size = [int(size_line[1]), int(size_line[2]), int(size_line[3])]
|
|
122
|
+
|
|
123
|
+
extent_line = next(iter_lines).split()
|
|
124
|
+
if extent_line[0] == "Extent":
|
|
125
|
+
self.extent = [float(extent_line[1]), float(extent_line[2]), float(extent_line[3])]
|
|
126
|
+
|
|
127
|
+
origin_line = next(iter_lines).split()
|
|
128
|
+
if origin_line[0] == "Origin":
|
|
129
|
+
self.origin = [float(origin_line[1]), float(origin_line[2]), float(origin_line[3])]
|
|
130
|
+
|
|
131
|
+
while next(iter_lines) != "\tVolumeRegionsMapSubvolume {\n":
|
|
132
|
+
pass
|
|
133
|
+
num_volume_regions = int(next(iter_lines))
|
|
134
|
+
_header_line = next(iter_lines)
|
|
135
|
+
self.volume_regions = []
|
|
136
|
+
for _i in range(num_volume_regions):
|
|
137
|
+
parts = next(iter_lines).split()
|
|
138
|
+
self.volume_regions.append((int(parts[0]), int(parts[1]), float(parts[2]), parts[3].strip("/")))
|
|
139
|
+
|
|
140
|
+
while next(iter_lines) != "\tMembraneRegionsMapVolumeRegion {\n":
|
|
141
|
+
pass
|
|
142
|
+
num_membrane_regions = int(next(iter_lines))
|
|
143
|
+
_header_line = next(iter_lines)
|
|
144
|
+
self.membrane_regions = []
|
|
145
|
+
for _i in range(num_membrane_regions):
|
|
146
|
+
parts = next(iter_lines).split()
|
|
147
|
+
self.membrane_regions.append((int(parts[0]), int(parts[1]), int(parts[2]), float(parts[3])))
|
|
148
|
+
|
|
149
|
+
while next(iter_lines) != "\tVolumeElementsMapVolumeRegion {\n":
|
|
150
|
+
pass
|
|
151
|
+
compressed_line = next(iter_lines).split()
|
|
152
|
+
num_volume_elements = int(compressed_line[0])
|
|
153
|
+
if compressed_line[1] != "Compressed":
|
|
154
|
+
raise ValueError("Expected 'Compressed' in VolumeElementsMapVolumeRegion")
|
|
155
|
+
# read HEX lines until "}" line, and concatenate into one string, then convert to bytes and decompress
|
|
156
|
+
hex_lines = []
|
|
157
|
+
while True:
|
|
158
|
+
line = next(iter_lines)
|
|
159
|
+
if line.strip() == "}":
|
|
160
|
+
break
|
|
161
|
+
hex_lines.append(line.strip())
|
|
162
|
+
hex_string: str = "".join(hex_lines).strip()
|
|
163
|
+
compressed_bytes = bytes.fromhex(hex_string)
|
|
164
|
+
uncompressed_bytes: bytes = zlib.decompress(compressed_bytes)
|
|
165
|
+
self.volume_region_map = np.frombuffer(uncompressed_bytes, dtype="<u2") # unsigned 2-byte integers
|
|
166
|
+
if self.volume_region_map.shape[0] != self.size[0] * self.size[1] * self.size[2]:
|
|
167
|
+
raise ValueError("Expected number of volume elements to match the size of volume region map")
|
|
168
|
+
if num_volume_elements != self.volume_region_map.shape[0]:
|
|
169
|
+
raise ValueError("Expected number of volume elements to match the size of volume region map")
|
|
170
|
+
if set(np.unique(self.volume_region_map)) != {v[0] for v in self.volume_regions}:
|
|
171
|
+
raise ValueError("Expected volume region map to have the same unique values as volume regions")
|
|
172
|
+
|
|
173
|
+
while next(iter_lines).strip() != "MembraneElements {":
|
|
174
|
+
pass
|
|
175
|
+
num_membrane_elements = int(next(iter_lines))
|
|
176
|
+
self.membrane_elements = np.zeros((num_membrane_elements, 8), dtype=np.int32)
|
|
177
|
+
_header_line = next(iter_lines)
|
|
178
|
+
mem_index = 0
|
|
179
|
+
while True:
|
|
180
|
+
line = next(iter_lines)
|
|
181
|
+
if line.strip() == "}":
|
|
182
|
+
break
|
|
183
|
+
parts = line.split()
|
|
184
|
+
idx = int(parts[0])
|
|
185
|
+
vol1 = int(parts[1])
|
|
186
|
+
vol2 = int(parts[2])
|
|
187
|
+
conn0 = int(parts[3])
|
|
188
|
+
conn1 = int(parts[4])
|
|
189
|
+
conn2 = int(parts[5])
|
|
190
|
+
conn3 = int(parts[6])
|
|
191
|
+
mem_reg_id = int(parts[7])
|
|
192
|
+
self.membrane_elements[mem_index, :] = [idx, vol1, vol2, conn0, conn1, conn2, conn3, mem_reg_id]
|
|
193
|
+
mem_index += 1
|
|
194
|
+
if self.membrane_elements.shape != (num_membrane_elements, 8):
|
|
195
|
+
raise RuntimeError("Expected membrane elements to have the correct shape")
|
|
196
|
+
if set(np.unique(self.membrane_elements[:, 7])) != {v[0] for v in self.membrane_regions}:
|
|
197
|
+
raise RuntimeError("Expected volume region ids in membrane elements to match volume regions")
|
|
198
|
+
|
|
199
|
+
def get_volume_element_box(self, i: int, j: int, k: int) -> Box3D:
|
|
200
|
+
x_lo = self.origin[0] + i * self.extent[0] / self.size[0]
|
|
201
|
+
y_lo = self.origin[1] + j * self.extent[1] / self.size[1]
|
|
202
|
+
z_lo = self.origin[2] + k * self.extent[2] / self.size[2]
|
|
203
|
+
x_hi = self.origin[0] + (i + 1) * self.extent[0] / self.size[0]
|
|
204
|
+
y_hi = self.origin[1] + (j + 1) * self.extent[1] / self.size[1]
|
|
205
|
+
z_hi = self.origin[2] + (k + 1) * self.extent[2] / self.size[2]
|
|
206
|
+
return Box3D(x_lo, y_lo, z_lo, x_hi, y_hi, z_hi)
|
|
207
|
+
|
|
208
|
+
def get_membrane_region_index(self, mem_element_index: int) -> int:
|
|
209
|
+
return int(self.membrane_elements[mem_element_index, 7])
|
|
210
|
+
|
|
211
|
+
def get_membrane_region_ids(self, volume_domain_name: str) -> set[int]:
|
|
212
|
+
return {
|
|
213
|
+
mem_reg_id
|
|
214
|
+
for mem_reg_id, vol_reg1, vol_reg2, surface in self.membrane_regions
|
|
215
|
+
if self.volume_regions[vol_reg1][3] == volume_domain_name
|
|
216
|
+
or self.volume_regions[vol_reg2][3] == volume_domain_name
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def get_volume_region_ids(self, volume_domain_name: str) -> set[int]:
|
|
220
|
+
return {
|
|
221
|
+
vol_reg_id
|
|
222
|
+
for vol_reg_id, subvol_id, volume, domain_name in self.volume_regions
|
|
223
|
+
if domain_name == volume_domain_name
|
|
224
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from h5py import Dataset, Group
|
|
6
|
+
from h5py import File as H5File
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StatisticType(IntEnum):
|
|
10
|
+
AVERAGE = 0
|
|
11
|
+
TOTAL = 1
|
|
12
|
+
MIN = 2
|
|
13
|
+
MAX = 3
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ImageMetadata:
|
|
17
|
+
name: str
|
|
18
|
+
group_path: str
|
|
19
|
+
extents: np.ndarray
|
|
20
|
+
origin: np.ndarray
|
|
21
|
+
shape: tuple[int, ...]
|
|
22
|
+
|
|
23
|
+
def __init__(self, name: str, group_path: str):
|
|
24
|
+
self.name = name
|
|
25
|
+
self.group_path = group_path
|
|
26
|
+
|
|
27
|
+
def get_dataset(self, hdf5_file: H5File, time_index: int) -> Dataset:
|
|
28
|
+
group_path_object = hdf5_file[self.group_path]
|
|
29
|
+
if not isinstance(group_path_object, Group):
|
|
30
|
+
raise TypeError(f"Expected a group at {self.group_path} but found {type(group_path_object)}")
|
|
31
|
+
image_group: Group = group_path_object
|
|
32
|
+
dataset_path_object = image_group[f"time{time_index:06d}"]
|
|
33
|
+
if not isinstance(dataset_path_object, Dataset):
|
|
34
|
+
raise TypeError(
|
|
35
|
+
f"Expected a dataset at {self.group_path}/time{time_index:06d} but found {type(dataset_path_object)}"
|
|
36
|
+
)
|
|
37
|
+
image_ds: Dataset = dataset_path_object
|
|
38
|
+
return image_ds
|
|
39
|
+
|
|
40
|
+
def read(self, f: H5File) -> None:
|
|
41
|
+
group_path_object = f[self.group_path]
|
|
42
|
+
if not isinstance(group_path_object, Group):
|
|
43
|
+
raise TypeError(f"Expected a group at {self.group_path} but found {type(group_path_object)}")
|
|
44
|
+
image_group: Group = group_path_object
|
|
45
|
+
|
|
46
|
+
# get attributes from the group
|
|
47
|
+
extents_list = []
|
|
48
|
+
origin_list = []
|
|
49
|
+
if "ExtentX" in image_group.attrs:
|
|
50
|
+
extents_list.append(image_group.attrs["ExtentX"])
|
|
51
|
+
if "ExtentY" in image_group.attrs:
|
|
52
|
+
extents_list.append(image_group.attrs["ExtentY"])
|
|
53
|
+
if "ExtentZ" in image_group.attrs:
|
|
54
|
+
extents_list.append(image_group.attrs["ExtentZ"])
|
|
55
|
+
if "OriginX" in image_group.attrs:
|
|
56
|
+
origin_list.append(image_group.attrs["OriginX"])
|
|
57
|
+
if "OriginY" in image_group.attrs:
|
|
58
|
+
origin_list.append(image_group.attrs["OriginY"])
|
|
59
|
+
if "OriginZ" in image_group.attrs:
|
|
60
|
+
origin_list.append(image_group.attrs["OriginZ"])
|
|
61
|
+
self.extents = np.array(extents_list)
|
|
62
|
+
self.origin = np.array(origin_list)
|
|
63
|
+
self.shape = self.get_dataset(f, 0).shape
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class VariableInfo:
|
|
67
|
+
var_index: int
|
|
68
|
+
var_name: str # e.g. "C_cyt"
|
|
69
|
+
stat_channel: int
|
|
70
|
+
statistic_type: StatisticType # e.g. StatisticType.AVERAGE
|
|
71
|
+
stat_var_name: str # e.g. "C_cyt_average"
|
|
72
|
+
stat_var_unit: str # e.g. "uM"
|
|
73
|
+
|
|
74
|
+
def __init__(self, stat_var_name: str, stat_var_unit: str, stat_channel: int, var_index: int):
|
|
75
|
+
self.stat_var_name = stat_var_name
|
|
76
|
+
self.stat_var_unit = stat_var_unit
|
|
77
|
+
self.stat_channel = stat_channel
|
|
78
|
+
self.var_index = var_index
|
|
79
|
+
# stat_var_name is in the form of "C_cyt_average" so remove _average to get the variable name
|
|
80
|
+
stat_type_raw = stat_var_name.split("_")[-1]
|
|
81
|
+
self.statistic_type = StatisticType[stat_type_raw.upper()]
|
|
82
|
+
self.var_name = stat_var_name.replace("_" + stat_type_raw, "")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PostProcessing:
|
|
86
|
+
postprocessing_hdf5_path: Path
|
|
87
|
+
times: np.ndarray
|
|
88
|
+
variables: list[VariableInfo]
|
|
89
|
+
statistics: np.ndarray # shape (times, vars, stats) where status is average=0, total=1, min=2, max=3
|
|
90
|
+
image_metadata: list[ImageMetadata]
|
|
91
|
+
|
|
92
|
+
def __init__(self, postprocessing_hdf5_path: Path):
|
|
93
|
+
self.postprocessing_hdf5_path = postprocessing_hdf5_path
|
|
94
|
+
self.variables = []
|
|
95
|
+
self.image_metadata = []
|
|
96
|
+
|
|
97
|
+
def read(self) -> None:
|
|
98
|
+
# read the file as hdf5
|
|
99
|
+
with H5File(name=self.postprocessing_hdf5_path, mode="r") as file: # type: ignore[call-arg]
|
|
100
|
+
# read dataset with path /PostProcessing/Times
|
|
101
|
+
postprocessing_times_object = file["/PostProcessing/Times"]
|
|
102
|
+
if not isinstance(postprocessing_times_object, Dataset):
|
|
103
|
+
raise TypeError(
|
|
104
|
+
f"Expected a dataset at /PostProcessing/Times but found {type(postprocessing_times_object)}"
|
|
105
|
+
)
|
|
106
|
+
times_ds: Dataset = postprocessing_times_object
|
|
107
|
+
# read array from times dataset into a ndarray
|
|
108
|
+
self.times = times_ds[()]
|
|
109
|
+
|
|
110
|
+
# read attributes from group /PostProcessing/VariableStatistics
|
|
111
|
+
# data is flat, so we can read the attributes directly, so name and units for each channel are separate
|
|
112
|
+
#
|
|
113
|
+
# key=comp_0_name, value=b'C_cyt_average'
|
|
114
|
+
# key=comp_0_unit, value=b'uM'
|
|
115
|
+
# key=comp_1_name, value=b'C_cyt_total'
|
|
116
|
+
# key=comp_1_unit, value=b'molecules'
|
|
117
|
+
# key=comp_2_name, value=b'C_cyt_min'
|
|
118
|
+
# key=comp_2_unit, value=b'uM'
|
|
119
|
+
# key=comp_3_name, value=b'C_cyt_max'
|
|
120
|
+
# key=comp_3_unit, value=b'uM'
|
|
121
|
+
#
|
|
122
|
+
var_stats_grp_object = file["/PostProcessing/VariableStatistics"]
|
|
123
|
+
if not isinstance(var_stats_grp_object, Group):
|
|
124
|
+
raise TypeError(
|
|
125
|
+
f"Expected a group at /PostProcessing/VariableStatistics but found {type(var_stats_grp_object)}"
|
|
126
|
+
)
|
|
127
|
+
var_stats_grp: Group = var_stats_grp_object
|
|
128
|
+
# gather stat_var_name and stat_var_unit for each channel into dictionaries by channel
|
|
129
|
+
var_name_by_channel: dict[int, str] = {}
|
|
130
|
+
var_unit_by_channel: dict[int, str] = {}
|
|
131
|
+
for k, v in var_stats_grp.attrs.items():
|
|
132
|
+
parts = k.split("_")
|
|
133
|
+
channel = int(parts[1])
|
|
134
|
+
if not isinstance(v, bytes):
|
|
135
|
+
raise TypeError(f"Expected a bytes object for attribute {k} but found {type(v)}")
|
|
136
|
+
value = v.decode("utf-8")
|
|
137
|
+
if parts[2] == "name":
|
|
138
|
+
var_name_by_channel[channel] = value
|
|
139
|
+
elif parts[2] == "unit":
|
|
140
|
+
var_unit_by_channel[channel] = value
|
|
141
|
+
# combine into a single list of VariableInfo objects, one for each channel
|
|
142
|
+
self.variables = [
|
|
143
|
+
VariableInfo(
|
|
144
|
+
stat_var_name=var_name_by_channel[i],
|
|
145
|
+
stat_var_unit=var_unit_by_channel[i],
|
|
146
|
+
stat_channel=i,
|
|
147
|
+
var_index=i // 4,
|
|
148
|
+
)
|
|
149
|
+
for i in range(len(var_name_by_channel))
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# within /PostProcessing/VariableStatistics, there are datasets for each time point
|
|
153
|
+
# PostProcessing/VariableStatistics
|
|
154
|
+
# PostProcessing/VariableStatistics/time000000
|
|
155
|
+
# PostProcessing/VariableStatistics/time000001
|
|
156
|
+
# PostProcessing/VariableStatistics/time000002
|
|
157
|
+
# PostProcessing/VariableStatistics/time000003
|
|
158
|
+
# PostProcessing/VariableStatistics/time000004
|
|
159
|
+
|
|
160
|
+
# we can read the data for each time point into a list of ndarrays
|
|
161
|
+
statistics_raw: np.ndarray = np.zeros((len(self.times), len(self.variables)))
|
|
162
|
+
for time_index in range(len(self.times)):
|
|
163
|
+
time_ds_object = var_stats_grp[f"time{time_index:06d}"]
|
|
164
|
+
if not isinstance(time_ds_object, Dataset):
|
|
165
|
+
raise TypeError(
|
|
166
|
+
f"Expected a dataset at /PostProcessing/VariableStatistics/time{time_index:06d} "
|
|
167
|
+
f"but found {type(time_ds_object)}"
|
|
168
|
+
)
|
|
169
|
+
time_ds: Dataset = time_ds_object
|
|
170
|
+
statistics_raw[time_index, :] = time_ds[()]
|
|
171
|
+
|
|
172
|
+
# reshape the statistics_raw into a 3D array (times, vars, stats)
|
|
173
|
+
self.statistics = statistics_raw.reshape((len(self.times), len(self.variables) // 4, 4))
|
|
174
|
+
|
|
175
|
+
# get list of child groups from /PostProcessing which are not Times or VariableStatistics
|
|
176
|
+
# e.g. /PostProcessing/fluor
|
|
177
|
+
postprocessing_dataset = file["/PostProcessing"]
|
|
178
|
+
if not isinstance(postprocessing_dataset, Group):
|
|
179
|
+
raise TypeError(f"Expected a group at /PostProcessing but found {type(postprocessing_dataset)}")
|
|
180
|
+
image_groups = [k for k in postprocessing_dataset if k not in ["Times", "VariableStatistics"]]
|
|
181
|
+
|
|
182
|
+
# for each image group, read the metadata to allow reading later
|
|
183
|
+
for image_group in image_groups:
|
|
184
|
+
metadata = ImageMetadata(group_path=f"/PostProcessing/{image_group}", name=image_group)
|
|
185
|
+
metadata.read(file)
|
|
186
|
+
self.image_metadata.append(metadata)
|
|
187
|
+
|
|
188
|
+
def read_image_data(self, image_metadata: ImageMetadata, time_index: int) -> np.ndarray:
|
|
189
|
+
with H5File(name=self.postprocessing_hdf5_path, mode="r") as file: # type: ignore[call-arg]
|
|
190
|
+
image_ds = image_metadata.get_dataset(hdf5_file=file, time_index=time_index)
|
|
191
|
+
return np.array(image_ds[()])
|