delta-microscopy 3.0.0a0__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.
- delta/__init__.py +50 -0
- delta/_conversions.py +425 -0
- delta/_version.py +1 -0
- delta/assets.py +195 -0
- delta/cli.py +616 -0
- delta/config.py +588 -0
- delta/data.py +1687 -0
- delta/imgops.py +1091 -0
- delta/lineage.py +872 -0
- delta/model.py +470 -0
- delta/pipeline.py +1573 -0
- delta/utils.py +1162 -0
- delta_microscopy-3.0.0a0.dist-info/METADATA +101 -0
- delta_microscopy-3.0.0a0.dist-info/RECORD +17 -0
- delta_microscopy-3.0.0a0.dist-info/WHEEL +4 -0
- delta_microscopy-3.0.0a0.dist-info/entry_points.txt +2 -0
- delta_microscopy-3.0.0a0.dist-info/licenses/LICENSE +21 -0
delta/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""DeLTA."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
logging.basicConfig(
|
|
9
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
10
|
+
level=logging.WARNING,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
if "TF_CPP_MIN_LOG_LEVEL" not in os.environ:
|
|
16
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
|
|
17
|
+
|
|
18
|
+
if "KERAS_BACKEND" not in os.environ:
|
|
19
|
+
for backend in ("torch", "tensorflow", "jax"): # Order of preference
|
|
20
|
+
if importlib.util.find_spec(backend) is not None:
|
|
21
|
+
os.environ["KERAS_BACKEND"] = backend
|
|
22
|
+
break
|
|
23
|
+
else:
|
|
24
|
+
LOGGER.critical(
|
|
25
|
+
"No deep learning backend has been found. At least one library "
|
|
26
|
+
"among torch, tensorflow or jax should be installed. "
|
|
27
|
+
"You can then select the library by passing its name to "
|
|
28
|
+
"the KERAS_BACKEND environment variable."
|
|
29
|
+
)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
# isort: off
|
|
33
|
+
|
|
34
|
+
from delta import assets, config
|
|
35
|
+
from delta import cli, data, imgops, lineage, model, pipeline, utils
|
|
36
|
+
from delta._version import __version__
|
|
37
|
+
|
|
38
|
+
# isort: on
|
|
39
|
+
|
|
40
|
+
__all__ = (
|
|
41
|
+
"__version__",
|
|
42
|
+
"assets",
|
|
43
|
+
"cli",
|
|
44
|
+
"config",
|
|
45
|
+
"data",
|
|
46
|
+
"lineage",
|
|
47
|
+
"model",
|
|
48
|
+
"pipeline",
|
|
49
|
+
"utils",
|
|
50
|
+
)
|
delta/_conversions.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Various conversions to save objects to disk."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import logging
|
|
5
|
+
from json import JSONDecodeError
|
|
6
|
+
|
|
7
|
+
import cv2
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
import xarray as xr
|
|
11
|
+
|
|
12
|
+
import delta
|
|
13
|
+
from delta import imgops, utils
|
|
14
|
+
from delta.config import Config
|
|
15
|
+
from delta.lineage import Pole
|
|
16
|
+
from delta.model import hash_model
|
|
17
|
+
|
|
18
|
+
LOGGER = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _roi_to_xarray(roi: "delta.pipeline.ROI") -> xr.Dataset:
|
|
22
|
+
# DataArray coordinates
|
|
23
|
+
frames = np.arange(len(roi.img_stack), dtype=np.int32) + roi.first_frame
|
|
24
|
+
y_orig = np.arange(roi.img_stack[0].shape[0], dtype=np.int16)
|
|
25
|
+
x_orig = np.arange(roi.img_stack[0].shape[1], dtype=np.int16)
|
|
26
|
+
tyx_orig = {"frame": frames, "y_orig": y_orig, "x_orig": x_orig}
|
|
27
|
+
y_resized = np.arange(roi.seg_stack[0].shape[0], dtype=np.int16)
|
|
28
|
+
x_resized = np.arange(roi.seg_stack[0].shape[1], dtype=np.int16)
|
|
29
|
+
tyx_resized = {"frame": frames, "y_resized": y_resized, "x_resized": x_resized}
|
|
30
|
+
cells = np.array(list(roi.lineage.cells.keys()), dtype=np.uint16)
|
|
31
|
+
ct = {"cell": cells, "frame": frames}
|
|
32
|
+
channels = roi.fluo_stack.channel
|
|
33
|
+
edge = ["-x", "+x", "-y", "+y"]
|
|
34
|
+
|
|
35
|
+
# Image stacks
|
|
36
|
+
img_stack = xr.DataArray(
|
|
37
|
+
roi.img_stack, dims=("frame", "y_orig", "x_orig"), coords=tyx_orig
|
|
38
|
+
)
|
|
39
|
+
fluo_stack = xr.DataArray(
|
|
40
|
+
roi.fluo_stack,
|
|
41
|
+
dims=("frame", "channel", "y_orig", "x_orig"),
|
|
42
|
+
coords=tyx_orig | {"channel": channels},
|
|
43
|
+
)
|
|
44
|
+
seg_stack = xr.DataArray(
|
|
45
|
+
roi.seg_stack, dims=("frame", "y_resized", "x_resized"), coords=tyx_resized
|
|
46
|
+
).astype(bool)
|
|
47
|
+
label_stack = xr.DataArray(
|
|
48
|
+
roi.label_stack, dims=("frame", "y_orig", "x_orig"), coords=tyx_orig
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Lineage (first define numpy arrays, then transform them into DataArrays)
|
|
52
|
+
nmother = np.zeros((len(cells),), dtype=np.uint16)
|
|
53
|
+
ndaughters = np.zeros((len(cells), len(frames)), dtype=np.uint16)
|
|
54
|
+
nnew_pole = np.zeros((len(cells), len(frames), 2), dtype=np.int16)
|
|
55
|
+
nold_pole = np.zeros((len(cells), len(frames), 2), dtype=np.int16)
|
|
56
|
+
nedges = np.zeros((len(cells), len(frames), 4), dtype=bool)
|
|
57
|
+
nscalars = {
|
|
58
|
+
"length": np.full((len(cells), len(frames)), np.nan, dtype=np.float32),
|
|
59
|
+
"width": np.full((len(cells), len(frames)), np.nan, dtype=np.float32),
|
|
60
|
+
"area": np.full((len(cells), len(frames)), np.nan, dtype=np.float32),
|
|
61
|
+
"perimeter": np.full((len(cells), len(frames)), np.nan, dtype=np.float32),
|
|
62
|
+
"growthrate_length": np.full(
|
|
63
|
+
(len(cells), len(frames)), np.nan, dtype=np.float32
|
|
64
|
+
),
|
|
65
|
+
"growthrate_area": np.full((len(cells), len(frames)), np.nan, dtype=np.float32),
|
|
66
|
+
}
|
|
67
|
+
nfluo = np.full((len(cells), len(frames), len(channels)), np.nan, dtype=np.float32)
|
|
68
|
+
for icell, cellid in enumerate(cells):
|
|
69
|
+
cell = roi.lineage.cells[cellid]
|
|
70
|
+
nmother[icell] = cell.motherid or 0
|
|
71
|
+
for frame in cell.frames:
|
|
72
|
+
features = cell.features(frame)
|
|
73
|
+
ndaughters[icell, frame - roi.first_frame] = cell.daughterid(frame) or 0
|
|
74
|
+
nnew_pole[icell, frame - roi.first_frame] = features.new_pole
|
|
75
|
+
nold_pole[icell, frame - roi.first_frame] = features.old_pole
|
|
76
|
+
for arg, narg in nscalars.items():
|
|
77
|
+
narg[icell, frame - roi.first_frame] = getattr(features, arg)
|
|
78
|
+
nfluo[icell, frame - roi.first_frame] = features.fluo
|
|
79
|
+
for iedge, edg in enumerate(edge):
|
|
80
|
+
nedges[icell, frame - roi.first_frame, iedge] = edg in features.edges
|
|
81
|
+
|
|
82
|
+
# Transform lineage information into DataArrays
|
|
83
|
+
mother = xr.DataArray(nmother, dims=("cell",), coords={"cell": cells})
|
|
84
|
+
daughters = xr.DataArray(ndaughters, dims=("cell", "frame"), coords=ct)
|
|
85
|
+
ctyx = ct | {"yx": ["y", "x"]}
|
|
86
|
+
new_pole = xr.DataArray(nnew_pole, dims=("cell", "frame", "yx"), coords=ctyx)
|
|
87
|
+
old_pole = xr.DataArray(nold_pole, dims=("cell", "frame", "yx"), coords=ctyx)
|
|
88
|
+
scalars = {
|
|
89
|
+
arg: xr.DataArray(narg, dims=("cell", "frame"), coords=ct)
|
|
90
|
+
for arg, narg in nscalars.items()
|
|
91
|
+
}
|
|
92
|
+
fluo = xr.DataArray(
|
|
93
|
+
nfluo, dims=("cell", "frame", "channel"), coords=ct | {"channel": channels}
|
|
94
|
+
)
|
|
95
|
+
edges = xr.DataArray(
|
|
96
|
+
nedges, dims=("cell", "frame", "edge"), coords=ct | {"edge": edge}
|
|
97
|
+
)
|
|
98
|
+
first_frame = xr.DataArray(
|
|
99
|
+
np.array(
|
|
100
|
+
[roi.lineage.cells[cellid].first_frame for cellid in cells], dtype=np.int32
|
|
101
|
+
),
|
|
102
|
+
dims=("cell",),
|
|
103
|
+
coords={"cell": cells},
|
|
104
|
+
)
|
|
105
|
+
last_frame = xr.DataArray(
|
|
106
|
+
np.array(
|
|
107
|
+
[roi.lineage.cells[cellid].last_frame for cellid in cells], dtype=np.int32
|
|
108
|
+
),
|
|
109
|
+
dims=("cell",),
|
|
110
|
+
coords={"cell": cells},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
model_hashes = {}
|
|
114
|
+
for model_name, model_config in roi.config.models.items():
|
|
115
|
+
try:
|
|
116
|
+
h = model_config._model_hash # type: ignore[attr-defined]
|
|
117
|
+
except AttributeError:
|
|
118
|
+
h = hash_model(model_config.model())
|
|
119
|
+
model_hashes[f"{model_name}_model_hash"] = h
|
|
120
|
+
|
|
121
|
+
# Create Dataset
|
|
122
|
+
dataset = xr.Dataset(
|
|
123
|
+
data_vars={
|
|
124
|
+
"img_stack": img_stack,
|
|
125
|
+
"fluo_stack": fluo_stack,
|
|
126
|
+
"seg_stack": seg_stack,
|
|
127
|
+
"label_stack": label_stack,
|
|
128
|
+
"mother": mother,
|
|
129
|
+
"daughter": daughters,
|
|
130
|
+
"new_pole": new_pole,
|
|
131
|
+
"old_pole": old_pole,
|
|
132
|
+
"edges": edges,
|
|
133
|
+
"fluo": fluo,
|
|
134
|
+
"first_frame": first_frame,
|
|
135
|
+
"last_frame": last_frame,
|
|
136
|
+
}
|
|
137
|
+
| scalars,
|
|
138
|
+
attrs={
|
|
139
|
+
"roi_nb": roi.roi_nb,
|
|
140
|
+
"box": str(roi.box.__dict__),
|
|
141
|
+
"scaling": roi.scaling,
|
|
142
|
+
"config": roi.config.serialize(),
|
|
143
|
+
"DeLTA_version": delta.__version__,
|
|
144
|
+
"file_format_version": "0.1.0",
|
|
145
|
+
}
|
|
146
|
+
| model_hashes,
|
|
147
|
+
)
|
|
148
|
+
return dataset
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _xarray_to_roi(dataset: xr.Dataset) -> "delta.pipeline.ROI":
|
|
152
|
+
box = imgops.CroppingBox(**ast.literal_eval(dataset.attrs["box"]))
|
|
153
|
+
try:
|
|
154
|
+
config = Config.deserialize(dataset.attrs["config"])
|
|
155
|
+
except JSONDecodeError:
|
|
156
|
+
LOGGER.warning(
|
|
157
|
+
"The configuration stored in this nc file is deprecated. "
|
|
158
|
+
"It will not be possible to read it anymore in a future DeLTA version. "
|
|
159
|
+
"To avoid that, just load and save this file with this DeLTA version: "
|
|
160
|
+
"`delta.pipeline.Position.load_netcdf(path).to_netcdf(path)`"
|
|
161
|
+
)
|
|
162
|
+
old_config = delta.config.OldConfig(**ast.literal_eval(dataset.attrs["config"]))
|
|
163
|
+
config = old_config.to_tomlconfig()
|
|
164
|
+
for name, model in config.models.items():
|
|
165
|
+
model._model_hash = dataset.attrs[f"{name}_model_hash"] # type: ignore[attr-defined]
|
|
166
|
+
img_stack = dataset.img_stack.expand_dims({"channel": [0]}, axis=1)
|
|
167
|
+
fluo_stack = dataset.fluo_stack.assign_coords(
|
|
168
|
+
{"channel": dataset.fluo_stack.channel + 1}
|
|
169
|
+
)
|
|
170
|
+
stack = xr.concat([img_stack, fluo_stack], dim="channel")
|
|
171
|
+
roi = delta.pipeline.ROI(
|
|
172
|
+
stack=stack.rename({"y_orig": "yc", "x_orig": "xc"}),
|
|
173
|
+
roi_nb=dataset.attrs["roi_nb"],
|
|
174
|
+
first_frame=dataset.frame[0].to_numpy() if len(dataset.frame) > 0 else 0,
|
|
175
|
+
box=box,
|
|
176
|
+
config=config,
|
|
177
|
+
)
|
|
178
|
+
roi.scaling = tuple(dataset.attrs["scaling"])
|
|
179
|
+
roi.seg_stack = list(dataset.seg_stack.to_numpy().astype(np.uint8))
|
|
180
|
+
roi.label_stack = list(dataset.label_stack.to_numpy())
|
|
181
|
+
cells = {}
|
|
182
|
+
for cellid in [int(cellid) for cellid in dataset.cell]:
|
|
183
|
+
xrcell = dataset.sel(cell=cellid)
|
|
184
|
+
mother = int(xrcell.mother)
|
|
185
|
+
frames = np.arange(xrcell.first_frame, xrcell.last_frame + 1)
|
|
186
|
+
data = xrcell.sel(frame=frames)
|
|
187
|
+
daughters = data.daughter.to_numpy()
|
|
188
|
+
edges = [
|
|
189
|
+
"".join(
|
|
190
|
+
str(e.to_numpy()) if data.edges.sel(frame=frame, edge=e) else ""
|
|
191
|
+
for e in data.edge
|
|
192
|
+
)
|
|
193
|
+
for frame in data.frame
|
|
194
|
+
]
|
|
195
|
+
cells[cellid] = delta.lineage.Cell(
|
|
196
|
+
motherid=mother if mother > 0 else None,
|
|
197
|
+
first_frame=frames[0],
|
|
198
|
+
_daughterids=[did if did > 0 else None for did in daughters],
|
|
199
|
+
_features=[
|
|
200
|
+
delta.lineage.CellFeatures(
|
|
201
|
+
new_pole=new_pole,
|
|
202
|
+
old_pole=old_pole,
|
|
203
|
+
length=length,
|
|
204
|
+
width=width,
|
|
205
|
+
area=area,
|
|
206
|
+
perimeter=perimeter,
|
|
207
|
+
fluo=fluo,
|
|
208
|
+
edges=edges,
|
|
209
|
+
growthrate_length=gr_l,
|
|
210
|
+
growthrate_area=gr_a,
|
|
211
|
+
)
|
|
212
|
+
for (
|
|
213
|
+
new_pole,
|
|
214
|
+
old_pole,
|
|
215
|
+
length,
|
|
216
|
+
width,
|
|
217
|
+
area,
|
|
218
|
+
perimeter,
|
|
219
|
+
fluo,
|
|
220
|
+
edges,
|
|
221
|
+
gr_l,
|
|
222
|
+
gr_a,
|
|
223
|
+
) in zip(
|
|
224
|
+
data.new_pole.to_numpy(),
|
|
225
|
+
data.old_pole.to_numpy(),
|
|
226
|
+
data.length.to_numpy(),
|
|
227
|
+
data.width.to_numpy(),
|
|
228
|
+
data.area.to_numpy(),
|
|
229
|
+
data.perimeter.to_numpy(),
|
|
230
|
+
data.fluo.to_numpy(),
|
|
231
|
+
edges,
|
|
232
|
+
data.growthrate_length.to_numpy(),
|
|
233
|
+
data.growthrate_area.to_numpy(),
|
|
234
|
+
strict=True,
|
|
235
|
+
)
|
|
236
|
+
],
|
|
237
|
+
)
|
|
238
|
+
roi.lineage.cells = cells
|
|
239
|
+
return roi
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _position_to_movie(
|
|
243
|
+
position: "delta.pipeline.Position",
|
|
244
|
+
reader: delta.utils.XPReader,
|
|
245
|
+
frames: range | None = None,
|
|
246
|
+
*,
|
|
247
|
+
with_labels: bool = False,
|
|
248
|
+
) -> list[npt.NDArray[np.uint8]]:
|
|
249
|
+
# Re-read trans frames:
|
|
250
|
+
trans_images = reader.images(
|
|
251
|
+
position=position.position_nb,
|
|
252
|
+
channels=reader.channels[0],
|
|
253
|
+
frames=frames,
|
|
254
|
+
rotate=position.rotate,
|
|
255
|
+
).isel(channel=0)
|
|
256
|
+
if frames is None:
|
|
257
|
+
frames = reader.frames
|
|
258
|
+
|
|
259
|
+
if position.config.correct_drift:
|
|
260
|
+
da_drift_values = xr.DataArray(
|
|
261
|
+
position.drift_values,
|
|
262
|
+
coords={"frame": trans_images.frame},
|
|
263
|
+
dims=("frame", "xy"),
|
|
264
|
+
)
|
|
265
|
+
trans_images = imgops.correct_drift(trans_images, da_drift_values)
|
|
266
|
+
|
|
267
|
+
# Fix brightness
|
|
268
|
+
ti_min = trans_images.min()
|
|
269
|
+
ti_max = trans_images.max()
|
|
270
|
+
trans_images = (trans_images - ti_min) / (ti_max - ti_min)
|
|
271
|
+
trans_images = trans_images.to_numpy()
|
|
272
|
+
|
|
273
|
+
movie = []
|
|
274
|
+
|
|
275
|
+
# Run through frames, compile movie:
|
|
276
|
+
for frame, init_trans_frame in zip(frames, trans_images, strict=True):
|
|
277
|
+
trans_frame = init_trans_frame
|
|
278
|
+
|
|
279
|
+
# RGB-ify:
|
|
280
|
+
trans_frame = np.repeat(trans_frame[:, :, np.newaxis], 3, axis=-1)
|
|
281
|
+
|
|
282
|
+
# Add frame number text:
|
|
283
|
+
trans_frame = cv2.putText(
|
|
284
|
+
trans_frame,
|
|
285
|
+
text=f"frame {frame:06d}",
|
|
286
|
+
org=(int(trans_frame.shape[0] * 0.05), int(trans_frame.shape[0] * 0.97)),
|
|
287
|
+
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
288
|
+
fontScale=1,
|
|
289
|
+
color=(1, 1, 1, 1),
|
|
290
|
+
thickness=2,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
for roi in position.rois:
|
|
294
|
+
# Get chamber-specific variables:
|
|
295
|
+
colors = utils.random_colors(
|
|
296
|
+
list(roi.lineage.cells.keys()), seed=roi.roi_nb
|
|
297
|
+
)
|
|
298
|
+
labels = roi.get_labels(frame)
|
|
299
|
+
cellids, contours = utils.cells_in_frame(labels, return_contours=True)
|
|
300
|
+
|
|
301
|
+
xtl, ytl = (roi.box.xtl, roi.box.ytl)
|
|
302
|
+
|
|
303
|
+
# Run through cells in labelled frame:
|
|
304
|
+
for icell, cellid in enumerate(cellids):
|
|
305
|
+
cell = roi.lineage.cells[cellid]
|
|
306
|
+
# Draw contours:
|
|
307
|
+
trans_frame = cv2.drawContours(
|
|
308
|
+
trans_frame,
|
|
309
|
+
contours,
|
|
310
|
+
icell,
|
|
311
|
+
color=colors[cellid],
|
|
312
|
+
thickness=1,
|
|
313
|
+
offset=(xtl, ytl),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if with_labels:
|
|
317
|
+
cell_y, cell_x = np.where(labels == cellid)
|
|
318
|
+
trans_frame = cv2.putText(
|
|
319
|
+
trans_frame,
|
|
320
|
+
text=str(cellid),
|
|
321
|
+
org=(
|
|
322
|
+
int(np.mean(cell_x) + xtl + 5),
|
|
323
|
+
int(np.mean(cell_y) + ytl + 5),
|
|
324
|
+
),
|
|
325
|
+
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
|
|
326
|
+
fontScale=0.5,
|
|
327
|
+
color=(1, 1, 1, 1),
|
|
328
|
+
thickness=1,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Draw poles:
|
|
332
|
+
oldpole = cell.features(frame).old_pole
|
|
333
|
+
assert isinstance(oldpole, np.ndarray) # for mypy
|
|
334
|
+
trans_frame = _draw_pole(
|
|
335
|
+
trans_frame,
|
|
336
|
+
contours[icell],
|
|
337
|
+
oldpole,
|
|
338
|
+
(xtl, ytl),
|
|
339
|
+
color=colors[cellid],
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
daughter = cell.daughterid(frame)
|
|
343
|
+
bornago = frame - cell.first_frame
|
|
344
|
+
mother = cell.motherid
|
|
345
|
+
|
|
346
|
+
if daughter is None and (bornago > 0 or mother is None):
|
|
347
|
+
newpole = cell.features(frame).new_pole
|
|
348
|
+
trans_frame = _draw_pole(
|
|
349
|
+
trans_frame,
|
|
350
|
+
contours[icell],
|
|
351
|
+
newpole,
|
|
352
|
+
(xtl, ytl),
|
|
353
|
+
color=(1.0, 1.0, 1.0),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Plot division arrow:
|
|
357
|
+
if daughter is not None:
|
|
358
|
+
newpole = cell.features(frame).new_pole
|
|
359
|
+
daupole = roi.lineage.cells[daughter].features(frame).new_pole
|
|
360
|
+
# Plot arrow:
|
|
361
|
+
trans_frame = cv2.arrowedLine(
|
|
362
|
+
trans_frame,
|
|
363
|
+
(newpole[1] + xtl, newpole[0] + ytl),
|
|
364
|
+
(daupole[1] + xtl, daupole[0] + ytl),
|
|
365
|
+
color=(1, 1, 1),
|
|
366
|
+
thickness=1,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Add to movie array:
|
|
370
|
+
movie += [imgops.to_integer_values(trans_frame, np.uint8)]
|
|
371
|
+
|
|
372
|
+
return movie
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _draw_pole(
|
|
376
|
+
image: npt.NDArray[np.float32],
|
|
377
|
+
contour: npt.NDArray[np.int32],
|
|
378
|
+
pole: Pole,
|
|
379
|
+
offset: tuple[int, int],
|
|
380
|
+
color: tuple[float, float, float],
|
|
381
|
+
width: int = 7,
|
|
382
|
+
) -> npt.NDArray[np.float32]:
|
|
383
|
+
contour = contour[:, 0, :]
|
|
384
|
+
pole_ind = np.argmin(np.sum(np.abs(contour - pole[::-1]), axis=1))
|
|
385
|
+
pole_cnt = contour[np.arange(pole_ind - width, pole_ind + width) % len(contour)]
|
|
386
|
+
return cv2.drawContours(image, [pole_cnt[:, None, :]], -1, color, -1, offset=offset)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _position_to_labels(
|
|
390
|
+
position: "delta.pipeline.Position",
|
|
391
|
+
frames: range | None = None,
|
|
392
|
+
*,
|
|
393
|
+
undo_corrections: bool = True,
|
|
394
|
+
) -> list[imgops.Labels]:
|
|
395
|
+
if frames is None:
|
|
396
|
+
frames = range(
|
|
397
|
+
position.rois[0].first_frame,
|
|
398
|
+
position.rois[0].first_frame + len(position.rois[0].label_stack),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
labels = []
|
|
402
|
+
for frame in frames:
|
|
403
|
+
labelled = np.zeros(position.shape, dtype=np.uint16)
|
|
404
|
+
|
|
405
|
+
for roi in position.rois:
|
|
406
|
+
roi.box.patch(labelled, roi.get_labels(frame))
|
|
407
|
+
|
|
408
|
+
# Undo drift and rotation corrections:
|
|
409
|
+
if undo_corrections:
|
|
410
|
+
shift = (0, 0)
|
|
411
|
+
if position.config.correct_drift:
|
|
412
|
+
shift = position.drift_values[frame - position.rois[0].first_frame]
|
|
413
|
+
shift = (-shift[0], -shift[1])
|
|
414
|
+
|
|
415
|
+
angle = 0.0
|
|
416
|
+
if position.config.correct_rotation:
|
|
417
|
+
angle = -position.rotate
|
|
418
|
+
|
|
419
|
+
labelled = imgops.affine_transform(
|
|
420
|
+
labelled, shift=shift, angle=angle, order=0
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
labels.append(labelled)
|
|
424
|
+
|
|
425
|
+
return labels
|
delta/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v3.0.0a0"
|
delta/assets.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asset downloads (model files, training sets, and demonstration movies).
|
|
3
|
+
|
|
4
|
+
We provide our trained models, training sets, and evaluation movies in `this
|
|
5
|
+
google drive folder
|
|
6
|
+
<https://drive.google.com/drive/folders/1nTRVo0rPP9CR9F6WUunVXSXrLNMT_zCP?usp=sharing>`_.
|
|
7
|
+
We refer to these data files as "assets".
|
|
8
|
+
|
|
9
|
+
You can of course download them manually, but shouldn't have to, because DeLTA
|
|
10
|
+
knows where to find them and downloads them automatically when needed, with the
|
|
11
|
+
`pooch library <https://www.fatiando.org/pooch/latest/>`_, which makes everything
|
|
12
|
+
completely transparent.
|
|
13
|
+
|
|
14
|
+
Cache location
|
|
15
|
+
--------------
|
|
16
|
+
|
|
17
|
+
Upon downloading the assets, DeLTA caches them in the default OS cache location
|
|
18
|
+
(see `the pooch documentation
|
|
19
|
+
<https://www.fatiando.org/pooch/latest/api/generated/pooch.os_cache.html>`_ for
|
|
20
|
+
details). If you want to download them to a different location, you can
|
|
21
|
+
specify this path in the ``DELTA_ASSETS_CACHE`` environment variable.
|
|
22
|
+
|
|
23
|
+
For example, to set this variable permanently within your conda environment,
|
|
24
|
+
you can run the following command in a terminal, inside your conda environment:
|
|
25
|
+
|
|
26
|
+
.. code:: none
|
|
27
|
+
|
|
28
|
+
(delta_env)$ conda env config vars set DELTA_ASSETS_CACHE="D:/data/delta_cache"
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Literal
|
|
33
|
+
|
|
34
|
+
import pooch
|
|
35
|
+
|
|
36
|
+
from delta._version import __version__
|
|
37
|
+
|
|
38
|
+
PRESETS = Literal["2D", "mothermachine"]
|
|
39
|
+
|
|
40
|
+
MODEL = Literal["rois", "seg", "track"]
|
|
41
|
+
|
|
42
|
+
BASE_URL = "https://drive.usercontent.google.com/download?confirm=t&id="
|
|
43
|
+
|
|
44
|
+
MODEL_REGISTRY = pooch.create(
|
|
45
|
+
path=pooch.os_cache("delta") / "models",
|
|
46
|
+
base_url="",
|
|
47
|
+
version=__version__,
|
|
48
|
+
version_dev="dev",
|
|
49
|
+
registry={
|
|
50
|
+
"moma_rois.keras": "sha256:a4585ee1a163c6a9aa83609d84246ca30c4b1f08c4656ab289476a120830e27e",
|
|
51
|
+
"moma_seg.keras": "sha256:e2428e125b013691846278224a4f607a022af41302b6dbc6c780dd97d4aa5f5a",
|
|
52
|
+
"moma_track.keras": "sha256:762f20bcfda307fbc30111b6f3f5bb67fcccc60d644dc305276fd48cbe45c35e",
|
|
53
|
+
"2D_seg.keras": "sha256:97941f89ed37fdd937fde8c4fb797dfec7b8f2972cfec26527786e823dda40c0",
|
|
54
|
+
"2D_track.keras": "sha256:e4ed540d9e126c7d3605cb38d00332d492c38676f146f75c6b139bb73c2f9fb6",
|
|
55
|
+
},
|
|
56
|
+
urls={
|
|
57
|
+
"moma_rois.keras": BASE_URL + "1ZeBOFPG7xdY3R7AY_Ba7lOfPNxzGYfaO",
|
|
58
|
+
"moma_seg.keras": BASE_URL + "1tJuRufC12MXvKDGBE90hi8yfWEUMkNMo",
|
|
59
|
+
"moma_track.keras": BASE_URL + "1MsXiNeqxXDJMoBJu-5fAfXVtMGaFcXhv",
|
|
60
|
+
"2D_seg.keras": BASE_URL + "190V0tgHk1HDhxGWQN2E4W5cozvN_5wvz",
|
|
61
|
+
"2D_track.keras": BASE_URL + "1wH4G7b191I9xcIdVTCwGO_qzC7MhepPl",
|
|
62
|
+
},
|
|
63
|
+
env="DELTA_ASSETS_CACHE",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
TRAININGSETS_REGISTRY = pooch.create(
|
|
67
|
+
path=pooch.os_cache("delta") / "training_sets",
|
|
68
|
+
base_url="",
|
|
69
|
+
version=__version__,
|
|
70
|
+
version_dev="dev",
|
|
71
|
+
registry={
|
|
72
|
+
"2D_training_segmentation.zip": "sha256:28d582ce7c3e99486e7323433fbec441e48b03f651b4c123dcc805bcf85a37c4",
|
|
73
|
+
"2D_training_tracking.zip": "sha256:af24c80b6a161ba67cfc4ee43ab08dd4ad1078343f55bcda4163337a8cc1890c",
|
|
74
|
+
"mothermachine_training_rois.zip": "sha256:51136a5e0278ed600fb01f208f452d6dfacf4dbf540af1c85ef1e125bc1567b9",
|
|
75
|
+
"mothermachine_training_segmentation.zip": "sha256:b1ec15052293ff5b16c1b8332bac8f31596f7aaa028c3c720d350fd21e7a113f",
|
|
76
|
+
"mothermachine_training_tracking.zip": "sha256:75c8b7c764a9463d4ab1dcb432b464c17456ccd0e8b76b120b8eecf73364414d",
|
|
77
|
+
},
|
|
78
|
+
urls={
|
|
79
|
+
"2D_training_segmentation.zip": BASE_URL + "1eMYwEhImt9-v8q4XlFMOAJbF6UvTkmlQ",
|
|
80
|
+
"2D_training_tracking.zip": BASE_URL + "1G4Hujltlr330rlPrFX31GR-BmnxnECLQ",
|
|
81
|
+
"mothermachine_training_rois.zip": BASE_URL
|
|
82
|
+
+ "15rg5uVZIWVy6n-trQPMHK_zSQrlcLOvl",
|
|
83
|
+
"mothermachine_training_segmentation.zip": BASE_URL
|
|
84
|
+
+ "1P3bnx0WaKf07kj7KPh1it0f_-1P8iQlf",
|
|
85
|
+
"mothermachine_training_tracking.zip": BASE_URL
|
|
86
|
+
+ "1XphzQzNKB97-JKPJ1QtGTI4_Wb7eY-pd",
|
|
87
|
+
},
|
|
88
|
+
env="DELTA_ASSETS_CACHE",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
DEMO_REGISTRY = pooch.create(
|
|
92
|
+
path=pooch.os_cache("delta") / "demo_movies",
|
|
93
|
+
base_url="",
|
|
94
|
+
version=__version__,
|
|
95
|
+
version_dev="dev",
|
|
96
|
+
registry={
|
|
97
|
+
"2D_demo.zip": "sha256:dbc294aaf67f1966dacf51fb61aa7623c1d8a477f9586e71e18b0a9a42f303dc",
|
|
98
|
+
"mothermachine_demo.zip": "sha256:3ce91358ea9982b17b0d3693a544bf168d3ee6385018e2607401647e62de0b71",
|
|
99
|
+
},
|
|
100
|
+
urls={
|
|
101
|
+
"2D_demo.zip": BASE_URL + "1qtlgQRVZ0bX5DPmr4knQBv7WoDFjQqxi",
|
|
102
|
+
"mothermachine_demo.zip": BASE_URL + "1jG9aV_MfwWDDZk5JMUnWz5ERySvydmiD",
|
|
103
|
+
},
|
|
104
|
+
env="DELTA_ASSETS_CACHE",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def download_training_set(presets: PRESETS, model: MODEL) -> Path:
|
|
109
|
+
"""
|
|
110
|
+
Return the path of the training set for the currently loaded configuration.
|
|
111
|
+
|
|
112
|
+
Depending on the current value of presets ("2D" or "mothermachine") and
|
|
113
|
+
of the value of the argument `model` ("rois", "seg" or "track"), this
|
|
114
|
+
function returns the path of the corresponding training set. If necessary,
|
|
115
|
+
it downloads it first and caches it under a directory that can be specified
|
|
116
|
+
by the environment variable `DELTA_ASSETS_CACHE`.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
presets : PRESETS
|
|
121
|
+
2D or mothermachine
|
|
122
|
+
model : str
|
|
123
|
+
Type of training set: `rois`, `seg` or `track`.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
path : Path
|
|
128
|
+
Path of the training set.
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
long_model = {"rois": "rois", "seg": "segmentation", "track": "tracking"}[model]
|
|
132
|
+
TRAININGSETS_REGISTRY.fetch(
|
|
133
|
+
f"{presets}_training_{long_model}.zip",
|
|
134
|
+
processor=pooch.Unzip(extract_dir="unzipped"),
|
|
135
|
+
progressbar=True,
|
|
136
|
+
)
|
|
137
|
+
path = Path(TRAININGSETS_REGISTRY.path) / "unzipped"
|
|
138
|
+
if presets == "mothermachine" and model in {"seg", "track"}:
|
|
139
|
+
return path / f"{presets}_training_{long_model}/train_multisets"
|
|
140
|
+
return path / f"{presets}_training_{long_model}"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def download_demo_movie(presets: PRESETS) -> Path:
|
|
144
|
+
"""
|
|
145
|
+
Return the path of the demo movie for the currently loaded configuration.
|
|
146
|
+
|
|
147
|
+
If necessary, it downloads it first and caches it under a directory that
|
|
148
|
+
can be specified by the environment variable `DELTA_ASSETS_CACHE`.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
presets : PRESETS
|
|
153
|
+
2D or mothermachine
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
path : Path
|
|
158
|
+
Path of the demo movie.
|
|
159
|
+
"""
|
|
160
|
+
DEMO_REGISTRY.fetch(
|
|
161
|
+
f"{presets}_demo.zip",
|
|
162
|
+
processor=pooch.Unzip(extract_dir="unzipped"),
|
|
163
|
+
progressbar=True,
|
|
164
|
+
)
|
|
165
|
+
if presets == "mothermachine":
|
|
166
|
+
prototype = "Pos{p}Chan{c}Frames{t}.png"
|
|
167
|
+
else:
|
|
168
|
+
prototype = "pos{p}cha{c}fra{t}.png"
|
|
169
|
+
return Path(DEMO_REGISTRY.path) / f"unzipped/{presets}_demo/{prototype}"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def download_model(presets: PRESETS, model: MODEL) -> Path:
|
|
173
|
+
"""
|
|
174
|
+
Return the path of the downloaded model, after downloading it if needed.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
presets: PRESETS
|
|
179
|
+
2D or mothermachine
|
|
180
|
+
model: MODEL
|
|
181
|
+
seg, track or rois
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
path: Path
|
|
186
|
+
Path of the downloaded model.
|
|
187
|
+
"""
|
|
188
|
+
filename = {
|
|
189
|
+
("2D", "seg"): "2D_seg.keras",
|
|
190
|
+
("2D", "track"): "2D_track.keras",
|
|
191
|
+
("mothermachine", "rois"): "moma_rois.keras",
|
|
192
|
+
("mothermachine", "seg"): "moma_seg.keras",
|
|
193
|
+
("mothermachine", "track"): "moma_track.keras",
|
|
194
|
+
}[presets, model]
|
|
195
|
+
return Path(MODEL_REGISTRY.fetch(filename, progressbar=True))
|