dcnum 0.14.0__py3-none-any.whl → 0.15.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.

Potentially problematic release.


This version of dcnum might be problematic. Click here for more details.

dcnum/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.14.0'
16
- __version_tuple__ = version_tuple = (0, 14, 0)
15
+ __version__ = version = '0.15.0'
16
+ __version_tuple__ = version_tuple = (0, 15, 0)
dcnum/feat/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # flake8: noqa: F401
2
+ """Feature computation"""
2
3
  from . import feat_background, feat_brightness, feat_moments, feat_texture
3
4
  from .event_extractor_manager_thread import EventExtractorManagerThread
4
5
  from .queue_event_extractor import (
@@ -1,3 +1,4 @@
1
+ """Feature computation: managing event extraction threads"""
1
2
  import logging
2
3
  import multiprocessing as mp
3
4
  import threading
@@ -45,6 +46,8 @@ class EventExtractorManagerThread(threading.Thread):
45
46
  """
46
47
  super(EventExtractorManagerThread, self).__init__(
47
48
  name="EventExtractorManager", *args, **kwargs)
49
+ if debug:
50
+ fe_kwargs["close_queues"] = False
48
51
  self.logger = logging.getLogger(
49
52
  "dcnum.feat.EventExtractorManagerThread")
50
53
  #: Keyword arguments for class:`.EventExtractor`
@@ -1,16 +1,6 @@
1
1
  # flake8: noqa: F401
2
- import functools
3
-
4
- from .base import Background
2
+ """Feature computation: background image data from image data"""
3
+ from .base import Background, get_available_background_methods
5
4
  # Background methods are registered by importing them here.
6
5
  from .bg_roll_median import BackgroundRollMed
7
6
  from .bg_sparse_median import BackgroundSparseMed
8
-
9
-
10
- @functools.cache
11
- def get_available_background_methods():
12
- """Return dictionary of background computation methods"""
13
- methods = {}
14
- for cls in Background.__subclasses__():
15
- methods[cls.key()] = cls
16
- return methods
@@ -1,8 +1,10 @@
1
1
  import abc
2
+ import functools
2
3
  import inspect
3
4
  import multiprocessing as mp
4
5
  import pathlib
5
6
  import uuid
7
+ import warnings
6
8
 
7
9
  import h5py
8
10
  import hdf5plugin
@@ -131,37 +133,6 @@ class Background(abc.ABC):
131
133
  if self.h5in is not self.h5out and self.h5out is not None:
132
134
  self.h5out.close()
133
135
 
134
- @staticmethod
135
- def get_kwargs_from_ppid(bg_ppid):
136
- """Return keyword arguments for any subclass from a PPID string"""
137
- name, pp_check_user_kwargs = bg_ppid.split(":")
138
- for cls in Background.__subclasses__():
139
- if cls.key() == name:
140
- break
141
- else:
142
- raise ValueError(
143
- f"Could not find background computation method '{name}'!")
144
- kwargs = ppid.ppid_to_kwargs(cls=cls,
145
- method="check_user_kwargs",
146
- ppid=pp_check_user_kwargs)
147
- return kwargs
148
-
149
- @classmethod
150
- def get_ppid_from_kwargs(cls, kwargs):
151
- """Return the PPID based on given keyword arguments for a subclass"""
152
- key = cls.key()
153
- cback = ppid.kwargs_to_ppid(cls, "check_user_kwargs", kwargs)
154
- return ":".join([key, cback])
155
-
156
- @classmethod
157
- def key(cls):
158
- if cls is Background:
159
- raise ValueError("Cannot get `key` for `Background` base class!")
160
- key = cls.__name__.lower()
161
- if key.startswith("background"):
162
- key = key[10:]
163
- return key
164
-
165
136
  @abc.abstractmethod
166
137
  def check_user_kwargs(self, **kwargs):
167
138
  """Implement this to check the kwargs during init"""
@@ -186,11 +157,42 @@ class Background(abc.ABC):
186
157
 
187
158
  k=100^b=10000
188
159
  """
189
- return self.get_ppid_from_kwargs(self.kwargs)
160
+ return self.get_ppid_from_ppkw(self.kwargs)
161
+
162
+ @classmethod
163
+ def get_ppid_code(cls):
164
+ if cls is Background:
165
+ raise ValueError("Cannot get `key` for `Background` base class!")
166
+ key = cls.__name__.lower()
167
+ if key.startswith("background"):
168
+ key = key[10:]
169
+ return key
170
+
171
+ @classmethod
172
+ def get_ppid_from_ppkw(cls, kwargs):
173
+ """Return the PPID based on given keyword arguments for a subclass"""
174
+ code = cls.get_ppid_code()
175
+ cback = ppid.kwargs_to_ppid(cls, "check_user_kwargs", kwargs)
176
+ return ":".join([code, cback])
177
+
178
+ @staticmethod
179
+ def get_ppkw_from_ppid(bg_ppid):
180
+ """Return keyword arguments for any subclass from a PPID string"""
181
+ code, pp_check_user_kwargs = bg_ppid.split(":")
182
+ for bg_code in get_available_background_methods():
183
+ if bg_code == code:
184
+ cls = get_available_background_methods()[bg_code]
185
+ break
186
+ else:
187
+ raise ValueError(
188
+ f"Could not find background computation method '{code}'!")
189
+ kwargs = ppid.ppid_to_kwargs(cls=cls,
190
+ method="check_user_kwargs",
191
+ ppid=pp_check_user_kwargs)
192
+ return kwargs
190
193
 
191
194
  def process(self):
192
195
  self.process_approach()
193
-
194
196
  bg_ppid = self.get_ppid()
195
197
  # Store pipeline information in the image_bg feature
196
198
  self.h5out["events/image_bg"].attrs["dcnum ppid background"] = bg_ppid
@@ -200,3 +202,19 @@ class Background(abc.ABC):
200
202
  @abc.abstractmethod
201
203
  def process_approach(self):
202
204
  """The actual background computation approach"""
205
+
206
+ @classmethod
207
+ def get_ppid_from_kwargs(cls, kwargs):
208
+ warnings.warn(
209
+ "Please use get_ppid_from_ppkw instead of get_ppid_from_kwargs.",
210
+ DeprecationWarning)
211
+ return cls.get_ppid_from_ppkw(kwargs)
212
+
213
+
214
+ @functools.cache
215
+ def get_available_background_methods():
216
+ """Return dictionary of background computation methods"""
217
+ methods = {}
218
+ for cls in Background.__subclasses__():
219
+ methods[cls.get_ppid_code()] = cls
220
+ return methods
@@ -1,3 +1,4 @@
1
1
  # flake8: noqa: F401
2
+ """Feature computation: brightness-based features"""
2
3
  from .bright_all import brightness_features
3
4
  from .common import brightness_names
@@ -1,3 +1,4 @@
1
1
  # flake8: noqa: F401
2
+ """Feature computation: OpenCV moments-based features"""
2
3
  from .mt_legacy import moments_based_features
3
4
 
@@ -1,2 +1,3 @@
1
1
  # flake8: noqa: F401
2
+ """Feature computation: Haralick texture features"""
2
3
  from .tex_all import haralick_names, haralick_texture_features
dcnum/feat/gate.py CHANGED
@@ -1,8 +1,10 @@
1
+ """Feature computation: gating after feature extraction"""
1
2
  import copy
3
+ import warnings
2
4
 
3
5
  import numpy as np
4
6
 
5
- from ..meta.ppid import kwargs_to_ppid
7
+ from ..meta.ppid import kwargs_to_ppid, ppid_to_kwargs
6
8
 
7
9
 
8
10
  class Gate:
@@ -84,42 +86,6 @@ class Gate:
84
86
 
85
87
  return all_online_filters
86
88
 
87
- def get_ppid(self):
88
- """Return a unique gating pipeline identifier
89
-
90
- The pipeline identifier is universally applicable and must
91
- be backwards-compatible (future versions of dcevent will
92
- correctly acknowledge the ID).
93
-
94
- The gating pipeline ID is defined as::
95
-
96
- KEY:KW_GATE
97
-
98
- Where KEY is e.g. "online_gates", and KW_GATE is
99
- the corresponding value, e.g.::
100
-
101
- online_gates=True^size_thresh_mask=5
102
- """
103
- return self.get_ppid_from_kwargs(self.kwargs)
104
-
105
- @classmethod
106
- def get_ppid_from_kwargs(cls, kwargs):
107
- # TODO:
108
- # If polygon filters are used, the MD5sum should be used and
109
- # they should be placed as a log to the output .rtdc file.
110
- kwargs = copy.deepcopy(kwargs)
111
- if kwargs.get("size_thresh_mask") is None:
112
- # Set the default described in init
113
- kwargs["size_thresh_mask"] = cls._default_size_thresh_mask
114
- key = cls.key()
115
- cback = kwargs_to_ppid(cls, "__init__", kwargs)
116
-
117
- return ":".join([key, cback])
118
-
119
- @property
120
- def features(self):
121
- return [kk.split()[0] for kk in list(self.box_gates.keys())]
122
-
123
89
  def gate_feature(self, feat, data):
124
90
  valid_left = True
125
91
  valid_right = True
@@ -129,10 +95,6 @@ class Gate:
129
95
  valid_right = data < self.box_gates[f"{feat} max"]
130
96
  return np.logical_and(valid_left, valid_right)
131
97
 
132
- @classmethod
133
- def key(cls):
134
- return "norm"
135
-
136
98
  def gate_event(self, event):
137
99
  """Return None if the event should not be used, else `event`"""
138
100
  if self.box_gates and event:
@@ -170,3 +132,62 @@ class Gate:
170
132
  if mask_sum is None:
171
133
  mask_sum = np.sum(mask)
172
134
  return mask_sum > self.kwargs["size_thresh_mask"]
135
+
136
+ def get_ppid(self):
137
+ """Return a unique gating pipeline identifier
138
+
139
+ The pipeline identifier is universally applicable and must
140
+ be backwards-compatible (future versions of dcevent will
141
+ correctly acknowledge the ID).
142
+
143
+ The gating pipeline ID is defined as::
144
+
145
+ KEY:KW_GATE
146
+
147
+ Where KEY is e.g. "online_gates", and KW_GATE is
148
+ the corresponding value, e.g.::
149
+
150
+ online_gates=True^size_thresh_mask=5
151
+ """
152
+ return self.get_ppid_from_ppkw(self.kwargs)
153
+
154
+ @classmethod
155
+ def get_ppid_code(cls):
156
+ return "norm"
157
+
158
+ @classmethod
159
+ def get_ppid_from_ppkw(cls, kwargs):
160
+ """return full pipeline identifier from the given keywords"""
161
+ # TODO:
162
+ # If polygon filters are used, the MD5sum should be used and
163
+ # they should be placed as a log to the output .rtdc file.
164
+ kwargs = copy.deepcopy(kwargs)
165
+ if kwargs.get("size_thresh_mask") is None:
166
+ # Set the default described in init
167
+ kwargs["size_thresh_mask"] = cls._default_size_thresh_mask
168
+ key = cls.get_ppid_code()
169
+ cback = kwargs_to_ppid(cls, "__init__", kwargs)
170
+
171
+ return ":".join([key, cback])
172
+
173
+ @staticmethod
174
+ def get_ppkw_from_ppid(gate_ppid):
175
+ code, pp_gate_kwargs = gate_ppid.split(":")
176
+ if code != Gate.get_ppid_code():
177
+ raise ValueError(
178
+ f"Could not find gating method '{code}'!")
179
+ kwargs = ppid_to_kwargs(cls=Gate,
180
+ method="__init__",
181
+ ppid=pp_gate_kwargs)
182
+ return kwargs
183
+
184
+ @property
185
+ def features(self):
186
+ return [kk.split()[0] for kk in list(self.box_gates.keys())]
187
+
188
+ @classmethod
189
+ def get_ppid_from_kwargs(cls, kwargs):
190
+ warnings.warn(
191
+ "Please use get_ppid_from_ppkw instead of get_ppid_from_kwargs.",
192
+ DeprecationWarning)
193
+ return cls.get_ppid_from_ppkw(kwargs)
@@ -1,3 +1,4 @@
1
+ """Feature Extraction: event extractor worker"""
1
2
  import collections
2
3
  import logging
3
4
  from logging.handlers import QueueHandler
@@ -6,10 +7,11 @@ import os
6
7
  import queue
7
8
  import threading
8
9
  import traceback
10
+ import warnings
9
11
 
10
12
  import numpy as np
11
13
 
12
- from ..meta.ppid import kwargs_to_ppid
14
+ from ..meta.ppid import kwargs_to_ppid, ppid_to_kwargs
13
15
  from ..read import HDF5Data
14
16
 
15
17
  from .feat_brightness import brightness_features
@@ -27,14 +29,13 @@ class QueueEventExtractor:
27
29
  def __init__(self,
28
30
  data: HDF5Data,
29
31
  gate: Gate,
30
- preselect: bool,
31
- ptp_median: float,
32
32
  raw_queue: mp.Queue,
33
33
  event_queue: mp.Queue,
34
34
  log_queue: mp.Queue,
35
35
  feat_nevents: mp.Array,
36
36
  label_array: mp.Array,
37
37
  finalize_extraction: mp.Value,
38
+ close_queues: bool = True,
38
39
  extract_kwargs: dict = None,
39
40
  *args, **kwargs):
40
41
  """Base class for event extraction from label images
@@ -44,15 +45,10 @@ class QueueEventExtractor:
44
45
 
45
46
  Parameters
46
47
  ----------
47
- data:
48
+ data: HDF5Data
48
49
  Data source.
49
- gate:
50
+ gate: Gate
50
51
  Gating rules.
51
- preselect:
52
- Whether to perform data preselection based on peak-to-peak
53
- values in the images.
54
- ptp_median:
55
- Median peak-to-peak value in the images for preselction.
56
52
  raw_queue:
57
53
  Queue from which the worker obtains the chunks and
58
54
  indices of the labels to work on.
@@ -70,27 +66,26 @@ class QueueEventExtractor:
70
66
  finalize_extraction:
71
67
  Shared value indicating whether this worker should stop as
72
68
  soon as the `raw_queue` is empty.
69
+ close_queues: bool
70
+ Whether to close event and logging queues
71
+ (set to False in debug mode)
73
72
  extract_kwargs:
74
73
  Keyword arguments for the extraction process. See the
75
74
  keyword-only arguments in
76
75
  :func:`QueueEventExtractor.get_events_from_masks`.
77
-
78
76
  """
79
77
  super(QueueEventExtractor, self).__init__(*args, **kwargs)
80
78
  #: Data instance
81
79
  self.data = data
82
80
  #: Gating information
83
81
  self.gate = gate
84
- #: Whether to perform Preselection
85
- self.preselect = preselect
86
- #: Peak-to-peak median for preselection
87
- self.ptp_median = ptp_median
88
82
  #: queue containing sub-indices for `label_array`
89
83
  self.raw_queue = raw_queue
90
84
  #: queue with event-wise feature dictionaries
91
85
  self.event_queue = event_queue
92
86
  #: queue for logging
93
87
  self.log_queue = log_queue
88
+ self.close_queues = close_queues
94
89
  #: Shared array of length `len(data)` into which the number of
95
90
  #: events per frame is written.
96
91
  self.feat_nevents = feat_nevents
@@ -111,24 +106,49 @@ class QueueEventExtractor:
111
106
  self.logger = None
112
107
 
113
108
  @staticmethod
114
- def get_init_kwargs(data, gate, preselect, ptp_median, log_queue):
115
- """You can pass `*args.values()` directly to __init__
109
+ def get_init_kwargs(data: HDF5Data,
110
+ gate: Gate,
111
+ log_queue: mp.Queue,
112
+ preselect: None = None,
113
+ ptp_median: None = None):
114
+ """Get initialization arguments for :cass:`.QueueEventExtractor`
116
115
 
117
116
  This method was created for convenience reasons:
118
117
  - It makes sure that the order of arguments is correct, since it
119
118
  is implemented in the same class.
120
119
  - It simplifies testing.
120
+
121
+ Parameters
122
+ ----------
123
+ data: HDF5Data
124
+ Input data
125
+ gate: HDF5Data
126
+ Gating class to use
127
+ log_queue: mp.Queue
128
+ Queue for sending log messages
129
+ preselect, ptp_median:
130
+ Deprecated
131
+
132
+ Returns
133
+ -------
134
+ args: dict
135
+ You can pass `*args.values()` directly to `__init__`
121
136
  """
122
137
  # queue with the raw (unsegmented) image data
123
138
  raw_queue = mp_spawn.Queue()
124
139
  # queue with event-wise feature dictionaries
125
140
  event_queue = mp_spawn.Queue()
126
141
 
142
+ if preselect is not None:
143
+ warnings.warn("The `preselect` argument is deprecated!",
144
+ DeprecationWarning)
145
+ if ptp_median is not None:
146
+ warnings.warn("The `ptp_median` argument is deprecated!",
147
+ DeprecationWarning)
148
+
127
149
  args = collections.OrderedDict()
128
150
  args["data"] = data
129
151
  args["gate"] = gate
130
- args["preselect"] = preselect
131
- args["ptp_median"] = ptp_median
132
152
  args["raw_queue"] = raw_queue
133
153
  args["event_queue"] = event_queue
134
154
  args["log_queue"] = log_queue
@@ -139,15 +159,9 @@ class QueueEventExtractor:
139
159
  np.ctypeslib.ctypes.c_int16,
140
160
  int(np.prod(data.image.chunk_shape)))
141
161
  args["finalize_extraction"] = mp_spawn.Value("b", False)
162
+ args["close_queues"] = True
142
163
  return args
143
164
 
144
- @classmethod
145
- def get_ppid_from_kwargs(cls, kwargs):
146
- """Return the pipeline ID for this event extractor"""
147
- key = "legacy"
148
- cback = kwargs_to_ppid(cls, "get_events_from_masks", kwargs)
149
- return ":".join([key, cback])
150
-
151
165
  def get_events_from_masks(self, masks, data_index, *,
152
166
  brightness: bool = True,
153
167
  haralick: bool = True,
@@ -236,7 +250,29 @@ class QueueEventExtractor:
236
250
 
237
251
  b=1^h=1
238
252
  """
239
- return self.get_ppid_from_kwargs(self.extract_kwargs)
253
+ return self.get_ppid_from_ppkw(self.extract_kwargs)
254
+
255
+ @classmethod
256
+ def get_ppid_code(cls):
257
+ return "legacy"
258
+
259
+ @classmethod
260
+ def get_ppid_from_ppkw(cls, kwargs):
261
+ """Return the pipeline ID for this event extractor"""
262
+ code = cls.get_ppid_code()
263
+ cback = kwargs_to_ppid(cls, "get_events_from_masks", kwargs)
264
+ return ":".join([code, cback])
265
+
266
+ @staticmethod
267
+ def get_ppkw_from_ppid(extr_ppid):
268
+ code, pp_extr_kwargs = extr_ppid.split(":")
269
+ if code != QueueEventExtractor.get_ppid_code():
270
+ raise ValueError(
271
+ f"Could not find extraction method '{code}'!")
272
+ kwargs = ppid_to_kwargs(cls=QueueEventExtractor,
273
+ method="get_events_from_masks",
274
+ ppid=pp_extr_kwargs)
275
+ return kwargs
240
276
 
241
277
  def process_label(self, label, index):
242
278
  """Process one label image, extracting masks and features"""
@@ -245,11 +281,7 @@ class QueueEventExtractor:
245
281
  # TODO: Do this before segmentation already?
246
282
  # skip events that have been analyzed already
247
283
  return None
248
- if self.preselect:
249
- # TODO: Do this before segmentation already?
250
- ptp = np.ptp(self.data.image_corr[index])
251
- if ptp < 0.1 * self.ptp_median:
252
- return None
284
+
253
285
  masks = self.get_masks_from_label(label)
254
286
  if masks.size:
255
287
  events = self.get_events_from_masks(
@@ -300,14 +332,22 @@ class QueueEventExtractor:
300
332
  self.event_queue.put((index, events))
301
333
 
302
334
  self.logger.debug(f"Finalizing `run` for PID {os.getpid()}, {self}")
303
- # Explicitly close the event queue and join it
304
- self.event_queue.close()
305
- self.event_queue.join_thread()
306
- self.logger.debug(f"End of `run` for PID {os.getpid()}, {self}")
307
- # Also close the logging queue. Not that not all messages might
308
- # arrive in the logging queue, since we called `cancel_join_thread`
309
- # earlier.
310
- self.log_queue.close()
335
+ if self.close_queues:
336
+ # Explicitly close the event queue and join it
337
+ self.event_queue.close()
338
+ self.event_queue.join_thread()
339
+ self.logger.debug(f"End of `run` for PID {os.getpid()}, {self}")
340
+ # Also close the logging queue. Note that not all messages might
341
+ # arrive in the logging queue, since we called `cancel_join_thread`
342
+ # earlier.
343
+ self.log_queue.close()
344
+
345
+ @classmethod
346
+ def get_ppid_from_kwargs(cls, kwargs):
347
+ warnings.warn(
348
+ "Please use get_ppid_from_ppkw instead of get_ppid_from_kwargs.",
349
+ DeprecationWarning)
350
+ return cls.get_ppid_from_ppkw(kwargs)
311
351
 
312
352
 
313
353
  class EventExtractorProcess(QueueEventExtractor, mp_spawn.Process):
@@ -0,0 +1,4 @@
1
+ # flake8: noqa: F401
2
+ """Logic for running the dcnum pipeline"""
3
+ from .job import DCNumPipelineJob
4
+ from .ctrl import DCNumJobRunner