dcnum 0.25.6__py3-none-any.whl → 0.25.8__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/__init__.py +23 -0
- dcnum/_version.py +2 -2
- dcnum/feat/event_extractor_manager_thread.py +29 -14
- dcnum/feat/feat_background/base.py +19 -11
- dcnum/feat/feat_background/bg_copy.py +4 -0
- dcnum/feat/feat_background/bg_roll_median.py +24 -15
- dcnum/feat/feat_background/bg_sparse_median.py +32 -17
- dcnum/feat/feat_contour/volume.py +2 -2
- dcnum/feat/gate.py +11 -11
- dcnum/feat/queue_event_extractor.py +39 -20
- dcnum/logic/ctrl.py +5 -4
- dcnum/logic/job.py +3 -1
- dcnum/logic/json_encoder.py +9 -0
- dcnum/meta/paths.py +1 -0
- dcnum/meta/ppid.py +4 -2
- dcnum/os_env_st.py +2 -2
- dcnum/read/__init__.py +2 -1
- dcnum/read/cache.py +3 -1
- dcnum/read/const.py +5 -2
- dcnum/read/detect_flicker.py +1 -1
- dcnum/read/hdf5_concat.py +145 -0
- dcnum/read/hdf5_data.py +11 -136
- dcnum/segm/segm_torch/segm_torch_base.py +3 -2
- dcnum/segm/segm_torch/torch_postproc.py +1 -0
- dcnum/segm/segm_torch/torch_preproc.py +1 -0
- dcnum/segm/segmenter.py +31 -20
- dcnum/segm/segmenter_manager_thread.py +19 -12
- dcnum/segm/segmenter_mpo.py +4 -4
- dcnum/segm/segmenter_sto.py +2 -2
- dcnum/write/queue_collector_thread.py +35 -18
- dcnum/write/writer.py +4 -3
- {dcnum-0.25.6.dist-info → dcnum-0.25.8.dist-info}/METADATA +4 -3
- dcnum-0.25.8.dist-info/RECORD +58 -0
- {dcnum-0.25.6.dist-info → dcnum-0.25.8.dist-info}/WHEEL +1 -1
- {dcnum-0.25.6.dist-info → dcnum-0.25.8.dist-info/licenses}/LICENSE +1 -1
- dcnum-0.25.6.dist-info/RECORD +0 -57
- {dcnum-0.25.6.dist-info → dcnum-0.25.8.dist-info}/top_level.txt +0 -0
dcnum/__init__.py
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
|
+
"""Base library for deformability cytometry postprocessing
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023 Paul Müller
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
"""
|
|
1
24
|
# flake8: noqa: F401
|
|
2
25
|
from ._version import __version__, __version_tuple__
|
dcnum/_version.py
CHANGED
|
@@ -35,8 +35,8 @@ class EventExtractorManagerThread(threading.Thread):
|
|
|
35
35
|
with segmenter"), so that the segmenter can compute a new
|
|
36
36
|
chunk of labels.
|
|
37
37
|
slot_chunks:
|
|
38
|
-
For each slot in
|
|
39
|
-
on which chunk in
|
|
38
|
+
For each slot in ``slot_states``, this shared array defines
|
|
39
|
+
on which chunk in ``image_data`` the segmentation took place.
|
|
40
40
|
fe_kwargs:
|
|
41
41
|
Feature extraction keyword arguments. See
|
|
42
42
|
:func:`.EventExtractor.get_init_kwargs` for more information.
|
|
@@ -47,36 +47,51 @@ class EventExtractorManagerThread(threading.Thread):
|
|
|
47
47
|
fills up, we take a break.
|
|
48
48
|
debug:
|
|
49
49
|
Whether to run in debugging mode which means only one
|
|
50
|
-
event extraction thread (
|
|
50
|
+
event extraction thread (``num_workers`` has no effect).
|
|
51
51
|
"""
|
|
52
52
|
super(EventExtractorManagerThread, self).__init__(
|
|
53
53
|
name="EventExtractorManager", *args, **kwargs)
|
|
54
54
|
self.logger = logging.getLogger(
|
|
55
55
|
"dcnum.feat.EventExtractorManagerThread")
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
self.fe_kwargs = fe_kwargs
|
|
58
|
-
|
|
58
|
+
"""Keyword arguments
|
|
59
|
+
for :class:`event_extractor_manager_thread.py.QueueEventExtractor`
|
|
60
|
+
instances"""
|
|
61
|
+
|
|
59
62
|
self.data = fe_kwargs["data"]
|
|
60
|
-
|
|
63
|
+
"""Data instance"""
|
|
64
|
+
|
|
61
65
|
self.slot_states = slot_states
|
|
62
|
-
|
|
66
|
+
"""States of the segmenter-extractor pipeline slots"""
|
|
67
|
+
|
|
63
68
|
self.slot_chunks = slot_chunks
|
|
64
|
-
|
|
69
|
+
"""Chunk indices corresponding to ``slot_states``
|
|
70
|
+
"""
|
|
71
|
+
|
|
65
72
|
self.num_workers = 1 if debug else num_workers
|
|
66
|
-
|
|
73
|
+
"""Number of workers"""
|
|
74
|
+
|
|
67
75
|
self.raw_queue = self.fe_kwargs["raw_queue"]
|
|
68
|
-
|
|
76
|
+
"""Queue for sending chunks and label indices to the workers"""
|
|
77
|
+
|
|
69
78
|
self.labels_list = labels_list
|
|
70
|
-
|
|
79
|
+
"""List of chunk labels corresponding to ``slot_states``
|
|
80
|
+
"""
|
|
81
|
+
|
|
71
82
|
self.label_array = np.ctypeslib.as_array(
|
|
72
83
|
self.fe_kwargs["label_array"]).reshape(
|
|
73
84
|
self.data.image.chunk_shape)
|
|
74
|
-
|
|
85
|
+
"""Shared labeling array"""
|
|
86
|
+
|
|
75
87
|
self.writer_dq = writer_dq
|
|
76
|
-
|
|
88
|
+
"""Writer deque to monitor"""
|
|
89
|
+
|
|
77
90
|
self.t_count = 0
|
|
78
|
-
|
|
91
|
+
"""Time counter for feature extraction"""
|
|
92
|
+
|
|
79
93
|
self.debug = debug
|
|
94
|
+
"""Whether debugging is enabled"""
|
|
80
95
|
|
|
81
96
|
def run(self):
|
|
82
97
|
# Initialize all workers
|
|
@@ -21,7 +21,7 @@ mp_spawn = mp.get_context('spawn')
|
|
|
21
21
|
class Background(abc.ABC):
|
|
22
22
|
def __init__(self, input_data, output_path, compress=True, num_cpus=None,
|
|
23
23
|
**kwargs):
|
|
24
|
-
"""
|
|
24
|
+
"""Base class for background computation
|
|
25
25
|
|
|
26
26
|
Parameters
|
|
27
27
|
----------
|
|
@@ -56,28 +56,35 @@ class Background(abc.ABC):
|
|
|
56
56
|
# Using spec is not really necessary here, because kwargs are
|
|
57
57
|
# fully populated for background computation, but this might change.
|
|
58
58
|
spec = inspect.getfullargspec(self.check_user_kwargs)
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
self.kwargs = spec.kwonlydefaults or {}
|
|
61
|
+
"""background keyword arguments"""
|
|
61
62
|
self.kwargs.update(kwargs)
|
|
62
63
|
|
|
63
64
|
if num_cpus is None:
|
|
64
65
|
num_cpus = mp_spawn.cpu_count()
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
self.num_cpus = num_cpus
|
|
68
|
+
"""number of CPUs used"""
|
|
67
69
|
|
|
68
|
-
#: number of images in the input data
|
|
69
70
|
self.image_count = None
|
|
70
|
-
|
|
71
|
+
"""number of images in the input data"""
|
|
72
|
+
|
|
71
73
|
self.image_proc = mp_spawn.Value("d", 0)
|
|
74
|
+
"""fraction images that have been processed"""
|
|
72
75
|
|
|
73
|
-
#: HDF5Data instance for input data
|
|
74
76
|
self.hdin = None
|
|
75
|
-
|
|
77
|
+
"""HDF5Data instance for input data"""
|
|
78
|
+
|
|
76
79
|
self.h5in = None
|
|
77
|
-
|
|
80
|
+
"""input h5py.File"""
|
|
81
|
+
|
|
78
82
|
self.h5out = None
|
|
79
|
-
|
|
83
|
+
"""output h5py.File"""
|
|
84
|
+
|
|
80
85
|
self.paths_ref = []
|
|
86
|
+
"""reference paths for logging to the output .rtdc file"""
|
|
87
|
+
|
|
81
88
|
# Check whether user passed an array or a path
|
|
82
89
|
if isinstance(input_data, pathlib.Path):
|
|
83
90
|
if str(input_data.resolve()) == str(output_path.resolve()):
|
|
@@ -96,10 +103,11 @@ class Background(abc.ABC):
|
|
|
96
103
|
else:
|
|
97
104
|
self.input_data = input_data
|
|
98
105
|
|
|
99
|
-
#: shape of event images
|
|
100
106
|
self.image_shape = self.input_data[0].shape
|
|
101
|
-
|
|
107
|
+
"""shape of event images"""
|
|
108
|
+
|
|
102
109
|
self.image_count = len(self.input_data)
|
|
110
|
+
"""total number of events"""
|
|
103
111
|
|
|
104
112
|
if self.h5out is None:
|
|
105
113
|
if not output_path.exists():
|
|
@@ -42,7 +42,7 @@ class BackgroundRollMed(Background):
|
|
|
42
42
|
batch_size: int
|
|
43
43
|
Number of events to process at the same time. Increasing this
|
|
44
44
|
number much more than two orders of magnitude larger than
|
|
45
|
-
|
|
45
|
+
``kernel_size`` will not increase computation speed. Larger
|
|
46
46
|
values lead to a higher memory consumption.
|
|
47
47
|
compress: bool
|
|
48
48
|
Whether to compress background data. Set this to False
|
|
@@ -64,39 +64,47 @@ class BackgroundRollMed(Background):
|
|
|
64
64
|
f"size {len(self.input_data)} is larger than the "
|
|
65
65
|
f"kernel size {kernel_size}!")
|
|
66
66
|
|
|
67
|
-
#: kernel size used for median filtering
|
|
68
67
|
self.kernel_size = kernel_size
|
|
69
|
-
|
|
68
|
+
"""kernel size used for median filtering"""
|
|
69
|
+
|
|
70
70
|
self.batch_size = batch_size
|
|
71
|
+
"""number of events processed at once"""
|
|
71
72
|
|
|
72
|
-
#: mp.RawArray for temporary batch input data
|
|
73
73
|
self.shared_input_raw = mp_spawn.RawArray(
|
|
74
74
|
np.ctypeslib.ctypes.c_uint8,
|
|
75
75
|
int(np.prod(self.image_shape)) * (batch_size + kernel_size))
|
|
76
|
-
|
|
76
|
+
"""mp.RawArray for temporary batch input data"""
|
|
77
|
+
|
|
77
78
|
self.shared_output_raw = mp_spawn.RawArray(
|
|
78
79
|
np.ctypeslib.ctypes.c_uint8,
|
|
79
80
|
int(np.prod(self.image_shape)) * batch_size)
|
|
81
|
+
"""mp.RawArray for temporary batch output data"""
|
|
82
|
+
|
|
80
83
|
# Convert the RawArray to something we can write to fast
|
|
81
84
|
# (similar to memoryview, but without having to cast) using
|
|
82
85
|
# np.ctypeslib.as_array. See discussion in
|
|
83
86
|
# https://stackoverflow.com/questions/37705974
|
|
84
|
-
#: numpy array reshaped view on `self.shared_input_raw` with
|
|
85
|
-
#: first axis enumerating the events
|
|
86
87
|
self.shared_input = np.ctypeslib.as_array(
|
|
87
88
|
self.shared_input_raw).reshape(batch_size + kernel_size, -1)
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
"""numpy array reshaped view on `self.shared_input_raw`.
|
|
90
|
+
The first axis enumerating the events
|
|
91
|
+
"""
|
|
92
|
+
|
|
90
93
|
self.shared_output = np.ctypeslib.as_array(
|
|
91
94
|
self.shared_output_raw).reshape(batch_size, -1)
|
|
92
|
-
|
|
95
|
+
"""numpy array reshaped view on `self.shared_output_raw`.
|
|
96
|
+
The first axis enumerating the events
|
|
97
|
+
"""
|
|
98
|
+
|
|
93
99
|
self.current_batch = 0
|
|
100
|
+
"""current batch index (see `self.process` and `process_next_batch`)"""
|
|
94
101
|
|
|
95
|
-
#: counter tracking process of workers
|
|
96
102
|
self.worker_counter = mp_spawn.Value("l", 0)
|
|
97
|
-
|
|
103
|
+
"""counter tracking process of workers"""
|
|
104
|
+
|
|
98
105
|
self.queue = mp_spawn.Queue()
|
|
99
|
-
|
|
106
|
+
"""queue for median computation jobs"""
|
|
107
|
+
|
|
100
108
|
self.workers = [WorkerRollMed(self.queue,
|
|
101
109
|
self.worker_counter,
|
|
102
110
|
self.shared_input_raw,
|
|
@@ -104,6 +112,7 @@ class BackgroundRollMed(Background):
|
|
|
104
112
|
self.batch_size,
|
|
105
113
|
self.kernel_size)
|
|
106
114
|
for _ in range(self.num_cpus)]
|
|
115
|
+
"""list of workers (processes)"""
|
|
107
116
|
[w.start() for w in self.workers]
|
|
108
117
|
|
|
109
118
|
def __enter__(self):
|
|
@@ -131,7 +140,7 @@ class BackgroundRollMed(Background):
|
|
|
131
140
|
batch_size: int
|
|
132
141
|
Number of events to process at the same time. Increasing this
|
|
133
142
|
number much more than two orders of magnitude larger than
|
|
134
|
-
|
|
143
|
+
``kernel_size`` will not increase computation speed. Larger
|
|
135
144
|
values lead to a higher memory consumption.
|
|
136
145
|
"""
|
|
137
146
|
assert kernel_size > 0, "kernel size must be positive number"
|
|
@@ -283,7 +292,7 @@ def compute_median_for_slice(shared_input, shared_output, kernel_size,
|
|
|
283
292
|
in the original image, batch_size + kernel_size events are
|
|
284
293
|
stored in this array one after another in a row.
|
|
285
294
|
The total size of this array is
|
|
286
|
-
|
|
295
|
+
``batch_size * kernel_size * number_of_pixels_in_the_image``.
|
|
287
296
|
shared_output: multiprocessing.RawArray
|
|
288
297
|
Used for storing the result. Note that the last `kernel_size`
|
|
289
298
|
elements for each pixel in this output array are junk data
|
|
@@ -21,8 +21,8 @@ class BackgroundSparseMed(Background):
|
|
|
21
21
|
|
|
22
22
|
In contrast to the rolling median background correction,
|
|
23
23
|
this algorithm only computes the background image every
|
|
24
|
-
|
|
25
|
-
200 frames instead of 100 frames).
|
|
24
|
+
``split_time`` seconds, but with a larger window (default kernel
|
|
25
|
+
size is 200 frames instead of 100 frames).
|
|
26
26
|
|
|
27
27
|
1. At time stamps every `split_time` seconds, a background image is
|
|
28
28
|
computed, resulting in a background series.
|
|
@@ -103,16 +103,20 @@ class BackgroundSparseMed(Background):
|
|
|
103
103
|
f"size {len(self.input_data)}. Setting it to input data size!")
|
|
104
104
|
kernel_size = len(self.input_data)
|
|
105
105
|
|
|
106
|
-
#: kernel size used for median filtering
|
|
107
106
|
self.kernel_size = kernel_size
|
|
108
|
-
|
|
107
|
+
"""kernel size used for median filtering"""
|
|
108
|
+
|
|
109
109
|
self.split_time = split_time
|
|
110
|
-
|
|
110
|
+
"""time between background images in the background series"""
|
|
111
|
+
|
|
111
112
|
self.thresh_cleansing = thresh_cleansing
|
|
112
|
-
|
|
113
|
+
"""cleansing threshold factor"""
|
|
114
|
+
|
|
113
115
|
self.frac_cleansing = frac_cleansing
|
|
114
|
-
|
|
116
|
+
"""keep at least this many background images from the series"""
|
|
117
|
+
|
|
115
118
|
self.offset_correction = offset_correction
|
|
119
|
+
"""offset/flickering correction"""
|
|
116
120
|
|
|
117
121
|
# time axis
|
|
118
122
|
self.time = None
|
|
@@ -142,48 +146,59 @@ class BackgroundSparseMed(Background):
|
|
|
142
146
|
self.time = np.linspace(0, dur, self.image_count,
|
|
143
147
|
endpoint=True)
|
|
144
148
|
|
|
145
|
-
#: duration of the measurement
|
|
146
149
|
self.duration = self.time[-1] - self.time[0]
|
|
150
|
+
"""duration of the measurement"""
|
|
147
151
|
|
|
148
152
|
self.step_times = np.arange(0, self.duration, self.split_time)
|
|
149
|
-
|
|
153
|
+
|
|
150
154
|
self.bg_images = np.zeros((self.step_times.size,
|
|
151
155
|
self.image_shape[0],
|
|
152
156
|
self.image_shape[1]),
|
|
153
157
|
dtype=np.uint8)
|
|
158
|
+
"""array containing all background images"""
|
|
154
159
|
|
|
155
|
-
#: mp.RawArray for temporary batch input data
|
|
156
160
|
self.shared_input_raw = mp_spawn.RawArray(
|
|
157
161
|
np.ctypeslib.ctypes.c_uint8,
|
|
158
162
|
int(np.prod(self.image_shape)) * kernel_size)
|
|
159
|
-
|
|
163
|
+
"""mp.RawArray for temporary batch input data"""
|
|
164
|
+
|
|
160
165
|
self.shared_output_raw = mp_spawn.RawArray(
|
|
161
166
|
np.ctypeslib.ctypes.c_uint8,
|
|
162
167
|
int(np.prod(self.image_shape)))
|
|
168
|
+
"""mp.RawArray for the median background image"""
|
|
169
|
+
|
|
163
170
|
# Convert the RawArray to something we can write to fast
|
|
164
171
|
# (similar to memoryview, but without having to cast) using
|
|
165
172
|
# np.ctypeslib.as_array. See discussion in
|
|
166
173
|
# https://stackoverflow.com/questions/37705974
|
|
167
|
-
#: numpy array reshaped view on `self.shared_input_raw` with
|
|
168
|
-
#: first axis enumerating the events
|
|
169
174
|
self.shared_input = np.ctypeslib.as_array(
|
|
170
175
|
self.shared_input_raw).reshape(kernel_size, -1)
|
|
176
|
+
"""numpy array reshaped view on `self.shared_input_raw`.
|
|
177
|
+
The First axis enumerating the events
|
|
178
|
+
"""
|
|
179
|
+
|
|
171
180
|
self.shared_output = np.ctypeslib.as_array(
|
|
172
181
|
self.shared_output_raw).reshape(self.image_shape)
|
|
173
|
-
|
|
182
|
+
"""numpy array reshaped view on `self.shared_output_raw`.
|
|
183
|
+
The First axis enumerating the events
|
|
184
|
+
"""
|
|
185
|
+
|
|
174
186
|
self.pool = mp_spawn.Pool(processes=self.num_cpus)
|
|
187
|
+
"""multiprocessing pool for parallel processing"""
|
|
175
188
|
|
|
176
|
-
#: counter tracking process of workers
|
|
177
189
|
self.worker_counter = mp_spawn.Value("l", 0)
|
|
178
|
-
|
|
190
|
+
"""counter tracking process of workers"""
|
|
191
|
+
|
|
179
192
|
self.queue = mp_spawn.Queue()
|
|
180
|
-
|
|
193
|
+
"""queue for median computation jobs"""
|
|
194
|
+
|
|
181
195
|
self.workers = [WorkerSparseMed(self.queue,
|
|
182
196
|
self.worker_counter,
|
|
183
197
|
self.shared_input_raw,
|
|
184
198
|
self.shared_output_raw,
|
|
185
199
|
self.kernel_size)
|
|
186
200
|
for _ in range(self.num_cpus)]
|
|
201
|
+
"""list of workers (processes)"""
|
|
187
202
|
[w.start() for w in self.workers]
|
|
188
203
|
|
|
189
204
|
def __enter__(self):
|
|
@@ -35,7 +35,7 @@ def volume_from_contours(
|
|
|
35
35
|
average is then used.
|
|
36
36
|
|
|
37
37
|
The volume is computed radially from the center position
|
|
38
|
-
given by (
|
|
38
|
+
given by (``pos_x``, ``pos_y``). For sufficiently smooth contours,
|
|
39
39
|
such as densely sampled ellipses, the center position does not
|
|
40
40
|
play an important role. For contours that are given on a coarse
|
|
41
41
|
grid, as is the case for deformability cytometry, the center position
|
|
@@ -111,7 +111,7 @@ def vol_revolve(r, z, point_scale=1.):
|
|
|
111
111
|
V = \frac{h \cdot \pi}{3} \cdot (R^2 + R \cdot r + r^2)
|
|
112
112
|
|
|
113
113
|
Where :math:`h` is the height of the cone and :math:`r` and
|
|
114
|
-
|
|
114
|
+
``R`` are the smaller and larger radii of the truncated cone.
|
|
115
115
|
|
|
116
116
|
Each line segment of the contour resembles one truncated cone. If
|
|
117
117
|
the z-step is positive (counter-clockwise contour), then the
|
dcnum/feat/gate.py
CHANGED
|
@@ -9,8 +9,8 @@ from ..meta.ppid import kwargs_to_ppid, ppid_to_kwargs
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Gate:
|
|
12
|
-
#: the default value for `size_thresh_mask` if not given as kwarg
|
|
13
12
|
_default_size_thresh_mask = 10
|
|
13
|
+
"""the default value for `size_thresh_mask` if not given as kwarg"""
|
|
14
14
|
|
|
15
15
|
def __init__(self, data, *,
|
|
16
16
|
online_gates: bool = False,
|
|
@@ -19,7 +19,7 @@ class Gate:
|
|
|
19
19
|
|
|
20
20
|
Parameters
|
|
21
21
|
----------
|
|
22
|
-
data: .HDF5Data
|
|
22
|
+
data: .hdf5_data.HDF5Data
|
|
23
23
|
dcnum data instance
|
|
24
24
|
online_gates: bool
|
|
25
25
|
set to True to enable gating with "online" gates stored
|
|
@@ -27,14 +27,14 @@ class Gate:
|
|
|
27
27
|
deformability cytometry before writing data to disk during
|
|
28
28
|
a measurement
|
|
29
29
|
size_thresh_mask: int
|
|
30
|
-
Only masks with more pixels than
|
|
30
|
+
Only masks with more pixels than ``size_thresh_mask`` are
|
|
31
31
|
considered to be a valid event; Originally, the
|
|
32
|
-
|
|
32
|
+
``bin area min / trig_thresh`` value defaulted to 200 which is
|
|
33
33
|
too large; defaults to 10 or the original value in case
|
|
34
|
-
|
|
34
|
+
``online_gates`` is set.
|
|
35
35
|
"""
|
|
36
|
-
#: box gating (value range for each feature)
|
|
37
36
|
self.box_gates = {}
|
|
37
|
+
"""box gating (value range for each feature)"""
|
|
38
38
|
|
|
39
39
|
if online_gates:
|
|
40
40
|
# Deal with online gates.
|
|
@@ -46,13 +46,13 @@ class Gate:
|
|
|
46
46
|
size_thresh_mask = data.meta_nest.get(
|
|
47
47
|
"online_contour", {}).get("bin area min")
|
|
48
48
|
|
|
49
|
-
#: gating keyword arguments
|
|
50
49
|
self.kwargs = {
|
|
51
50
|
"online_gates": online_gates,
|
|
52
51
|
# Set the size threshold, defaulting to `_default_size_thresh_mask`
|
|
53
52
|
"size_thresh_mask":
|
|
54
53
|
size_thresh_mask or self._default_size_thresh_mask
|
|
55
54
|
}
|
|
55
|
+
"""gating keyword arguments"""
|
|
56
56
|
|
|
57
57
|
def _extract_online_gates(self, data):
|
|
58
58
|
ogates = {}
|
|
@@ -168,10 +168,10 @@ class Gate:
|
|
|
168
168
|
data: numbers.Number | np.ndarray):
|
|
169
169
|
"""Return boolean indicating whether `data` value is in box gate
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
for
|
|
173
|
-
or a boolean array is returned, depending on the type of
|
|
174
|
-
Not that
|
|
171
|
+
``data`` may be a number or an array. If no box filter is defined
|
|
172
|
+
for ``feat``, True is always returned. Otherwise, either a boolean
|
|
173
|
+
or a boolean array is returned, depending on the type of ``data``.
|
|
174
|
+
Not that ``np.logical_and`` can deal with mixed argument types
|
|
175
175
|
(scalar and array).
|
|
176
176
|
"""
|
|
177
177
|
bound_lo, bound_up = self.box_gates[feat]
|
|
@@ -12,7 +12,7 @@ import numpy as np
|
|
|
12
12
|
|
|
13
13
|
from ..os_env_st import RequestSingleThreaded, confirm_single_threaded
|
|
14
14
|
from ..meta.ppid import kwargs_to_ppid, ppid_to_kwargs
|
|
15
|
-
from ..read import HDF5Data
|
|
15
|
+
from ..read.hdf5_data import HDF5Data
|
|
16
16
|
|
|
17
17
|
from .feat_brightness import brightness_features
|
|
18
18
|
from .feat_contour import moments_based_features, volume_from_contours
|
|
@@ -48,9 +48,9 @@ class QueueEventExtractor:
|
|
|
48
48
|
|
|
49
49
|
Parameters
|
|
50
50
|
----------
|
|
51
|
-
data: HDF5Data
|
|
51
|
+
data: .hdf5_data.HDF5Data
|
|
52
52
|
Data source.
|
|
53
|
-
gate: Gate
|
|
53
|
+
gate: .gate.Gate
|
|
54
54
|
Gating rules.
|
|
55
55
|
raw_queue:
|
|
56
56
|
Queue from which the worker obtains the chunks and
|
|
@@ -84,41 +84,57 @@ class QueueEventExtractor:
|
|
|
84
84
|
The index to increment values in `worker_monitor`
|
|
85
85
|
"""
|
|
86
86
|
super(QueueEventExtractor, self).__init__(*args, **kwargs)
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
self.worker_index = worker_index or 0
|
|
89
|
-
|
|
89
|
+
"""Worker index for populating"""
|
|
90
|
+
|
|
90
91
|
self.data = data
|
|
91
|
-
|
|
92
|
+
"""Data instance"""
|
|
93
|
+
|
|
92
94
|
self.gate = gate
|
|
93
|
-
|
|
95
|
+
"""Gating information"""
|
|
96
|
+
|
|
94
97
|
self.raw_queue = raw_queue
|
|
95
|
-
|
|
98
|
+
"""queue containing sub-indices for ``label_array``"""
|
|
99
|
+
|
|
96
100
|
self.event_queue = event_queue
|
|
97
|
-
|
|
101
|
+
"""queue with event-wise feature dictionaries"""
|
|
102
|
+
|
|
98
103
|
self.log_queue = log_queue
|
|
99
|
-
|
|
104
|
+
"""queue for logging"""
|
|
105
|
+
|
|
100
106
|
self.invalid_mask_counter = invalid_mask_counter
|
|
101
|
-
|
|
107
|
+
"""invalid mask counter"""
|
|
108
|
+
|
|
102
109
|
self.worker_monitor = worker_monitor
|
|
110
|
+
"""worker busy counter"""
|
|
111
|
+
|
|
103
112
|
# Logging needs to be set up after `start` is called, otherwise
|
|
104
113
|
# it looks like we have the same PID as the parent process. We
|
|
105
114
|
# are setting up logging in `run`.
|
|
106
115
|
self.logger = None
|
|
107
116
|
self.log_level = log_level or logging.getLogger("dcnum").level
|
|
108
|
-
|
|
109
|
-
#: events per frame is written.
|
|
117
|
+
|
|
110
118
|
self.feat_nevents = feat_nevents
|
|
111
|
-
|
|
119
|
+
"""Number of events per frame
|
|
120
|
+
Shared array of length `len(data)` into which the number of
|
|
121
|
+
events per frame is written.
|
|
122
|
+
"""
|
|
123
|
+
|
|
112
124
|
self.label_array = label_array
|
|
113
|
-
|
|
125
|
+
"""Shared array containing the labels of one chunk from `data`."""
|
|
126
|
+
|
|
114
127
|
self.finalize_extraction = finalize_extraction
|
|
128
|
+
"""Set to True to let worker join when `raw_queue` is empty."""
|
|
129
|
+
|
|
115
130
|
# Keyword arguments for data extraction
|
|
116
131
|
if extract_kwargs is None:
|
|
117
132
|
extract_kwargs = {}
|
|
118
133
|
extract_kwargs.setdefault("brightness", True)
|
|
119
134
|
extract_kwargs.setdefault("haralick", True)
|
|
120
|
-
|
|
135
|
+
|
|
121
136
|
self.extract_kwargs = extract_kwargs
|
|
137
|
+
"""Feature extraction keyword arguments."""
|
|
122
138
|
|
|
123
139
|
@staticmethod
|
|
124
140
|
def get_init_kwargs(data: HDF5Data,
|
|
@@ -127,18 +143,19 @@ class QueueEventExtractor:
|
|
|
127
143
|
log_queue: mp.Queue,
|
|
128
144
|
log_level: int = None,
|
|
129
145
|
):
|
|
130
|
-
"""Get initialization arguments for :
|
|
146
|
+
"""Get initialization arguments for :class:`.QueueEventExtractor`
|
|
131
147
|
|
|
132
148
|
This method was created for convenience reasons:
|
|
149
|
+
|
|
133
150
|
- It makes sure that the order of arguments is correct, since it
|
|
134
151
|
is implemented in the same class.
|
|
135
152
|
- It simplifies testing.
|
|
136
153
|
|
|
137
154
|
Parameters
|
|
138
155
|
----------
|
|
139
|
-
data: HDF5Data
|
|
156
|
+
data: .hdf5_data.HDF5Data
|
|
140
157
|
Input data
|
|
141
|
-
gate:
|
|
158
|
+
gate: .gate.Gate
|
|
142
159
|
Gating class to use
|
|
143
160
|
num_extractors: int
|
|
144
161
|
Number of extractors that will be used
|
|
@@ -330,9 +347,11 @@ class QueueEventExtractor:
|
|
|
330
347
|
self.worker_monitor[self.worker_index] = 0
|
|
331
348
|
# Don't wait for these two queues when joining workers
|
|
332
349
|
self.raw_queue.cancel_join_thread()
|
|
333
|
-
|
|
350
|
+
|
|
334
351
|
self.logger = logging.getLogger(
|
|
335
352
|
f"dcnum.feat.EventExtractor.{os.getpid()}")
|
|
353
|
+
"""logger that sends all logs to `self.log_queue`"""
|
|
354
|
+
|
|
336
355
|
self.logger.setLevel(self.log_level)
|
|
337
356
|
# Clear any handlers that might be set for this logger. This is
|
|
338
357
|
# important for the case when we are an instance of
|
dcnum/logic/ctrl.py
CHANGED
|
@@ -39,8 +39,6 @@ from .json_encoder import ExtendedJSONEncoder
|
|
|
39
39
|
# queues and threads and would end up with race conditions otherwise.
|
|
40
40
|
mp_spawn = mp.get_context("spawn")
|
|
41
41
|
|
|
42
|
-
#: valid states for a job runnter. The states must be in logical ordern,
|
|
43
|
-
#: not in alphabetical order.
|
|
44
42
|
valid_states = [
|
|
45
43
|
"created",
|
|
46
44
|
"init",
|
|
@@ -52,6 +50,9 @@ valid_states = [
|
|
|
52
50
|
"done",
|
|
53
51
|
"error",
|
|
54
52
|
]
|
|
53
|
+
"""Valid states for a `DCNumJobRunner`.
|
|
54
|
+
The states must be in logical order, not in alphabetical order.
|
|
55
|
+
"""
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
class DCNumJobRunner(threading.Thread):
|
|
@@ -59,11 +60,11 @@ class DCNumJobRunner(threading.Thread):
|
|
|
59
60
|
job: DCNumPipelineJob,
|
|
60
61
|
tmp_suffix: str = None,
|
|
61
62
|
*args, **kwargs):
|
|
62
|
-
"""Run a pipeline as defined by a :class
|
|
63
|
+
"""Run a pipeline as defined by a :class:`.job.DCNumPipelineJob`
|
|
63
64
|
|
|
64
65
|
Parameters
|
|
65
66
|
----------
|
|
66
|
-
job: DCNumPipelineJob
|
|
67
|
+
job: .job.DCNumPipelineJob
|
|
67
68
|
pipeline job to run
|
|
68
69
|
tmp_suffix: str
|
|
69
70
|
optional unique string for creating temporary files
|
dcnum/logic/job.py
CHANGED
|
@@ -67,6 +67,7 @@ class DCNumPipelineJob:
|
|
|
67
67
|
strategy on how to handle event data; In principle, not all
|
|
68
68
|
events have to be stored in the output file if basins are
|
|
69
69
|
defined, linking back to the original file.
|
|
70
|
+
|
|
70
71
|
- You can "drain" all basins which means that the output file
|
|
71
72
|
will contain all features, but will also be very big.
|
|
72
73
|
- You can "tap" the basins, including the input file, which means
|
|
@@ -89,8 +90,9 @@ class DCNumPipelineJob:
|
|
|
89
90
|
else:
|
|
90
91
|
basin_strategy = "tap"
|
|
91
92
|
|
|
92
|
-
#: initialize keyword arguments for this job
|
|
93
93
|
self.kwargs = {}
|
|
94
|
+
"""initialize keyword arguments for this job"""
|
|
95
|
+
|
|
94
96
|
spec = inspect.getfullargspec(DCNumPipelineJob.__init__)
|
|
95
97
|
locs = locals()
|
|
96
98
|
for arg in spec.args:
|