dcnum 0.17.0__py3-none-any.whl → 0.23.2__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.
Potentially problematic release.
This version of dcnum might be problematic. Click here for more details.
- dcnum/_version.py +2 -2
- dcnum/feat/__init__.py +1 -1
- dcnum/feat/event_extractor_manager_thread.py +34 -25
- dcnum/feat/feat_background/base.py +22 -26
- dcnum/feat/feat_background/bg_copy.py +18 -12
- dcnum/feat/feat_background/bg_roll_median.py +20 -10
- dcnum/feat/feat_background/bg_sparse_median.py +55 -7
- dcnum/feat/feat_brightness/bright_all.py +41 -6
- dcnum/feat/feat_contour/__init__.py +4 -0
- dcnum/feat/{feat_moments/mt_legacy.py → feat_contour/moments.py} +32 -8
- dcnum/feat/feat_contour/volume.py +174 -0
- dcnum/feat/feat_texture/tex_all.py +28 -1
- dcnum/feat/gate.py +2 -2
- dcnum/feat/queue_event_extractor.py +30 -9
- dcnum/logic/ctrl.py +222 -48
- dcnum/logic/job.py +85 -2
- dcnum/logic/json_encoder.py +2 -0
- dcnum/meta/ppid.py +17 -3
- dcnum/read/__init__.py +1 -0
- dcnum/read/cache.py +100 -78
- dcnum/read/const.py +6 -4
- dcnum/read/hdf5_data.py +146 -23
- dcnum/read/mapped.py +87 -0
- dcnum/segm/__init__.py +6 -3
- dcnum/segm/segm_thresh.py +6 -18
- dcnum/segm/segm_torch/__init__.py +23 -0
- dcnum/segm/segm_torch/segm_torch_base.py +125 -0
- dcnum/segm/segm_torch/segm_torch_mpo.py +71 -0
- dcnum/segm/segm_torch/segm_torch_sto.py +88 -0
- dcnum/segm/segm_torch/torch_model.py +95 -0
- dcnum/segm/segm_torch/torch_postproc.py +93 -0
- dcnum/segm/segm_torch/torch_preproc.py +114 -0
- dcnum/segm/segmenter.py +181 -80
- dcnum/segm/segmenter_manager_thread.py +38 -30
- dcnum/segm/{segmenter_cpu.py → segmenter_mpo.py} +116 -44
- dcnum/segm/segmenter_sto.py +110 -0
- dcnum/write/__init__.py +2 -1
- dcnum/write/deque_writer_thread.py +9 -1
- dcnum/write/queue_collector_thread.py +8 -14
- dcnum/write/writer.py +128 -5
- {dcnum-0.17.0.dist-info → dcnum-0.23.2.dist-info}/METADATA +4 -2
- dcnum-0.23.2.dist-info/RECORD +55 -0
- {dcnum-0.17.0.dist-info → dcnum-0.23.2.dist-info}/WHEEL +1 -1
- dcnum/feat/feat_moments/__init__.py +0 -4
- dcnum/segm/segmenter_gpu.py +0 -64
- dcnum-0.17.0.dist-info/RECORD +0 -46
- /dcnum/feat/{feat_moments/ct_opencv.py → feat_contour/contour.py} +0 -0
- {dcnum-0.17.0.dist-info → dcnum-0.23.2.dist-info}/LICENSE +0 -0
- {dcnum-0.17.0.dist-info → dcnum-0.23.2.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ import threading
|
|
|
5
5
|
from typing import Dict
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
+
import scipy.ndimage as ndi
|
|
8
9
|
|
|
9
10
|
from .segmenter import Segmenter
|
|
10
11
|
|
|
@@ -14,7 +15,7 @@ from .segmenter import Segmenter
|
|
|
14
15
|
mp_spawn = mp.get_context('spawn')
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class
|
|
18
|
+
class MPOSegmenter(Segmenter, abc.ABC):
|
|
18
19
|
hardware_processor = "cpu"
|
|
19
20
|
|
|
20
21
|
def __init__(self,
|
|
@@ -23,7 +24,7 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
23
24
|
kwargs_mask: Dict = None,
|
|
24
25
|
debug: bool = False,
|
|
25
26
|
**kwargs):
|
|
26
|
-
"""
|
|
27
|
+
"""Segmenter with multiprocessing operation
|
|
27
28
|
|
|
28
29
|
Parameters
|
|
29
30
|
----------
|
|
@@ -32,17 +33,23 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
32
33
|
debug: bool
|
|
33
34
|
Debugging parameters
|
|
34
35
|
kwargs:
|
|
35
|
-
Additional, optional keyword arguments for `
|
|
36
|
+
Additional, optional keyword arguments for `segment_algorithm`
|
|
36
37
|
defined in the subclass.
|
|
37
38
|
"""
|
|
38
|
-
super(
|
|
39
|
+
super(MPOSegmenter, self).__init__(kwargs_mask=kwargs_mask,
|
|
39
40
|
debug=debug,
|
|
40
41
|
**kwargs)
|
|
41
42
|
self.num_workers = num_workers or mp.cpu_count()
|
|
43
|
+
# batch input image data
|
|
42
44
|
self.mp_image_raw = None
|
|
43
45
|
self._mp_image_np = None
|
|
46
|
+
# batch output image data
|
|
44
47
|
self.mp_labels_raw = None
|
|
45
48
|
self._mp_labels_np = None
|
|
49
|
+
# batch image background offset
|
|
50
|
+
self.mp_bg_off_raw = None
|
|
51
|
+
self._mp_bg_off_np = None
|
|
52
|
+
# workers
|
|
46
53
|
self._mp_workers = []
|
|
47
54
|
# Image shape of the input array
|
|
48
55
|
self.image_shape = None
|
|
@@ -78,6 +85,7 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
78
85
|
del state["logger"]
|
|
79
86
|
del state["_mp_image_np"]
|
|
80
87
|
del state["_mp_labels_np"]
|
|
88
|
+
del state["_mp_bg_off_np"]
|
|
81
89
|
del state["_mp_workers"]
|
|
82
90
|
return state
|
|
83
91
|
|
|
@@ -86,26 +94,26 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
86
94
|
self.__dict__.update(state)
|
|
87
95
|
|
|
88
96
|
@staticmethod
|
|
89
|
-
def _create_shared_array(
|
|
97
|
+
def _create_shared_array(array_shape, batch_size, dtype):
|
|
90
98
|
"""Return raw and numpy-view on shared array
|
|
91
99
|
|
|
92
100
|
Parameters
|
|
93
101
|
----------
|
|
94
|
-
|
|
102
|
+
array_shape: tuple of int
|
|
95
103
|
Shape of one single image in the array
|
|
96
104
|
batch_size: int
|
|
97
105
|
Number of images in the array
|
|
98
106
|
dtype:
|
|
99
|
-
|
|
100
|
-
or `np.ctypeslib.ctypes.c_bool`
|
|
107
|
+
numpy dtype
|
|
101
108
|
"""
|
|
102
|
-
|
|
103
|
-
sa_raw = mp_spawn.RawArray(
|
|
109
|
+
ctype = np.ctypeslib.as_ctypes_type(dtype)
|
|
110
|
+
sa_raw = mp_spawn.RawArray(ctype,
|
|
111
|
+
int(np.prod(array_shape) * batch_size))
|
|
104
112
|
# Convert the RawArray to something we can write to fast
|
|
105
113
|
# (similar to memory view, but without having to cast) using
|
|
106
114
|
# np.ctypeslib.as_array. See discussion in
|
|
107
115
|
# https://stackoverflow.com/questions/37705974
|
|
108
|
-
sa_np = np.ctypeslib.as_array(sa_raw).reshape(batch_size,
|
|
116
|
+
sa_np = np.ctypeslib.as_array(sa_raw).reshape(batch_size, *array_shape)
|
|
109
117
|
return sa_raw, sa_np
|
|
110
118
|
|
|
111
119
|
@property
|
|
@@ -127,39 +135,49 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
127
135
|
[w.join() for w in self._mp_workers]
|
|
128
136
|
|
|
129
137
|
def segment_batch(self,
|
|
130
|
-
|
|
138
|
+
images: np.ndarray,
|
|
131
139
|
start: int = None,
|
|
132
|
-
stop: int = None
|
|
133
|
-
|
|
140
|
+
stop: int = None,
|
|
141
|
+
bg_off: np.ndarray = None,
|
|
142
|
+
):
|
|
143
|
+
"""Perform batch segmentation of `images`
|
|
144
|
+
|
|
145
|
+
Before segmentation, an optional background offset correction with
|
|
146
|
+
`bg_off` is performed. After segmentation, mask postprocessing is
|
|
147
|
+
performed according to the class definition.
|
|
134
148
|
|
|
135
149
|
Parameters
|
|
136
150
|
----------
|
|
137
|
-
|
|
151
|
+
images: 3d np.ndarray
|
|
138
152
|
The time-series image data. First axis is time.
|
|
139
153
|
start: int
|
|
140
|
-
First index to analyze in `
|
|
154
|
+
First index to analyze in `images`
|
|
141
155
|
stop: int
|
|
142
|
-
Index after the last index to analyze in `
|
|
156
|
+
Index after the last index to analyze in `images`
|
|
157
|
+
bg_off: 1D np.ndarray
|
|
158
|
+
Optional 1D numpy array with background offset
|
|
143
159
|
|
|
144
160
|
Notes
|
|
145
161
|
-----
|
|
146
162
|
- If the segmentation algorithm only accepts background-corrected
|
|
147
|
-
images, then `
|
|
163
|
+
images, then `images` must already be background-corrected,
|
|
164
|
+
except for the optional `bg_off`.
|
|
148
165
|
"""
|
|
149
166
|
if stop is None or start is None:
|
|
150
167
|
start = 0
|
|
151
|
-
stop = len(
|
|
168
|
+
stop = len(images)
|
|
152
169
|
|
|
153
170
|
batch_size = stop - start
|
|
154
|
-
size = np.prod(
|
|
171
|
+
size = np.prod(images.shape[1:]) * batch_size
|
|
155
172
|
|
|
156
173
|
if self.image_shape is None:
|
|
157
|
-
self.image_shape =
|
|
174
|
+
self.image_shape = images[0].shape
|
|
158
175
|
|
|
159
176
|
if self._mp_image_np is not None and self._mp_image_np.size != size:
|
|
160
177
|
# reset image data
|
|
161
178
|
self._mp_image_np = None
|
|
162
179
|
self._mp_labels_np = None
|
|
180
|
+
self._mp_bg_off_np = None
|
|
163
181
|
# TODO: If only the batch_size changes, don't
|
|
164
182
|
# reinitialize the workers. Otherwise, the final rest of
|
|
165
183
|
# analyzing a dataset would always take a little longer.
|
|
@@ -168,30 +186,48 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
168
186
|
self.mp_batch_index.value = -1
|
|
169
187
|
self.mp_shutdown.value = 0
|
|
170
188
|
|
|
189
|
+
if bg_off is not None:
|
|
190
|
+
if not self.requires_background_correction:
|
|
191
|
+
raise ValueError(f"The segmenter {self.__class__.__name__} "
|
|
192
|
+
f"does not employ background correction, "
|
|
193
|
+
f"but the `bg_off` keyword argument was "
|
|
194
|
+
f"passed to `segment_chunk`. Please check "
|
|
195
|
+
f"your analysis pipeline.")
|
|
196
|
+
# background offset
|
|
197
|
+
if self._mp_bg_off_np is None:
|
|
198
|
+
self.mp_bg_off_raw, self._mp_bg_off_np = \
|
|
199
|
+
self._create_shared_array(
|
|
200
|
+
array_shape=(stop - start,),
|
|
201
|
+
batch_size=batch_size,
|
|
202
|
+
dtype=np.float64)
|
|
203
|
+
self._mp_bg_off_np[:] = bg_off[start:stop]
|
|
204
|
+
|
|
205
|
+
# input images
|
|
171
206
|
if self._mp_image_np is None:
|
|
172
207
|
self.mp_image_raw, self._mp_image_np = self._create_shared_array(
|
|
173
|
-
|
|
208
|
+
array_shape=self.image_shape,
|
|
174
209
|
batch_size=batch_size,
|
|
175
|
-
dtype=
|
|
210
|
+
dtype=images.dtype,
|
|
176
211
|
)
|
|
212
|
+
self._mp_image_np[:] = images[start:stop]
|
|
177
213
|
|
|
214
|
+
# output labels
|
|
178
215
|
if self._mp_labels_np is None:
|
|
179
216
|
self.mp_labels_raw, self._mp_labels_np = self._create_shared_array(
|
|
180
|
-
|
|
217
|
+
array_shape=self.image_shape,
|
|
181
218
|
batch_size=batch_size,
|
|
182
|
-
dtype=np.
|
|
219
|
+
dtype=np.uint16,
|
|
183
220
|
)
|
|
184
221
|
|
|
185
|
-
# populate image data
|
|
186
|
-
self._mp_image_np[:] = image_data[start:stop]
|
|
187
|
-
|
|
188
222
|
# Create the workers
|
|
189
223
|
if self.debug:
|
|
190
|
-
worker_cls =
|
|
224
|
+
worker_cls = MPOSegmenterWorkerThread
|
|
191
225
|
num_workers = 1
|
|
226
|
+
self.logger.debug("Running with one worker in main thread")
|
|
192
227
|
else:
|
|
193
|
-
worker_cls =
|
|
194
|
-
num_workers = min(self.num_workers,
|
|
228
|
+
worker_cls = MPOSegmenterWorkerProcess
|
|
229
|
+
num_workers = min(self.num_workers, images.shape[0])
|
|
230
|
+
self.logger.debug(f"Running with {num_workers} workers")
|
|
195
231
|
|
|
196
232
|
if not self._mp_workers:
|
|
197
233
|
step_size = batch_size // num_workers
|
|
@@ -222,8 +258,33 @@ class CPUSegmenter(Segmenter, abc.ABC):
|
|
|
222
258
|
|
|
223
259
|
return self._mp_labels_np
|
|
224
260
|
|
|
261
|
+
def segment_single(self, image, bg_off: float = None):
|
|
262
|
+
"""Return the integer label image for an input image
|
|
263
|
+
|
|
264
|
+
Before segmentation, an optional background offset correction with
|
|
265
|
+
`bg_off` is performed. After segmentation, mask postprocessing is
|
|
266
|
+
performed according to the class definition.
|
|
267
|
+
"""
|
|
268
|
+
segm_wrap = self.segment_algorithm_wrapper()
|
|
269
|
+
# optional subtraction of background offset
|
|
270
|
+
if bg_off is not None:
|
|
271
|
+
image = image - bg_off
|
|
272
|
+
# obtain mask or label
|
|
273
|
+
mol = segm_wrap(image)
|
|
274
|
+
if mol.dtype == bool:
|
|
275
|
+
# convert mask to labels
|
|
276
|
+
labels, _ = ndi.label(
|
|
277
|
+
input=mol,
|
|
278
|
+
structure=ndi.generate_binary_structure(2, 2))
|
|
279
|
+
else:
|
|
280
|
+
labels = mol
|
|
281
|
+
# optional mask/label postprocessing
|
|
282
|
+
if self.mask_postprocessing:
|
|
283
|
+
labels = self.process_mask(labels, **self.kwargs_mask)
|
|
284
|
+
return labels
|
|
285
|
+
|
|
225
286
|
|
|
226
|
-
class
|
|
287
|
+
class MPOSegmenterWorker:
|
|
227
288
|
def __init__(self,
|
|
228
289
|
segmenter,
|
|
229
290
|
sl_start: int,
|
|
@@ -233,7 +294,7 @@ class CPUSegmenterWorker:
|
|
|
233
294
|
|
|
234
295
|
Parameters
|
|
235
296
|
----------
|
|
236
|
-
segmenter:
|
|
297
|
+
segmenter: MPOSegmenter
|
|
237
298
|
The segmentation instance
|
|
238
299
|
sl_start: int
|
|
239
300
|
Start of slice of input array to process
|
|
@@ -241,7 +302,7 @@ class CPUSegmenterWorker:
|
|
|
241
302
|
Stop of slice of input array to process
|
|
242
303
|
"""
|
|
243
304
|
# Must call super init, otherwise Thread or Process are not initialized
|
|
244
|
-
super(
|
|
305
|
+
super(MPOSegmenterWorker, self).__init__()
|
|
245
306
|
self.segmenter = segmenter
|
|
246
307
|
# Value incrementing the batch index. Starts with 0 and is
|
|
247
308
|
# incremented every time :func:`Segmenter.segment_batch` is
|
|
@@ -253,8 +314,10 @@ class CPUSegmenterWorker:
|
|
|
253
314
|
# Shutdown bit tells workers to stop when set to != 0
|
|
254
315
|
self.shutdown = segmenter.mp_shutdown
|
|
255
316
|
# The image data for segmentation
|
|
256
|
-
self.
|
|
257
|
-
#
|
|
317
|
+
self.image_arr_raw = segmenter.mp_image_raw
|
|
318
|
+
# Background data offset
|
|
319
|
+
self.bg_off = segmenter.mp_bg_off_raw
|
|
320
|
+
# Integer output label array
|
|
258
321
|
self.labels_data_raw = segmenter.mp_labels_raw
|
|
259
322
|
# The shape of one image
|
|
260
323
|
self.image_shape = segmenter.image_shape
|
|
@@ -266,10 +329,14 @@ class CPUSegmenterWorker:
|
|
|
266
329
|
# We have to create the numpy-versions of the mp.RawArrays here,
|
|
267
330
|
# otherwise we only get some kind of copy in the new process
|
|
268
331
|
# when we use "spawn" instead of "fork".
|
|
269
|
-
|
|
332
|
+
labels_arr = np.ctypeslib.as_array(self.labels_data_raw).reshape(
|
|
270
333
|
-1, self.image_shape[0], self.image_shape[1])
|
|
271
|
-
|
|
334
|
+
image_arr = np.ctypeslib.as_array(self.image_arr_raw).reshape(
|
|
272
335
|
-1, self.image_shape[0], self.image_shape[1])
|
|
336
|
+
if self.bg_off is not None:
|
|
337
|
+
bg_off_data = np.ctypeslib.as_array(self.bg_off)
|
|
338
|
+
else:
|
|
339
|
+
bg_off_data = None
|
|
273
340
|
|
|
274
341
|
idx = self.sl_start
|
|
275
342
|
itr = 0 # current iteration (incremented when we reach self.sl_stop)
|
|
@@ -283,8 +350,13 @@ class CPUSegmenterWorker:
|
|
|
283
350
|
with self.batch_worker:
|
|
284
351
|
self.batch_worker.value += 1
|
|
285
352
|
else:
|
|
286
|
-
|
|
287
|
-
|
|
353
|
+
if bg_off_data is None:
|
|
354
|
+
bg_off = None
|
|
355
|
+
else:
|
|
356
|
+
bg_off = bg_off_data[idx]
|
|
357
|
+
|
|
358
|
+
labels_arr[idx, :, :] = self.segmenter.segment_single(
|
|
359
|
+
image=image_arr[idx], bg_off=bg_off)
|
|
288
360
|
idx += 1
|
|
289
361
|
elif self.shutdown.value:
|
|
290
362
|
break
|
|
@@ -293,11 +365,11 @@ class CPUSegmenterWorker:
|
|
|
293
365
|
time.sleep(.01)
|
|
294
366
|
|
|
295
367
|
|
|
296
|
-
class
|
|
368
|
+
class MPOSegmenterWorkerProcess(MPOSegmenterWorker, mp_spawn.Process):
|
|
297
369
|
def __init__(self, *args, **kwargs):
|
|
298
|
-
super(
|
|
370
|
+
super(MPOSegmenterWorkerProcess, self).__init__(*args, **kwargs)
|
|
299
371
|
|
|
300
372
|
|
|
301
|
-
class
|
|
373
|
+
class MPOSegmenterWorkerThread(MPOSegmenterWorker, threading.Thread):
|
|
302
374
|
def __init__(self, *args, **kwargs):
|
|
303
|
-
super(
|
|
375
|
+
super(MPOSegmenterWorkerThread, self).__init__(*args, **kwargs)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import scipy.ndimage as ndi
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from .segmenter import Segmenter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class STOSegmenter(Segmenter, abc.ABC):
|
|
12
|
+
hardware_processor = "gpu"
|
|
13
|
+
|
|
14
|
+
def __init__(self,
|
|
15
|
+
*,
|
|
16
|
+
num_workers: int = None,
|
|
17
|
+
kwargs_mask: Dict = None,
|
|
18
|
+
debug: bool = False,
|
|
19
|
+
**kwargs
|
|
20
|
+
):
|
|
21
|
+
"""Segmenter with single thread operation
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
kwargs_mask: dict
|
|
26
|
+
Keyword arguments for mask post-processing (see `process_mask`)
|
|
27
|
+
debug: bool
|
|
28
|
+
Debugging parameters
|
|
29
|
+
kwargs:
|
|
30
|
+
Additional, optional keyword arguments for `segment_algorithm`
|
|
31
|
+
defined in the subclass.
|
|
32
|
+
"""
|
|
33
|
+
if num_workers not in [None, 1]:
|
|
34
|
+
raise ValueError(f"Number of workers must not be larger than 1 "
|
|
35
|
+
f"for GPU segmenter, got '{num_workers}'!")
|
|
36
|
+
super(STOSegmenter, self).__init__(kwargs_mask=kwargs_mask,
|
|
37
|
+
debug=debug,
|
|
38
|
+
**kwargs)
|
|
39
|
+
|
|
40
|
+
def segment_batch(self,
|
|
41
|
+
images: np.ndarray,
|
|
42
|
+
start: int = None,
|
|
43
|
+
stop: int = None,
|
|
44
|
+
bg_off: np.ndarray = None,
|
|
45
|
+
):
|
|
46
|
+
"""Perform batch segmentation of `images`
|
|
47
|
+
|
|
48
|
+
Before segmentation, an optional background offset correction with
|
|
49
|
+
`bg_off` is performed. After segmentation, mask postprocessing is
|
|
50
|
+
performed according to the class definition.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
images: 3d np.ndarray
|
|
55
|
+
The time-series image data. First axis is time.
|
|
56
|
+
start: int
|
|
57
|
+
First index to analyze in `images`
|
|
58
|
+
stop: int
|
|
59
|
+
Index after the last index to analyze in `images`
|
|
60
|
+
bg_off: 1D np.ndarray
|
|
61
|
+
Optional 1D numpy array with background offset
|
|
62
|
+
|
|
63
|
+
Notes
|
|
64
|
+
-----
|
|
65
|
+
- If the segmentation algorithm only accepts background-corrected
|
|
66
|
+
images, then `images` must already be background-corrected,
|
|
67
|
+
except for the optional `bg_off`.
|
|
68
|
+
"""
|
|
69
|
+
if stop is None or start is None:
|
|
70
|
+
start = 0
|
|
71
|
+
stop = len(images)
|
|
72
|
+
|
|
73
|
+
image_slice = images[start:stop]
|
|
74
|
+
segm = self.segment_algorithm_wrapper()
|
|
75
|
+
|
|
76
|
+
if bg_off is not None:
|
|
77
|
+
if not self.requires_background_correction:
|
|
78
|
+
raise ValueError(f"The segmenter {self.__class__.__name__} "
|
|
79
|
+
f"does not employ background correction, "
|
|
80
|
+
f"but the `bg_off` keyword argument was "
|
|
81
|
+
f"passed to `segment_chunk`. Please check "
|
|
82
|
+
f"your analysis pipeline.")
|
|
83
|
+
image_slice = image_slice - bg_off.reshape(-1, 1, 1)
|
|
84
|
+
labels = segm(image_slice)
|
|
85
|
+
|
|
86
|
+
# Make sure we have integer labels and perform mask postprocessing
|
|
87
|
+
if labels.dtype == bool:
|
|
88
|
+
new_labels = np.zeros_like(labels, dtype=np.uint16)
|
|
89
|
+
for ii in range(len(labels)):
|
|
90
|
+
ndi.label(
|
|
91
|
+
input=labels[ii],
|
|
92
|
+
output=new_labels[ii],
|
|
93
|
+
structure=ndi.generate_binary_structure(2, 2))
|
|
94
|
+
labels = new_labels
|
|
95
|
+
|
|
96
|
+
# Perform mask postprocessing
|
|
97
|
+
if self.mask_postprocessing:
|
|
98
|
+
for ii in range(len(labels)):
|
|
99
|
+
labels[ii] = self.process_mask(labels[ii], **self.kwargs_mask)
|
|
100
|
+
|
|
101
|
+
return labels
|
|
102
|
+
|
|
103
|
+
def segment_single(self, image, bg_off: float = None):
|
|
104
|
+
"""This is a convenience-wrapper around `segment_batch`"""
|
|
105
|
+
if bg_off is None:
|
|
106
|
+
bg_off_batch = None
|
|
107
|
+
else:
|
|
108
|
+
bg_off_batch = np.atleast_1d(bg_off)
|
|
109
|
+
images = image[np.newaxis]
|
|
110
|
+
return self.segment_batch(images, bg_off=bg_off_batch)[0]
|
dcnum/write/__init__.py
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
from .deque_writer_thread import DequeWriterThread
|
|
3
3
|
from .queue_collector_thread import EventStash, QueueCollectorThread
|
|
4
4
|
from .writer import (
|
|
5
|
-
HDF5Writer, copy_metadata, create_with_basins,
|
|
5
|
+
HDF5Writer, copy_features, copy_metadata, create_with_basins,
|
|
6
|
+
set_default_filter_kwargs)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import logging
|
|
2
3
|
import pathlib
|
|
3
4
|
import threading
|
|
4
5
|
import time
|
|
5
6
|
|
|
7
|
+
import h5py
|
|
8
|
+
|
|
6
9
|
from .writer import HDF5Writer
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class DequeWriterThread(threading.Thread):
|
|
10
13
|
def __init__(self,
|
|
11
|
-
path_out: pathlib.Path,
|
|
14
|
+
path_out: pathlib.Path | h5py.File,
|
|
12
15
|
dq: collections.deque,
|
|
13
16
|
ds_kwds: dict = None,
|
|
14
17
|
mode: str = "a",
|
|
@@ -24,6 +27,7 @@ class DequeWriterThread(threading.Thread):
|
|
|
24
27
|
using `popleft()`.
|
|
25
28
|
"""
|
|
26
29
|
super(DequeWriterThread, self).__init__(*args, **kwargs)
|
|
30
|
+
self.logger = logging.getLogger("dcnum.write.DequeWriterThread")
|
|
27
31
|
if mode == "w":
|
|
28
32
|
path_out.unlink(missing_ok=True)
|
|
29
33
|
self.writer = HDF5Writer(path_out, mode=mode, ds_kwds=ds_kwds)
|
|
@@ -40,17 +44,21 @@ class DequeWriterThread(threading.Thread):
|
|
|
40
44
|
self.may_stop_loop = True
|
|
41
45
|
|
|
42
46
|
def run(self):
|
|
47
|
+
time_tot = 0
|
|
43
48
|
while True:
|
|
44
49
|
ldq = len(self.dq)
|
|
45
50
|
if self.must_stop_loop:
|
|
46
51
|
break
|
|
47
52
|
elif ldq:
|
|
53
|
+
t0 = time.perf_counter()
|
|
48
54
|
for _ in range(ldq):
|
|
49
55
|
feat, data = self.dq.popleft()
|
|
50
56
|
self.writer.store_feature_chunk(feat=feat, data=data)
|
|
57
|
+
time_tot += time.perf_counter() - t0
|
|
51
58
|
elif self.may_stop_loop:
|
|
52
59
|
break
|
|
53
60
|
else:
|
|
54
61
|
# wait for the next item to arrive
|
|
55
62
|
time.sleep(.1)
|
|
63
|
+
self.logger.info(f"Disk time: {time_tot:.1f}s")
|
|
56
64
|
self.writer.close()
|
|
@@ -245,20 +245,14 @@ class QueueCollectorThread(threading.Thread):
|
|
|
245
245
|
# the events that we just saved.
|
|
246
246
|
indices = stash.indices_for_data
|
|
247
247
|
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
dtype=np.uint8)
|
|
257
|
-
for ii, idx in enumerate(indices):
|
|
258
|
-
imdat[ii] = self.data.image[idx]
|
|
259
|
-
bgdat[ii] = self.data.image_bg[idx]
|
|
260
|
-
self.writer_dq.append(("image", imdat))
|
|
261
|
-
self.writer_dq.append(("image_bg", bgdat))
|
|
248
|
+
# This is the unmapped index from the input HDF5Data instance.
|
|
249
|
+
# Unmapped means that this only enumerates HDF5Data, but since
|
|
250
|
+
# HDF5Data can be mapped, the index does not necessarily enumerate
|
|
251
|
+
# the underlying HDF5 file. Later on, we will have to convert this
|
|
252
|
+
# to the correct "basinmap0" feature
|
|
253
|
+
# (see `DCNumJobRunner.task_enforce_basin_strategy`)
|
|
254
|
+
self.writer_dq.append(("index_unmapped",
|
|
255
|
+
np.array(indices, dtype=np.uint32)))
|
|
262
256
|
|
|
263
257
|
# Write the number of events.
|
|
264
258
|
self.writer_dq.append(("nevents",
|