toolsandogh 0.1.0__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.
- toolsandogh/__init__.py +23 -0
- toolsandogh/_canonicalize_video.py +242 -0
- toolsandogh/_generate_video.py +72 -0
- toolsandogh/_load_video.py +263 -0
- toolsandogh/_plot_video.py +0 -0
- toolsandogh/_rolling.py +155 -0
- toolsandogh/_rvt.py +107 -0
- toolsandogh/_store_video.py +55 -0
- toolsandogh/_validate_video.py +69 -0
- toolsandogh/_version.py +34 -0
- toolsandogh/scripts/__init__.py +0 -0
- toolsandogh/scripts/convert.py +92 -0
- toolsandogh/scripts/iscat_analysis.py +1495 -0
- toolsandogh/tests/__init__.py +1 -0
- toolsandogh/tests/conftest.py +8 -0
- toolsandogh/tests/test_io.py +103 -0
- toolsandogh/tests/test_iscat_analysis.py +106 -0
- toolsandogh/tests/test_rolling.py +47 -0
- toolsandogh/tests/testfile.tiff +0 -0
- toolsandogh/utils/__init__.py +0 -0
- toolsandogh/utils/mproctest.py +86 -0
- toolsandogh-0.1.0.dist-info/METADATA +88 -0
- toolsandogh-0.1.0.dist-info/RECORD +26 -0
- toolsandogh-0.1.0.dist-info/WHEEL +5 -0
- toolsandogh-0.1.0.dist-info/licenses/LICENSE +621 -0
- toolsandogh-0.1.0.dist-info/top_level.txt +1 -0
toolsandogh/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A collection of tools for working with large-scale microscopy data.
|
|
3
|
+
|
|
4
|
+
Provided to you by the Sandoghdar Division of the Max Planck Institute for the Physics of Light.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._canonicalize_video import canonicalize_video
|
|
8
|
+
from ._generate_video import generate_video
|
|
9
|
+
from ._load_video import load_video
|
|
10
|
+
from ._rolling import differential_rolling_average, rolling_average, rolling_sum
|
|
11
|
+
from ._rvt import radial_variance_transform
|
|
12
|
+
from ._store_video import store_video
|
|
13
|
+
|
|
14
|
+
__all__: list[str] = [
|
|
15
|
+
"canonicalize_video",
|
|
16
|
+
"rolling_sum",
|
|
17
|
+
"rolling_average",
|
|
18
|
+
"differential_rolling_average",
|
|
19
|
+
"generate_video",
|
|
20
|
+
"load_video",
|
|
21
|
+
"store_video",
|
|
22
|
+
"radial_variance_transform",
|
|
23
|
+
]
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
import dask.array as da
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
import xarray as xr
|
|
8
|
+
from ome_types.model import (
|
|
9
|
+
OME,
|
|
10
|
+
Image,
|
|
11
|
+
Pixels,
|
|
12
|
+
Pixels_DimensionOrder,
|
|
13
|
+
PixelType,
|
|
14
|
+
UnitsLength,
|
|
15
|
+
UnitsTime,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from ._validate_video import validate_video
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def canonicalize_video(
|
|
22
|
+
video: npt.ArrayLike,
|
|
23
|
+
# optional metadata
|
|
24
|
+
acquisition_date: datetime | None = None,
|
|
25
|
+
creator: str | None = None,
|
|
26
|
+
dt: float | None = None,
|
|
27
|
+
dz: float | None = None,
|
|
28
|
+
dy: float | None = None,
|
|
29
|
+
dx: float | None = None,
|
|
30
|
+
) -> xr.DataArray:
|
|
31
|
+
"""
|
|
32
|
+
Turn the supplied data into its canonical TCZYX video representation.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
video : xr.DataArray
|
|
37
|
+
An xarray.
|
|
38
|
+
acquisition_date : datetime.datetime
|
|
39
|
+
A timestamp of when the video was created. Defaults to datetime.now().
|
|
40
|
+
creator : str
|
|
41
|
+
A string describing who created the video.
|
|
42
|
+
dt : float
|
|
43
|
+
The time interval in milliseconds between one video frame and the next.
|
|
44
|
+
Defaults to 1/60 of a second.
|
|
45
|
+
dz : float
|
|
46
|
+
The spatial distance in micrometer between one Z slice and the next.
|
|
47
|
+
Defaults to 1 micrometer.
|
|
48
|
+
dy : float
|
|
49
|
+
The spatial distance in micrometer between one row of pixels and the next.
|
|
50
|
+
Defaults to 1 micrometer.
|
|
51
|
+
dx : float
|
|
52
|
+
The spatial distance in micrometer between one column of pixels and the next.
|
|
53
|
+
Defaults to 1 micrometer.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
xarray.DataArray
|
|
58
|
+
A TCZYX video with the supplied parameters.
|
|
59
|
+
"""
|
|
60
|
+
# Turn video into an xarray.
|
|
61
|
+
if not isinstance(video, xr.DataArray):
|
|
62
|
+
video = video_from_array(video)
|
|
63
|
+
|
|
64
|
+
# Ensure the video's data is a Dask array.
|
|
65
|
+
if not isinstance(video.data, da.Array):
|
|
66
|
+
video = video.copy(data=da.from_array(video.data), deep=False)
|
|
67
|
+
|
|
68
|
+
# Ensure the TZYX axes exist and are continuous.
|
|
69
|
+
for dim in ("T", "Z", "Y", "X"):
|
|
70
|
+
if dim not in video.dims:
|
|
71
|
+
video = video.expand_dims({dim: np.arange(1.0)})
|
|
72
|
+
elif video[dim].dtype != np.float64:
|
|
73
|
+
values = np.array(video[dim])
|
|
74
|
+
video = video.assign_coords({dim: values.astype(np.float64)})
|
|
75
|
+
|
|
76
|
+
# Ensure the C axis exists.
|
|
77
|
+
if "C" not in video.dims:
|
|
78
|
+
video = video.expand_dims({"C": 1})
|
|
79
|
+
|
|
80
|
+
# If there is a S axis, merge its entries.
|
|
81
|
+
if "S" in video.dims:
|
|
82
|
+
video = video.mean("S", dtype=np.float32).astype(video.dtype)
|
|
83
|
+
|
|
84
|
+
# Ensure the correct ordering of axes.
|
|
85
|
+
video = video.transpose("T", "C", "Z", "Y", "X")
|
|
86
|
+
|
|
87
|
+
# Ensure there is metadata attached.
|
|
88
|
+
if not hasattr(video, "processed"):
|
|
89
|
+
(size_t, size_c, size_z, size_y, size_x) = video.shape
|
|
90
|
+
pixels = Pixels(
|
|
91
|
+
type=dtype_pixel_type(video.dtype),
|
|
92
|
+
big_endian=dtype_is_big_endian(video.dtype),
|
|
93
|
+
dimension_order=Pixels_DimensionOrder.XYZCT,
|
|
94
|
+
size_t=size_t,
|
|
95
|
+
size_c=size_c,
|
|
96
|
+
size_z=size_z,
|
|
97
|
+
size_y=size_y,
|
|
98
|
+
size_x=size_x,
|
|
99
|
+
time_increment=dt or (1000 / 60),
|
|
100
|
+
physical_size_z=dz or 1.0,
|
|
101
|
+
physical_size_y=dy or 1.0,
|
|
102
|
+
physical_size_x=dx or 1.0,
|
|
103
|
+
time_increment_unit=UnitsTime.MILLISECOND,
|
|
104
|
+
physical_size_z_unit=UnitsLength.MICROMETER,
|
|
105
|
+
physical_size_y_unit=UnitsLength.MICROMETER,
|
|
106
|
+
physical_size_x_unit=UnitsLength.MICROMETER,
|
|
107
|
+
)
|
|
108
|
+
image = Image(
|
|
109
|
+
acquisition_date=(acquisition_date or datetime.now(timezone.utc)),
|
|
110
|
+
description="Video with auto-generated metadata.",
|
|
111
|
+
pixels=pixels,
|
|
112
|
+
)
|
|
113
|
+
ome = OME(
|
|
114
|
+
images=[image],
|
|
115
|
+
creator=(creator or "MPL Erlangen, Sandoghdar Division, toolsandogh"),
|
|
116
|
+
)
|
|
117
|
+
video = video.assign_attrs({"processed": ome})
|
|
118
|
+
# TODO: Update metadata with any supplied parameters.
|
|
119
|
+
|
|
120
|
+
# Raise an exception if the video is still not in canonical form.
|
|
121
|
+
validate_video(video)
|
|
122
|
+
|
|
123
|
+
# Done.
|
|
124
|
+
return video
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def video_from_array(array: npt.ArrayLike) -> xr.DataArray:
|
|
128
|
+
"""
|
|
129
|
+
Turn a supplied array into an xarray.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
array : npt.ArrayLike
|
|
134
|
+
An object designating an array.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
xr.DataArray
|
|
139
|
+
An xarray with the same content and dtype as the supplied array.
|
|
140
|
+
"""
|
|
141
|
+
# Determine the Dask array holding the video's data.
|
|
142
|
+
if isinstance(array, da.Array):
|
|
143
|
+
data = array
|
|
144
|
+
else:
|
|
145
|
+
data = da.from_array(array)
|
|
146
|
+
|
|
147
|
+
# Determine the appropriate dims
|
|
148
|
+
rank = len(data.shape)
|
|
149
|
+
match rank:
|
|
150
|
+
case 0:
|
|
151
|
+
dims = ()
|
|
152
|
+
case 1:
|
|
153
|
+
dims = ("X",)
|
|
154
|
+
case 2:
|
|
155
|
+
dims = ("Y", "X")
|
|
156
|
+
case 3:
|
|
157
|
+
dims = ("T", "Y", "X")
|
|
158
|
+
case 4:
|
|
159
|
+
dims = ("T", "Z", "Y", "X")
|
|
160
|
+
case 5:
|
|
161
|
+
dims = ("T", "C", "Z", "Y", "X")
|
|
162
|
+
case 6:
|
|
163
|
+
dims = ("T", "C", "Z", "Y", "X", "S")
|
|
164
|
+
case _:
|
|
165
|
+
raise RuntimeError(f"Cannot interpret {rank}-dimensional data as a video.")
|
|
166
|
+
|
|
167
|
+
# Create the xarray.
|
|
168
|
+
return xr.DataArray(data=data, dims=dims)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def dtype_pixel_type(dtype: npt.DTypeLike) -> PixelType:
|
|
172
|
+
"""
|
|
173
|
+
Return the OME Pixel type corresponding to the supplied dtype.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
dtype : npt.DTypeLike
|
|
178
|
+
The Numpy dtype to be used for representing pixel data.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
ome_types.model.PixelType
|
|
183
|
+
A suitable OME pixel type.
|
|
184
|
+
"""
|
|
185
|
+
match np.dtype(dtype):
|
|
186
|
+
case np.int8:
|
|
187
|
+
return PixelType.INT8
|
|
188
|
+
case np.int16:
|
|
189
|
+
return PixelType.INT16
|
|
190
|
+
case np.int32:
|
|
191
|
+
return PixelType.INT32
|
|
192
|
+
case np.uint8:
|
|
193
|
+
return PixelType.UINT8
|
|
194
|
+
case np.uint16:
|
|
195
|
+
return PixelType.UINT16
|
|
196
|
+
case np.uint32:
|
|
197
|
+
return PixelType.UINT32
|
|
198
|
+
case np.float32:
|
|
199
|
+
return PixelType.FLOAT
|
|
200
|
+
case np.float64:
|
|
201
|
+
return PixelType.DOUBLE
|
|
202
|
+
case np.complex64:
|
|
203
|
+
return PixelType.COMPLEXFLOAT
|
|
204
|
+
case np.complex128:
|
|
205
|
+
return PixelType.COMPLEXDOUBLE
|
|
206
|
+
# The dtypes np.int64 and np.uint64 have no OME equivalent.
|
|
207
|
+
case np.int64:
|
|
208
|
+
return PixelType.BIT
|
|
209
|
+
case np.uint64:
|
|
210
|
+
return PixelType.BIT
|
|
211
|
+
case _:
|
|
212
|
+
raise RuntimeError(f"Cannot interpret {dtype} as a OME pixel type.")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def dtype_is_big_endian(dtype: npt.DTypeLike) -> bool:
|
|
216
|
+
"""
|
|
217
|
+
Return whether the video's data is stored in big endian byte order.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
dtype : npt.DTypeLike
|
|
222
|
+
The Numpy dtype to be used for representing pixel data.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
bool
|
|
227
|
+
True when the dtype is big endian, False otherwise.
|
|
228
|
+
"""
|
|
229
|
+
dtype = np.dtype(dtype)
|
|
230
|
+
|
|
231
|
+
if dtype.itemsize == 1:
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
match np.dtype(dtype).byteorder:
|
|
235
|
+
case ">":
|
|
236
|
+
return True
|
|
237
|
+
case "<":
|
|
238
|
+
return False
|
|
239
|
+
case "=":
|
|
240
|
+
return sys.byteorder == "big"
|
|
241
|
+
case _:
|
|
242
|
+
raise RuntimeError(f"Cannot determine endianness of {dtype}.")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import dask.array as da
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
import xarray as xr
|
|
5
|
+
|
|
6
|
+
from ._canonicalize_video import canonicalize_video
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def generate_video(
|
|
10
|
+
T: int = 1,
|
|
11
|
+
C: int = 1,
|
|
12
|
+
Z: int = 1,
|
|
13
|
+
Y: int = 1,
|
|
14
|
+
X: int = 1,
|
|
15
|
+
dt: float = 1.0,
|
|
16
|
+
dz: float = 1.0,
|
|
17
|
+
dy: float = 1.0,
|
|
18
|
+
dx: float = 1.0,
|
|
19
|
+
dtype: npt.DTypeLike = np.float32,
|
|
20
|
+
) -> xr.DataArray:
|
|
21
|
+
"""
|
|
22
|
+
Generate a video filled with random noise.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
T : int
|
|
27
|
+
The T (time) extent of the video.
|
|
28
|
+
C : int
|
|
29
|
+
The C (channel) extent of the video.
|
|
30
|
+
Z : int
|
|
31
|
+
The Z (height) extent of the video.
|
|
32
|
+
Y : int
|
|
33
|
+
The Y (row) extent of the video.
|
|
34
|
+
X : int
|
|
35
|
+
The X (column) extent of the video.
|
|
36
|
+
dt : float
|
|
37
|
+
The T (time) step size of the video.
|
|
38
|
+
dz : float
|
|
39
|
+
The Z (height) step size of the video.
|
|
40
|
+
dy : float
|
|
41
|
+
The Y (row) step size of the video.
|
|
42
|
+
dx : float
|
|
43
|
+
The X (column) step size of the video.
|
|
44
|
+
dtype : npt.DtypeLike
|
|
45
|
+
The dtype of the resulting video.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
xarray.DataArray
|
|
50
|
+
A TCZYX video with the supplied parameters.
|
|
51
|
+
"""
|
|
52
|
+
rng = da.random.default_rng()
|
|
53
|
+
video = canonicalize_video(
|
|
54
|
+
rng.random(
|
|
55
|
+
size=(T, C, Z, Y, X),
|
|
56
|
+
dtype=dtype, # type: ignore
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Check that the video matches the supplied parameters.
|
|
61
|
+
for dim, size, step in zip("TCZYX", (T, C, Z, Y, X), (dt, None, dz, dy, dx)):
|
|
62
|
+
coord = video[dim]
|
|
63
|
+
assert len(coord) == size
|
|
64
|
+
if len(coord) > 1 and step is not None:
|
|
65
|
+
array = coord.values
|
|
66
|
+
delta = array[1] - array[0]
|
|
67
|
+
error = abs(delta - step)
|
|
68
|
+
assert (error / step) <= 1e-3
|
|
69
|
+
assert video.dtype == dtype
|
|
70
|
+
|
|
71
|
+
# Done.
|
|
72
|
+
return video
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Define a function for loading videos from a wide range of sources."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import os.path
|
|
5
|
+
import pathlib
|
|
6
|
+
import urllib.parse
|
|
7
|
+
|
|
8
|
+
import bioio
|
|
9
|
+
import bioio_czi
|
|
10
|
+
import bioio_nd2
|
|
11
|
+
import dask.array as da
|
|
12
|
+
import fsspec
|
|
13
|
+
import numpy as np
|
|
14
|
+
import numpy.typing as npt
|
|
15
|
+
import xarray as xr
|
|
16
|
+
from fsspec.utils import math
|
|
17
|
+
|
|
18
|
+
from ._canonicalize_video import canonicalize_video
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_video(
|
|
22
|
+
path: str | os.PathLike,
|
|
23
|
+
T: int | None = None,
|
|
24
|
+
C: int | None = None,
|
|
25
|
+
Z: int | None = None,
|
|
26
|
+
Y: int | None = None,
|
|
27
|
+
X: int | None = None,
|
|
28
|
+
dt: float | None = None,
|
|
29
|
+
dz: float | None = None,
|
|
30
|
+
dy: float | None = None,
|
|
31
|
+
dx: float | None = None,
|
|
32
|
+
dtype: npt.DTypeLike | None = None,
|
|
33
|
+
) -> xr.DataArray:
|
|
34
|
+
"""
|
|
35
|
+
Load a video from the given path.
|
|
36
|
+
|
|
37
|
+
The resulting video is a 5D xarray with dimensions TCZYX. If any keyword
|
|
38
|
+
arguments are supplied, this function asserts that the resulting video
|
|
39
|
+
matches these arguments, e.g., Z=1 may be used assert that the result is a
|
|
40
|
+
2D video.
|
|
41
|
+
|
|
42
|
+
The suffix of the supplied path determines the method that is used for
|
|
43
|
+
reading the file. For .raw and .bin files, the X, Y arguments are
|
|
44
|
+
mandatory, the number of frames T is inferred automatically, and the
|
|
45
|
+
remaining arguments default to Z=1, C=1, dz=1.0, dy=dx=1.0, dt=1.0, and
|
|
46
|
+
dtype=uint8.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
path : os.PathLike
|
|
51
|
+
The name of a file, a URI, or a path.
|
|
52
|
+
T : int
|
|
53
|
+
The expected T (time) extent of the video.
|
|
54
|
+
C : int
|
|
55
|
+
The expected C (channel) extent of the video.
|
|
56
|
+
Z : int
|
|
57
|
+
The expected Z (height) extent of the video.
|
|
58
|
+
Y : int
|
|
59
|
+
The expected Y (row) extent of the video.
|
|
60
|
+
X : int
|
|
61
|
+
The expected X (column) extent of the video.
|
|
62
|
+
dt : float
|
|
63
|
+
The expected T (time) step size of the video.
|
|
64
|
+
dz : float
|
|
65
|
+
The expected Z (height) step size of the video.
|
|
66
|
+
dy : float
|
|
67
|
+
The expected Y (row) step size of the video.
|
|
68
|
+
dx : float
|
|
69
|
+
The expected X (column) step size of the video.
|
|
70
|
+
dtype : npt.DtypeLike
|
|
71
|
+
The expected dtype of the video.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
xarray.DataArray
|
|
76
|
+
A canonical TCZYX array that matches the supplied parameters.
|
|
77
|
+
"""
|
|
78
|
+
# Parse the supplied path.
|
|
79
|
+
pathstr = str(path)
|
|
80
|
+
url = urllib.parse.urlparse(pathstr)
|
|
81
|
+
path = pathlib.Path(url.path)
|
|
82
|
+
|
|
83
|
+
# Gather all keyword arguments for those load functions that require them.
|
|
84
|
+
kwargs = {
|
|
85
|
+
"T": T,
|
|
86
|
+
"C": C,
|
|
87
|
+
"Z": Z,
|
|
88
|
+
"Y": Y,
|
|
89
|
+
"X": X,
|
|
90
|
+
"dt": dt,
|
|
91
|
+
"dz": dz,
|
|
92
|
+
"dy": dy,
|
|
93
|
+
"dx": dx,
|
|
94
|
+
"dtype": dtype,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Load the video.
|
|
98
|
+
protocols = fsspec.available_protocols()
|
|
99
|
+
match (url.scheme or "file", path.suffix):
|
|
100
|
+
case (scheme, ".bin" | ".raw") if scheme in protocols:
|
|
101
|
+
video = load_raw_video(path, **kwargs)
|
|
102
|
+
case (_, ".czi"):
|
|
103
|
+
img = bioio.BioImage(pathstr, reader=bioio_czi.Reader)
|
|
104
|
+
video = img.xarray_dask_data
|
|
105
|
+
case (_, ".nd2"):
|
|
106
|
+
img = bioio.BioImage(pathstr, reader=bioio_nd2.Reader)
|
|
107
|
+
video = img.xarray_dask_data
|
|
108
|
+
case (_, _):
|
|
109
|
+
# Let bioio figure out the rest or raise an exception
|
|
110
|
+
img = bioio.BioImage(pathstr)
|
|
111
|
+
video = img.xarray_dask_data
|
|
112
|
+
|
|
113
|
+
# Ensure the video is canonical and return.
|
|
114
|
+
return canonicalize_video(video)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def load_raw_video(
|
|
118
|
+
path: os.PathLike,
|
|
119
|
+
T: int | None = None,
|
|
120
|
+
C: int | None = None,
|
|
121
|
+
Z: int | None = None,
|
|
122
|
+
Y: int | None = None,
|
|
123
|
+
X: int | None = None,
|
|
124
|
+
dt: float | None = None,
|
|
125
|
+
dz: float | None = None,
|
|
126
|
+
dy: float | None = None,
|
|
127
|
+
dx: float | None = None,
|
|
128
|
+
dtype: npt.DTypeLike | None = None,
|
|
129
|
+
) -> xr.DataArray:
|
|
130
|
+
"""
|
|
131
|
+
Load a video from the specified raw file.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
path : os.PathLike
|
|
136
|
+
The name of a file, a URI, or a path.
|
|
137
|
+
T : int
|
|
138
|
+
The expected T (time) extent of the video.
|
|
139
|
+
C : int
|
|
140
|
+
The expected C (channel) extent of the video.
|
|
141
|
+
Z : int
|
|
142
|
+
The expected Z (height) extent of the video.
|
|
143
|
+
Y : int
|
|
144
|
+
The expected Y (row) extent of the video.
|
|
145
|
+
X : int
|
|
146
|
+
The expected X (column) extent of the video.
|
|
147
|
+
dt : float
|
|
148
|
+
The expected T (time) step size of the video.
|
|
149
|
+
dz : float
|
|
150
|
+
The expected Z (height) step size of the video.
|
|
151
|
+
dy : float
|
|
152
|
+
The expected Y (row) step size of the video.
|
|
153
|
+
dx : float
|
|
154
|
+
The expected X (column) step size of the video.
|
|
155
|
+
dtype : npt.DtypeLike
|
|
156
|
+
The expected dtype of the video.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
xarray.DataArray
|
|
161
|
+
A canonical TCZYX array that matches the supplied parameters.
|
|
162
|
+
"""
|
|
163
|
+
# The parameters X and Y are mandatory for loading raw files.
|
|
164
|
+
if Y is None:
|
|
165
|
+
raise RuntimeError("The parameter Y is required for loading raw files.")
|
|
166
|
+
if X is None:
|
|
167
|
+
raise RuntimeError("The parameter X is required for loading raw files.")
|
|
168
|
+
# Z and C default to one.
|
|
169
|
+
if Z is None:
|
|
170
|
+
Z = 1
|
|
171
|
+
if C is None:
|
|
172
|
+
C = 1
|
|
173
|
+
# The default dtype is np.uint8
|
|
174
|
+
if dtype is None:
|
|
175
|
+
dtype = np.uint8
|
|
176
|
+
# The number of frames T can be inferred from the file's size.
|
|
177
|
+
bits_per_item = 12 if (dtype == "uint12") else (np.dtype(dtype).itemsize * 8)
|
|
178
|
+
nbits = os.path.getsize(path) * 8
|
|
179
|
+
nitems = nbits // bits_per_item
|
|
180
|
+
items_per_T = C * Z * Y * X
|
|
181
|
+
if T is None:
|
|
182
|
+
T = nitems // items_per_T
|
|
183
|
+
nexpected = T * items_per_T
|
|
184
|
+
if nexpected > nitems:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"The file {path} has only {nitems} items, but {nexpected} were expected."
|
|
187
|
+
)
|
|
188
|
+
# All step sizes default to 1.0.
|
|
189
|
+
if dt is None:
|
|
190
|
+
dt = 1.0
|
|
191
|
+
if dz is None:
|
|
192
|
+
dz = 1.0
|
|
193
|
+
if dy is None:
|
|
194
|
+
dy = dx or 1.0
|
|
195
|
+
if dx is None:
|
|
196
|
+
dx = dy or 1.0
|
|
197
|
+
# Load the file as a dask array. Treat the uint12 case specially.
|
|
198
|
+
shape = (T, C, Z, Y, X)
|
|
199
|
+
chunks = ("auto", "auto", None, None, None)
|
|
200
|
+
if dtype == "uint12":
|
|
201
|
+
nbytes = (nexpected * 12) // 8
|
|
202
|
+
mmap_array = np.memmap(path, dtype=np.uint8, mode="r", shape=(nbytes,))
|
|
203
|
+
dask_array = da.from_array(mmap_array)
|
|
204
|
+
a = dask_array[0::3].astype(np.uint16)
|
|
205
|
+
b = dask_array[1::3].astype(np.uint16)
|
|
206
|
+
c = dask_array[2::3].astype(np.uint16)
|
|
207
|
+
assert len(a) == len(b) == len(c)
|
|
208
|
+
evens = ((b & 0x0F) << 8) | a
|
|
209
|
+
odds = ((b & 0xF0) >> 4) | (c << 4)
|
|
210
|
+
flat = da.stack([evens, odds], axis=1).ravel()
|
|
211
|
+
dask_array = flat.reshape((T, C, Z, Y, X), chunks=chunks)
|
|
212
|
+
else:
|
|
213
|
+
dtype = np.dtype(dtype)
|
|
214
|
+
mmap_array = np.memmap(path, dtype=dtype, mode="r", shape=shape)
|
|
215
|
+
dask_array = da.from_array(mmap_array, chunks=chunks) # type: ignore
|
|
216
|
+
# Wrap the dask array as a xarray.DataArray and return it.
|
|
217
|
+
return xr.DataArray(
|
|
218
|
+
dask_array,
|
|
219
|
+
coords={
|
|
220
|
+
"T": dt * np.arange(T),
|
|
221
|
+
"C": range(C),
|
|
222
|
+
"Z": dz * np.arange(Z),
|
|
223
|
+
"Y": dy * np.arange(Y),
|
|
224
|
+
"X": dx * np.arange(X),
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def load_raw_chunk(
|
|
230
|
+
path: os.PathLike,
|
|
231
|
+
shape: tuple[int, ...],
|
|
232
|
+
dtype: npt.DTypeLike,
|
|
233
|
+
offset: int,
|
|
234
|
+
) -> np.ndarray:
|
|
235
|
+
"""
|
|
236
|
+
Load a portion of the supplied raw file as a Numpy array.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
path : os.PathLike
|
|
241
|
+
Path (local or remote) to the raw file.
|
|
242
|
+
shape : tuple[int, ...]
|
|
243
|
+
Desired shape of the returned array (e.g. ``(T, C, Z, Y, X)``). The
|
|
244
|
+
product of the dimensions must match the number of items that will be
|
|
245
|
+
read.
|
|
246
|
+
dtype : npt.DTypeLike
|
|
247
|
+
Numpy data type of the stored items.
|
|
248
|
+
offset : int
|
|
249
|
+
Index of the first element to read **in items**, *not* in bytes.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
np.ndarray
|
|
254
|
+
A dense NumPy array with the requested shape and dtype.
|
|
255
|
+
"""
|
|
256
|
+
count = math.prod(shape)
|
|
257
|
+
with fsspec.open(path, newline="") as f:
|
|
258
|
+
return np.fromfile(
|
|
259
|
+
f, # type: ignore
|
|
260
|
+
dtype=dtype,
|
|
261
|
+
offset=offset,
|
|
262
|
+
count=count,
|
|
263
|
+
).reshape(shape)
|
|
File without changes
|