dacapo-toolbox 1.0.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.
- dacapo_toolbox/__init__.py +0 -0
- dacapo_toolbox/dataset.py +483 -0
- dacapo_toolbox/modules/__init__.py +0 -0
- dacapo_toolbox/modules/distance_head.py +29 -0
- dacapo_toolbox/postprocessing.py +281 -0
- dacapo_toolbox/py.typed +0 -0
- dacapo_toolbox/sample_datasets.py +57 -0
- dacapo_toolbox/tmp.py +11 -0
- dacapo_toolbox/transforms/affs.py +159 -0
- dacapo_toolbox/transforms/distances.py +38 -0
- dacapo_toolbox/transforms/lsds.py +285 -0
- dacapo_toolbox/transforms/weight_balancing.py +93 -0
- dacapo_toolbox/vis/preview.py +444 -0
- dacapo_toolbox-1.0.0.dist-info/METADATA +74 -0
- dacapo_toolbox-1.0.0.dist-info/RECORD +17 -0
- dacapo_toolbox-1.0.0.dist-info/WHEEL +5 -0
- dacapo_toolbox-1.0.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import logging
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
import gunpowder as gp
|
|
6
|
+
from gunpowder.nodes.gp_graph_source import GraphSource as GPGraphSource
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import dask.array as da
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from funlib.geometry import Coordinate, Roi
|
|
12
|
+
from funlib.persistence import Array
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
import time
|
|
17
|
+
import functools
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from .tmp import gcd
|
|
20
|
+
from typing import Callable
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def interpolatable_dtypes(dtype) -> bool:
|
|
26
|
+
return dtype in [np.float32, np.float64, np.uint8, np.uint16]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def nx_to_gp_graph(
|
|
30
|
+
graph: nx.Graph,
|
|
31
|
+
scale: Sequence[float],
|
|
32
|
+
) -> gp.Graph:
|
|
33
|
+
"""
|
|
34
|
+
Convert a NetworkX graph to a gunpowder Graph.
|
|
35
|
+
"""
|
|
36
|
+
graph: gp.Graph = gp.Graph(
|
|
37
|
+
[
|
|
38
|
+
gp.Node(
|
|
39
|
+
node,
|
|
40
|
+
np.array(attrs.pop("position")) / np.array(scale),
|
|
41
|
+
attrs=attrs,
|
|
42
|
+
)
|
|
43
|
+
for node, attrs in graph.nodes(data=True)
|
|
44
|
+
], # type: ignore[arg-type]
|
|
45
|
+
[gp.Edge(u, v, attrs) for u, v, attrs in graph.edges(data=True)], # type: ignore[arg-type]
|
|
46
|
+
gp.GraphSpec(Roi((None,) * len(scale), (None,) * len(scale))),
|
|
47
|
+
)
|
|
48
|
+
return graph
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def gp_to_nx_graph(
|
|
52
|
+
graph: gp.Graph,
|
|
53
|
+
) -> nx.Graph:
|
|
54
|
+
"""
|
|
55
|
+
Convert a gunpowder Graph to a NetworkX graph.
|
|
56
|
+
"""
|
|
57
|
+
g = nx.Graph()
|
|
58
|
+
for node in graph.nodes:
|
|
59
|
+
g.add_node(node.id, position=node.location, **node.attrs)
|
|
60
|
+
for edge in graph.edges:
|
|
61
|
+
g.add_edge(edge.u, edge.v, **edge.attrs)
|
|
62
|
+
return g
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PipelineDataset(torch.utils.data.IterableDataset):
|
|
66
|
+
"""
|
|
67
|
+
A torch dataset that wraps a gunpowder pipeline and provides batches of data.
|
|
68
|
+
It has support for applying torchvision style transforms to the resulting batches.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
pipeline: gp.Pipeline,
|
|
74
|
+
request: gp.BatchRequest,
|
|
75
|
+
keys: list[gp.ArrayKey],
|
|
76
|
+
transforms: dict[
|
|
77
|
+
str | tuple[str | tuple[str, ...], str | tuple[str, ...]], Callable
|
|
78
|
+
]
|
|
79
|
+
| None = None,
|
|
80
|
+
):
|
|
81
|
+
self.pipeline = pipeline
|
|
82
|
+
self.request = request
|
|
83
|
+
self.keys = keys
|
|
84
|
+
self.transforms = transforms
|
|
85
|
+
|
|
86
|
+
def __iter__(self):
|
|
87
|
+
while True:
|
|
88
|
+
t1 = time.time()
|
|
89
|
+
batch_request = self.request.copy()
|
|
90
|
+
batch_request._random_seed = random.randint(0, 2**32 - 1)
|
|
91
|
+
batch = self.pipeline.request_batch(batch_request)
|
|
92
|
+
# TODO: Throw warning on incorrect byteorder!
|
|
93
|
+
torch_batch = {
|
|
94
|
+
str(key): torch.from_numpy(
|
|
95
|
+
batch[key]
|
|
96
|
+
.data.astype(batch[key].data.dtype.newbyteorder("="))
|
|
97
|
+
.copy()
|
|
98
|
+
)
|
|
99
|
+
if isinstance(key, gp.ArrayKey)
|
|
100
|
+
else gp_to_nx_graph(batch[key])
|
|
101
|
+
for key in self.keys
|
|
102
|
+
}
|
|
103
|
+
torch_batch["metadata"] = {
|
|
104
|
+
str(key): (batch[key].spec.roi.offset, batch[key].spec.voxel_size)
|
|
105
|
+
for key in self.keys
|
|
106
|
+
if isinstance(key, gp.ArrayKey)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if self.transforms is not None:
|
|
110
|
+
for transform_signature, transform_func in self.transforms.items():
|
|
111
|
+
if isinstance(transform_signature, tuple):
|
|
112
|
+
in_key, out_key = transform_signature
|
|
113
|
+
else:
|
|
114
|
+
in_key, out_key = transform_signature, transform_signature
|
|
115
|
+
|
|
116
|
+
if isinstance(in_key, str):
|
|
117
|
+
in_keys = [in_key]
|
|
118
|
+
elif isinstance(in_key, tuple):
|
|
119
|
+
in_keys = list(in_key)
|
|
120
|
+
|
|
121
|
+
for in_key in in_keys:
|
|
122
|
+
assert in_key in torch_batch, (
|
|
123
|
+
f"Can only process keys that are in the batch. Please ensure that {in_key} "
|
|
124
|
+
f"is either provided as a dataset or created as the result of a transform "
|
|
125
|
+
f"of the form ({{in_key}}, {in_key})) *before* the transform ({in_key})."
|
|
126
|
+
)
|
|
127
|
+
in_tensors = [torch_batch[in_key] for in_key in in_keys]
|
|
128
|
+
out_tensor = transform_func(*in_tensors)
|
|
129
|
+
if isinstance(out_key, str):
|
|
130
|
+
torch_batch[out_key] = out_tensor
|
|
131
|
+
else:
|
|
132
|
+
out_keys = out_key
|
|
133
|
+
out_tensors = out_tensor
|
|
134
|
+
for out_key, out_tensor in zip(out_keys, out_tensors):
|
|
135
|
+
torch_batch[out_key] = out_tensor
|
|
136
|
+
|
|
137
|
+
t2 = time.time()
|
|
138
|
+
logger.debug(f"Batch generated in {t2 - t1:.4f} seconds")
|
|
139
|
+
yield torch_batch
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class SimpleAugmentConfig:
|
|
144
|
+
"""
|
|
145
|
+
The simple augment handles non-interpolating geometric transformations.
|
|
146
|
+
This includes mirroring and transposing in n-dimensional space.
|
|
147
|
+
See https://github.com/funkelab/gunpowder/blob/main/gunpowder/nodes/simple_augment.py
|
|
148
|
+
for more details.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
:param p: Probability of applying the augmentations.
|
|
152
|
+
:param mirror_only: List of axes to mirror. If None, all axes may be mirrored.
|
|
153
|
+
:param transpose_only: List of axes to transpose. If None, all axes may be transposed.
|
|
154
|
+
:param mirror_probs: List of probabilities for each axis in `mirror_only`.
|
|
155
|
+
If None, uses equal probability for all axes.
|
|
156
|
+
:param transpose_probs: Dictionary mapping tuples of axes to probabilities for transposing.
|
|
157
|
+
If None, uses equal probability for all axes.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
p: float = 0.0
|
|
161
|
+
mirror_only: Sequence[int] | None = None
|
|
162
|
+
transpose_only: Sequence[int] | None = None
|
|
163
|
+
mirror_probs: Sequence[float] | None = None
|
|
164
|
+
transpose_probs: dict[tuple[int, ...], float] | Sequence[float] | None = None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class DeformAugmentConfig:
|
|
169
|
+
"""
|
|
170
|
+
The deform augment handles interpolating geometric transformations.
|
|
171
|
+
This includes scaling, rotation, and elastic deformations.
|
|
172
|
+
See https://github.com/funkelab/gunpowder/blob/main/gunpowder/nodes/deform_augment.py
|
|
173
|
+
for more details.
|
|
174
|
+
|
|
175
|
+
Parameters:
|
|
176
|
+
:param p: Probability of applying the augmentations.
|
|
177
|
+
:param control_point_spacing: Spacing of the control points for the elastic deformation.
|
|
178
|
+
:param jitter_sigma: Standard deviation of the Gaussian noise added to the control points.
|
|
179
|
+
:param scale_interval: Interval for scaling the input data.
|
|
180
|
+
:param rotate: Whether to apply random rotations.
|
|
181
|
+
:param subsample: Subsampling factor for the control points.
|
|
182
|
+
:param spatial_dims: Number of spatial dimensions.
|
|
183
|
+
:param rotation_axes: Axes around which to rotate. If None, rotates around all axes.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
p: float = 0.0
|
|
187
|
+
control_point_spacing: Sequence[int] | None = None
|
|
188
|
+
jitter_sigma: Sequence[float] | None = None
|
|
189
|
+
scale_interval: tuple[float, float] | None = None
|
|
190
|
+
rotate: bool = False
|
|
191
|
+
subsample: int = 4
|
|
192
|
+
spatial_dims: int = 3
|
|
193
|
+
rotation_axes: Sequence[int] | None = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass
|
|
197
|
+
class MaskedSampling:
|
|
198
|
+
"""
|
|
199
|
+
Sampling strategy that uses a mask to determine which samples to include.
|
|
200
|
+
|
|
201
|
+
:param mask_key: The key of the mask array in the dataset.
|
|
202
|
+
:param min_masked: Minimum fraction of samples that must be masked in to include the
|
|
203
|
+
sample. If less than this fraction is masked in, the sample is skipped.
|
|
204
|
+
:param strategy: Optional strategy to apply to the mask. by default generates an integral
|
|
205
|
+
mask for quick sampling at the cost of extra memory usage. If your dataset is large, you
|
|
206
|
+
may want to use "reject".
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
mask_key: str
|
|
210
|
+
min_masked: float = 1.0
|
|
211
|
+
strategy: str = "integral_mask"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass
|
|
215
|
+
class PointSampling:
|
|
216
|
+
"""
|
|
217
|
+
Sampling strategy that uses a set of points to determine which samples to include.
|
|
218
|
+
|
|
219
|
+
:param sample_points_key: The key of the sample points array in the dataset.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
sample_points_key: str
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def iterable_dataset(
|
|
226
|
+
datasets: dict[str, Array | nx.Graph | Sequence[Array] | Sequence[nx.Graph]],
|
|
227
|
+
shapes: dict[str, Sequence[int]],
|
|
228
|
+
weights: Sequence[float] | None = None,
|
|
229
|
+
transforms: dict[
|
|
230
|
+
str | tuple[str | tuple[str, ...], str | tuple[str, ...]], Callable
|
|
231
|
+
]
|
|
232
|
+
| None = None,
|
|
233
|
+
sampling_strategies: MaskedSampling
|
|
234
|
+
| PointSampling
|
|
235
|
+
| Sequence[MaskedSampling | PointSampling]
|
|
236
|
+
| None = None,
|
|
237
|
+
trim: int | Sequence[int] | None = None,
|
|
238
|
+
simple_augment_config: SimpleAugmentConfig | None = None,
|
|
239
|
+
deform_augment_config: DeformAugmentConfig | None = None,
|
|
240
|
+
interpolatable: dict[str, bool] | None = None,
|
|
241
|
+
) -> torch.utils.data.IterableDataset:
|
|
242
|
+
"""
|
|
243
|
+
Builds a gunpowder pipeline and wraps it in a torch IterableDataset.
|
|
244
|
+
See https://pytorch.org/docs/stable/data.html#torch.utils.data.IterableDataset for more info
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
# Check the validity of the inputs
|
|
248
|
+
assert len(datasets) >= 1, "Expected at least one dataset, got an empty dictionary"
|
|
249
|
+
assert "ROI_MASK" not in datasets, (
|
|
250
|
+
"The key 'ROI_MASK' is reserved for internal use. "
|
|
251
|
+
"Please use a different key for your dataset."
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if interpolatable is None:
|
|
255
|
+
interpolatable = {}
|
|
256
|
+
|
|
257
|
+
# convert single arrays to lists
|
|
258
|
+
datasets: dict[str, list[Array] | list[nx.Graph]] = {
|
|
259
|
+
name: [ds] if isinstance(ds, (Array, nx.Graph)) else list(ds)
|
|
260
|
+
for name, ds in datasets.items()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# define keys:
|
|
264
|
+
keys = [
|
|
265
|
+
gp.ArrayKey(name) if isinstance(dataset[0], Array) else gp.GraphKey(name)
|
|
266
|
+
for name, dataset in datasets.items()
|
|
267
|
+
]
|
|
268
|
+
array_keys = [key for key in keys if isinstance(key, gp.ArrayKey)]
|
|
269
|
+
graph_keys = [key for key in keys if isinstance(key, gp.GraphKey)]
|
|
270
|
+
|
|
271
|
+
roi_mask_key = gp.ArrayKey("ROI_MASK")
|
|
272
|
+
|
|
273
|
+
# reorganize from raw: [a,b,c], gt: [a,b,c] to (raw,gt): [(a,a), (b,b), (c,c)]
|
|
274
|
+
crops_datasets: list[tuple[Array | nx.Graph, ...]] = list(zip(*datasets.values()))
|
|
275
|
+
crops_scale = [
|
|
276
|
+
functools.reduce(
|
|
277
|
+
lambda x, y: gcd(x, y),
|
|
278
|
+
[array.voxel_size for array in crop_datasets if isinstance(array, Array)],
|
|
279
|
+
)
|
|
280
|
+
for crop_datasets in crops_datasets
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
# check that for each key, all arrays have the same voxel size when scaled
|
|
284
|
+
# by the crop scale
|
|
285
|
+
assert all(
|
|
286
|
+
functools.reduce(
|
|
287
|
+
lambda x, y: x * 0 if x != y else x,
|
|
288
|
+
[
|
|
289
|
+
array.voxel_size / scale
|
|
290
|
+
for array, scale in zip(datasets[key], crops_scale)
|
|
291
|
+
if isinstance(array, Array)
|
|
292
|
+
],
|
|
293
|
+
)
|
|
294
|
+
== datasets[key][0].voxel_size / crops_scale[0]
|
|
295
|
+
for key in datasets
|
|
296
|
+
if isinstance(datasets[key][0], Array)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
if isinstance(sampling_strategies, (MaskedSampling, PointSampling)):
|
|
300
|
+
sampling_strategies = [sampling_strategies] * len(crops_datasets)
|
|
301
|
+
elif sampling_strategies is None:
|
|
302
|
+
sampling_strategies = [None] * len(crops_datasets)
|
|
303
|
+
|
|
304
|
+
# Get source nodes
|
|
305
|
+
dataset_sources = []
|
|
306
|
+
|
|
307
|
+
# type hints since zip seems to get rid of the type info
|
|
308
|
+
# crop_datasets: list[Array | nx.Graph]
|
|
309
|
+
# crop_scale: Coordinate
|
|
310
|
+
# sampling_strategy: MaskedSampling | PointSampling | None
|
|
311
|
+
|
|
312
|
+
for crop_datasets, crop_scale, sampling_strategy in zip(
|
|
313
|
+
crops_datasets, crops_scale, sampling_strategies
|
|
314
|
+
):
|
|
315
|
+
crop_arrays = [array for array in crop_datasets if isinstance(array, Array)]
|
|
316
|
+
|
|
317
|
+
for dataset in crop_datasets:
|
|
318
|
+
assert dataset is None or isinstance(dataset, (Array, nx.Graph)), (
|
|
319
|
+
f"Dataset {dataset} is not an Array or a NetworkX graph. "
|
|
320
|
+
f"{type(dataset)} is not supported. Please provide a valid dataset."
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# smallest roi
|
|
324
|
+
crop_roi = (
|
|
325
|
+
functools.reduce(
|
|
326
|
+
lambda x, y: x.intersect(y), [array.roi for array in crop_arrays]
|
|
327
|
+
)
|
|
328
|
+
/ crop_scale
|
|
329
|
+
)
|
|
330
|
+
crop_voxel_size = (
|
|
331
|
+
functools.reduce(
|
|
332
|
+
lambda x, y: Coordinate(*map(min, x, y)),
|
|
333
|
+
[array.voxel_size for array in crop_arrays],
|
|
334
|
+
)
|
|
335
|
+
/ crop_scale
|
|
336
|
+
)
|
|
337
|
+
crop_roi.snap_to_grid(crop_voxel_size, mode="grow")
|
|
338
|
+
if trim is not None:
|
|
339
|
+
if isinstance(trim, int):
|
|
340
|
+
trim = [trim] * crop_roi.dims()
|
|
341
|
+
trim = Coordinate(trim)
|
|
342
|
+
crop_roi = crop_roi.grow(-trim * crop_voxel_size, -trim * crop_voxel_size)
|
|
343
|
+
|
|
344
|
+
crop_graphs = [
|
|
345
|
+
nx_to_gp_graph(graph, crop_scale)
|
|
346
|
+
if graph is not None
|
|
347
|
+
else nx_to_gp_graph(nx.Graph(), crop_scale)
|
|
348
|
+
for graph in crop_datasets
|
|
349
|
+
if isinstance(graph, nx.Graph) or graph is None
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
crop_sources = tuple(
|
|
353
|
+
gp.ArraySource(
|
|
354
|
+
key,
|
|
355
|
+
Array(
|
|
356
|
+
array.data,
|
|
357
|
+
offset=array.roi.offset / crop_scale,
|
|
358
|
+
voxel_size=array.voxel_size / crop_scale,
|
|
359
|
+
units=array.units,
|
|
360
|
+
axis_names=array.axis_names,
|
|
361
|
+
types=array.types,
|
|
362
|
+
),
|
|
363
|
+
interpolatable=interpolatable.get(
|
|
364
|
+
str(key), interpolatable_dtypes(array.dtype)
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
+ gp.Pad(
|
|
368
|
+
key,
|
|
369
|
+
None
|
|
370
|
+
if not (
|
|
371
|
+
isinstance(sampling_strategy, MaskedSampling)
|
|
372
|
+
and sampling_strategy.mask_key == str(key)
|
|
373
|
+
)
|
|
374
|
+
else Coordinate((0,) * len(crop_scale)),
|
|
375
|
+
)
|
|
376
|
+
for key, array in zip(array_keys, crop_arrays)
|
|
377
|
+
) + tuple(
|
|
378
|
+
GPGraphSource(key, graph) for key, graph in zip(graph_keys, crop_graphs)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
crop_sources += (
|
|
382
|
+
gp.ArraySource( # a dummy array for consisntency
|
|
383
|
+
roi_mask_key,
|
|
384
|
+
Array(
|
|
385
|
+
da.ones(crop_roi.shape),
|
|
386
|
+
offset=crop_roi.offset,
|
|
387
|
+
voxel_size=crop_voxel_size,
|
|
388
|
+
),
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
dataset_source = crop_sources + gp.MergeProvider()
|
|
393
|
+
|
|
394
|
+
if sampling_strategy is None:
|
|
395
|
+
# If no sampling strategy is provided, use random sampling
|
|
396
|
+
dataset_source += gp.RandomLocation()
|
|
397
|
+
elif isinstance(sampling_strategy, PointSampling):
|
|
398
|
+
assert gp.GraphKey(sampling_strategy.sample_points_key) in keys, (
|
|
399
|
+
f"Sample points key {sampling_strategy.sample_points_key} must be one of the dataset keys: {keys}. "
|
|
400
|
+
"Please ensure that the sample points are provided as part of the dataset."
|
|
401
|
+
)
|
|
402
|
+
dataset_source += gp.RandomLocation(
|
|
403
|
+
ensure_nonempty=gp.GraphKey(sampling_strategy.sample_points_key),
|
|
404
|
+
ensure_centered=True,
|
|
405
|
+
)
|
|
406
|
+
elif (
|
|
407
|
+
isinstance(sampling_strategy, MaskedSampling)
|
|
408
|
+
and sampling_strategy.strategy == "integral_mask"
|
|
409
|
+
):
|
|
410
|
+
assert gp.ArrayKey(sampling_strategy.mask_key) in keys, (
|
|
411
|
+
f"Mask key {sampling_strategy.mask_key} must be one of the dataset keys: {keys}. "
|
|
412
|
+
"Please ensure that the mask is provided as part of the dataset."
|
|
413
|
+
)
|
|
414
|
+
dataset_source += gp.RandomLocation(
|
|
415
|
+
min_masked=sampling_strategy.min_masked,
|
|
416
|
+
mask=gp.ArrayKey(sampling_strategy.mask_key),
|
|
417
|
+
)
|
|
418
|
+
elif sampling_strategy == "reject":
|
|
419
|
+
dataset_source += gp.RandomLocation()
|
|
420
|
+
dataset_source += gp.Reject(roi_mask_key, 1.0)
|
|
421
|
+
else:
|
|
422
|
+
raise ValueError(
|
|
423
|
+
f"Unsupported sampling strategy: {sampling_strategy}. "
|
|
424
|
+
"Please use either `None`, or provide a PointSampling/MaskSampling instance."
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
dataset_sources.append(dataset_source)
|
|
428
|
+
|
|
429
|
+
pipeline = tuple(dataset_sources) + gp.RandomProvider(weights)
|
|
430
|
+
|
|
431
|
+
if deform_augment_config is not None:
|
|
432
|
+
pipeline += gp.DeformAugment(
|
|
433
|
+
control_point_spacing=Coordinate(
|
|
434
|
+
deform_augment_config.control_point_spacing
|
|
435
|
+
or (1,) * crop_scale[0].dims()
|
|
436
|
+
),
|
|
437
|
+
jitter_sigma=deform_augment_config.jitter_sigma
|
|
438
|
+
or (0,) * crop_scale[0].dims(),
|
|
439
|
+
scale_interval=deform_augment_config.scale_interval,
|
|
440
|
+
rotate=deform_augment_config.rotate,
|
|
441
|
+
subsample=deform_augment_config.subsample,
|
|
442
|
+
spatial_dims=deform_augment_config.spatial_dims,
|
|
443
|
+
rotation_axes=deform_augment_config.rotation_axes,
|
|
444
|
+
use_fast_points_transform=True,
|
|
445
|
+
p=deform_augment_config.p,
|
|
446
|
+
)
|
|
447
|
+
if simple_augment_config is not None:
|
|
448
|
+
pipeline += gp.SimpleAugment(
|
|
449
|
+
mirror_only=simple_augment_config.mirror_only,
|
|
450
|
+
transpose_only=simple_augment_config.transpose_only,
|
|
451
|
+
mirror_probs=simple_augment_config.mirror_probs,
|
|
452
|
+
transpose_probs=simple_augment_config.transpose_probs,
|
|
453
|
+
p=simple_augment_config.p,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# generate request for all necessary inputs to training
|
|
457
|
+
request = gp.BatchRequest()
|
|
458
|
+
for key in array_keys:
|
|
459
|
+
crop_scale = crops_scale[0]
|
|
460
|
+
data_shape = shapes.get(str(key), None)
|
|
461
|
+
assert data_shape is not None, (
|
|
462
|
+
f"Shape for key {key} not provided. Please provide a shape for all keys."
|
|
463
|
+
)
|
|
464
|
+
request.add(
|
|
465
|
+
key, Coordinate(data_shape) * datasets[str(key)][0].voxel_size / crop_scale
|
|
466
|
+
)
|
|
467
|
+
for key in graph_keys:
|
|
468
|
+
data_shape = shapes.get(str(key), None)
|
|
469
|
+
assert data_shape is not None, (
|
|
470
|
+
f"Shape for key {key} not provided. Please provide a shape for all keys."
|
|
471
|
+
)
|
|
472
|
+
request.add(key, Coordinate(data_shape))
|
|
473
|
+
|
|
474
|
+
# Add mask placeholder to guarantee center voxel is contained in
|
|
475
|
+
# the mask, and to be used for some sampling strategies.
|
|
476
|
+
request.add(
|
|
477
|
+
roi_mask_key,
|
|
478
|
+
crop_voxel_size,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Build the pipeline
|
|
482
|
+
gp.build(pipeline).__enter__()
|
|
483
|
+
return PipelineDataset(pipeline, request=request, keys=keys, transforms=transforms)
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DistanceHead(torch.nn.Module):
|
|
5
|
+
def __init__(self, in_channels: int = 12, incr_factor: int = 12, dims: int = 3):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.dims = dims
|
|
8
|
+
self.conv1 = torch.nn.Conv3d(
|
|
9
|
+
in_channels, in_channels * incr_factor, kernel_size=1
|
|
10
|
+
)
|
|
11
|
+
self.relu = torch.nn.LeakyReLU()
|
|
12
|
+
self.conv2 = torch.nn.Conv3d(in_channels * incr_factor, 1, kernel_size=1)
|
|
13
|
+
self.sigmoid = torch.nn.Sigmoid()
|
|
14
|
+
|
|
15
|
+
torch.nn.init.kaiming_normal_(
|
|
16
|
+
self.conv1.weight, mode="fan_out", nonlinearity="relu"
|
|
17
|
+
)
|
|
18
|
+
torch.nn.init.kaiming_normal_(
|
|
19
|
+
self.conv2.weight, mode="fan_out", nonlinearity="sigmoid"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
|
|
23
|
+
out = self.sigmoid(self.conv2(self.relu(self.conv1(x - y))))
|
|
24
|
+
# remove channel dimension
|
|
25
|
+
if out.ndim == self.dims + 1:
|
|
26
|
+
out = out.unsqueeze(0)
|
|
27
|
+
elif out.ndim == self.dims + 2:
|
|
28
|
+
out = out.squeeze(1)
|
|
29
|
+
return out
|