dcnum 0.20.2__tar.gz → 0.20.4__tar.gz

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.

Files changed (105) hide show
  1. {dcnum-0.20.2 → dcnum-0.20.4}/CHANGELOG +9 -1
  2. {dcnum-0.20.2/src/dcnum.egg-info → dcnum-0.20.4}/PKG-INFO +1 -1
  3. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/_version.py +2 -2
  4. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/event_extractor_manager_thread.py +34 -25
  5. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/queue_event_extractor.py +4 -4
  6. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/logic/ctrl.py +13 -6
  7. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/logic/job.py +9 -1
  8. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/segmenter.py +1 -1
  9. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/segmenter_cpu.py +2 -0
  10. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/segmenter_manager_thread.py +25 -27
  11. {dcnum-0.20.2 → dcnum-0.20.4/src/dcnum.egg-info}/PKG-INFO +1 -1
  12. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_event_extractor_manager.py +0 -1
  13. {dcnum-0.20.2 → dcnum-0.20.4}/.github/workflows/check.yml +0 -0
  14. {dcnum-0.20.2 → dcnum-0.20.4}/.github/workflows/deploy_pypi.yml +0 -0
  15. {dcnum-0.20.2 → dcnum-0.20.4}/.gitignore +0 -0
  16. {dcnum-0.20.2 → dcnum-0.20.4}/.readthedocs.yml +0 -0
  17. {dcnum-0.20.2 → dcnum-0.20.4}/LICENSE +0 -0
  18. {dcnum-0.20.2 → dcnum-0.20.4}/README.rst +0 -0
  19. {dcnum-0.20.2 → dcnum-0.20.4}/docs/conf.py +0 -0
  20. {dcnum-0.20.2 → dcnum-0.20.4}/docs/extensions/github_changelog.py +0 -0
  21. {dcnum-0.20.2 → dcnum-0.20.4}/docs/index.rst +0 -0
  22. {dcnum-0.20.2 → dcnum-0.20.4}/docs/requirements.txt +0 -0
  23. {dcnum-0.20.2 → dcnum-0.20.4}/pyproject.toml +0 -0
  24. {dcnum-0.20.2 → dcnum-0.20.4}/setup.cfg +0 -0
  25. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/__init__.py +0 -0
  26. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/__init__.py +0 -0
  27. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_background/__init__.py +0 -0
  28. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_background/base.py +0 -0
  29. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_background/bg_copy.py +0 -0
  30. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_background/bg_roll_median.py +0 -0
  31. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_background/bg_sparse_median.py +0 -0
  32. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_brightness/__init__.py +0 -0
  33. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_brightness/bright_all.py +0 -0
  34. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_brightness/common.py +0 -0
  35. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_contour/__init__.py +0 -0
  36. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_contour/contour.py +0 -0
  37. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_contour/moments.py +0 -0
  38. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_contour/volume.py +0 -0
  39. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_texture/__init__.py +0 -0
  40. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_texture/common.py +0 -0
  41. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/feat_texture/tex_all.py +0 -0
  42. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/feat/gate.py +0 -0
  43. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/logic/__init__.py +0 -0
  44. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/logic/json_encoder.py +0 -0
  45. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/meta/__init__.py +0 -0
  46. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/meta/paths.py +0 -0
  47. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/meta/ppid.py +0 -0
  48. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/read/__init__.py +0 -0
  49. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/read/cache.py +0 -0
  50. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/read/const.py +0 -0
  51. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/read/hdf5_data.py +0 -0
  52. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/read/mapped.py +0 -0
  53. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/__init__.py +0 -0
  54. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/segm_thresh.py +0 -0
  55. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/segm/segmenter_gpu.py +0 -0
  56. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/write/__init__.py +0 -0
  57. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/write/deque_writer_thread.py +0 -0
  58. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/write/queue_collector_thread.py +0 -0
  59. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum/write/writer.py +0 -0
  60. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum.egg-info/SOURCES.txt +0 -0
  61. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum.egg-info/dependency_links.txt +0 -0
  62. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum.egg-info/requires.txt +0 -0
  63. {dcnum-0.20.2 → dcnum-0.20.4}/src/dcnum.egg-info/top_level.txt +0 -0
  64. {dcnum-0.20.2 → dcnum-0.20.4}/tests/conftest.py +0 -0
  65. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip +0 -0
  66. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_cytoshot_full-features_2023.zip +0 -0
  67. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_cytoshot_full-features_2024.zip +0 -0
  68. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip +0 -0
  69. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_shapein_empty.zip +0 -0
  70. {dcnum-0.20.2 → dcnum-0.20.4}/tests/data/fmt-hdf5_shapein_raw-with-variable-length-logs.zip +0 -0
  71. {dcnum-0.20.2 → dcnum-0.20.4}/tests/helper_methods.py +0 -0
  72. {dcnum-0.20.2 → dcnum-0.20.4}/tests/requirements.txt +0 -0
  73. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_background_base.py +0 -0
  74. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_background_bg_copy.py +0 -0
  75. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_background_bg_roll_median.py +0 -0
  76. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_background_bg_sparsemed.py +0 -0
  77. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_brightness.py +0 -0
  78. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_gate.py +0 -0
  79. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_haralick.py +0 -0
  80. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_moments_based.py +0 -0
  81. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_moments_based_extended.py +0 -0
  82. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_feat_volume.py +0 -0
  83. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_init.py +0 -0
  84. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_logic_job.py +0 -0
  85. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_logic_join.py +0 -0
  86. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_logic_json.py +0 -0
  87. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_logic_pipeline.py +0 -0
  88. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_paths.py +0 -0
  89. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_base.py +0 -0
  90. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_bg.py +0 -0
  91. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_data.py +0 -0
  92. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_feat.py +0 -0
  93. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_gate.py +0 -0
  94. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_meta_ppid_segm.py +0 -0
  95. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_read_basin.py +0 -0
  96. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_read_concat_hdf5.py +0 -0
  97. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_read_hdf5.py +0 -0
  98. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_read_hdf5_basins.py +0 -0
  99. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_read_hdf5_index_mapping.py +0 -0
  100. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_segm_base.py +0 -0
  101. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_segm_no_mask_proc.py +0 -0
  102. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_segm_thresh.py +0 -0
  103. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_write_deque_writer_thread.py +0 -0
  104. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_write_queue_collector_thread.py +0 -0
  105. {dcnum-0.20.2 → dcnum-0.20.4}/tests/test_write_writer.py +0 -0
@@ -1,3 +1,11 @@
1
+ 0.20.4
2
+ - fix: slot_chunks were not performed in increasing order, making it
3
+ impossible for writer to continue writing and consequent OOM
4
+ - enh: reserve one CPU for writer thread and control logic
5
+ - enh: disentangle logging and debugging keyword arguments
6
+ - enh: do not initialize slot_chunks and slot_states with lock
7
+ 0.20.3
8
+ - enh: improve strategy for stalling for slow writer
1
9
  0.20.2
2
10
  - ref: remove __init__ from SegmentThresh
3
11
  0.20.1
@@ -24,7 +32,7 @@
24
32
  postprocessing
25
33
  - feat: allow to specify ranges when creating an HDF5Data instance to
26
34
  enable e.g. processing only a portion of an input file
27
- - feat: volume computation via contour revolve algorithm
35
+ - feat: volume computation via contour revolve algorithm (#23)
28
36
  - feat: background offset (flickering) correction via the
29
37
  "offset_correction" keyword for the "sparsemed" background computer
30
38
  and "bg_off" everywhere else
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcnum
3
- Version: 0.20.2
3
+ Version: 0.20.4
4
4
  Summary: numerics toolbox for imaging deformability cytometry
5
5
  Author: Maximilian Schlögel, Paul Müller
6
6
  Maintainer-email: Paul Müller <dev@craban.de>
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.20.2'
16
- __version_tuple__ = version_tuple = (0, 20, 2)
15
+ __version__ = version = '0.20.4'
16
+ __version_tuple__ = version_tuple = (0, 20, 4)
@@ -46,8 +46,8 @@ class EventExtractorManagerThread(threading.Thread):
46
46
  The queue the writer uses. We monitor this queue. If it
47
47
  fills up, we take a break.
48
48
  debug:
49
- Whether to run in debugging mode which means more log
50
- messages and only one thread (`num_workers` has no effect).
49
+ Whether to run in debugging mode which means only one
50
+ event extraction thread (`num_workers` has no effect).
51
51
  """
52
52
  super(EventExtractorManagerThread, self).__init__(
53
53
  name="EventExtractorManager", *args, **kwargs)
@@ -95,32 +95,41 @@ class EventExtractorManagerThread(threading.Thread):
95
95
  while True:
96
96
  # If the writer_dq starts filling up, then this could lead to
97
97
  # an oom-kill signal. Stall for the writer to prevent this.
98
- ldq = len(self.writer_dq)
99
- if ldq > 1000:
100
- stallsec = ldq / 1000
98
+ if (ldq := len(self.writer_dq)) > 1000:
99
+ time.sleep(1)
100
+ ldq2 = len(self.writer_dq)
101
+ stall_time = (ldq2 - 200) / (ldq - ldq2) if ldq2 > 200 else 0
102
+ time.sleep(stall_time)
101
103
  self.logger.warning(
102
- f"Stalling {stallsec:.1f}s for slow writer")
103
- time.sleep(stallsec)
104
+ f"Stalled {stall_time + 1:.1f}s for slow writer "
105
+ f"({ldq} chunks queued)")
104
106
 
105
- cur_slot = 0
106
107
  unavailable_slots = 0
108
+ found_free_slot = False
107
109
  # Check all slots for segmented labels
108
- while True:
109
- # - "e" there is data from the segmenter (the extractor
110
- # can take it and process it)
111
- # - "s" the extractor processed the data and is waiting
112
- # for the segmenter
113
- if self.slot_states[cur_slot] == "e":
114
- # The segmenter has something for us in this slot.
115
- break
116
- else:
117
- # Try another slot.
118
- unavailable_slots += 1
119
- cur_slot = (cur_slot + 1) % num_slots
120
- if unavailable_slots >= num_slots:
121
- # There is nothing to do, try to avoid 100% CPU
122
- unavailable_slots = 0
123
- time.sleep(.1)
110
+ while not found_free_slot:
111
+ # We sort the slots according to the slot chunks so that we
112
+ # always process the slot with the smallest slot chunk number
113
+ # first. Initially, the slot_chunks array is filled with
114
+ # zeros, but the segmenter fills up the slots with the lowest
115
+ # number first.
116
+ for cur_slot in np.argsort(self.slot_chunks):
117
+ # - "e" there is data from the segmenter (the extractor
118
+ # can take it and process it)
119
+ # - "s" the extractor processed the data and is waiting
120
+ # for the segmenter
121
+ if self.slot_states[cur_slot] == "e":
122
+ # The segmenter has something for us in this slot.
123
+ found_free_slot = True
124
+ break
125
+ else:
126
+ # Try another slot.
127
+ unavailable_slots += 1
128
+ cur_slot = (cur_slot + 1) % num_slots
129
+ if unavailable_slots >= num_slots:
130
+ # There is nothing to do, try to avoid 100% CPU
131
+ unavailable_slots = 0
132
+ time.sleep(.1)
124
133
 
125
134
  t1 = time.monotonic()
126
135
 
@@ -147,7 +156,7 @@ class EventExtractorManagerThread(threading.Thread):
147
156
  # We are done here. The segmenter may continue its deed.
148
157
  self.slot_states[cur_slot] = "w"
149
158
 
150
- self.logger.debug(f"Extracted one chunk: {chunk}")
159
+ self.logger.debug(f"Extracted chunk {chunk} in slot {cur_slot}")
151
160
  self.t_count += time.monotonic() - t1
152
161
 
153
162
  chunks_processed += 1
@@ -36,7 +36,7 @@ class QueueEventExtractor:
36
36
  finalize_extraction: mp.Value,
37
37
  invalid_mask_counter: mp.Value,
38
38
  worker_monitor: mp.RawArray,
39
- log_level: int = logging.INFO,
39
+ log_level: int = None,
40
40
  extract_kwargs: dict = None,
41
41
  worker_index: int = None,
42
42
  *args, **kwargs):
@@ -103,7 +103,7 @@ class QueueEventExtractor:
103
103
  # it looks like we have the same PID as the parent process. We
104
104
  # are setting up logging in `run`.
105
105
  self.logger = None
106
- self.log_level = log_level
106
+ self.log_level = log_level or logging.getLogger("dcnum").level
107
107
  #: Shared array of length `len(data)` into which the number of
108
108
  #: events per frame is written.
109
109
  self.feat_nevents = feat_nevents
@@ -124,7 +124,7 @@ class QueueEventExtractor:
124
124
  gate: Gate,
125
125
  num_extractors: int,
126
126
  log_queue: mp.Queue,
127
- log_level: int = logging.INFO,
127
+ log_level: int = None,
128
128
  ):
129
129
  """Get initialization arguments for :cass:`.QueueEventExtractor`
130
130
 
@@ -172,7 +172,7 @@ class QueueEventExtractor:
172
172
  args["finalize_extraction"] = mp_spawn.Value("b", False)
173
173
  args["invalid_mask_counter"] = mp_spawn.Value("L", 0)
174
174
  args["worker_monitor"] = mp_spawn.RawArray("L", num_extractors)
175
- args["log_level"] = log_level
175
+ args["log_level"] = log_level or logging.getLogger("dcnum").level
176
176
  return args
177
177
 
178
178
  def get_events_from_masks(self, masks, data_index, *,
@@ -90,8 +90,7 @@ class DCNumJobRunner(threading.Thread):
90
90
  # Set up logging
91
91
  # General logger for this job
92
92
  self.main_logger = logging.getLogger("dcnum")
93
- self.main_logger.setLevel(
94
- logging.DEBUG if job["debug"] else logging.INFO)
93
+ self.main_logger.setLevel(job["log_level"])
95
94
  # Log file output in target directory
96
95
  self.path_log = job["path_out"].with_suffix(".log")
97
96
  self.path_log.parent.mkdir(exist_ok=True, parents=True)
@@ -659,15 +658,24 @@ class DCNumJobRunner(threading.Thread):
659
658
  # Split segmentation and feature extraction workers evenly.
660
659
  num_extractors = self.job["num_procs"] // 2
661
660
  num_segmenters = self.job["num_procs"] - num_extractors
661
+ # leave one CPU for the writer and the remaining Threads
662
+ num_segmenters -= 1
662
663
  else: # GPU segmenter
663
664
  num_slots = 3
664
665
  num_extractors = self.job["num_procs"]
666
+ # leave one CPU for the writer and the remaining Threads
667
+ num_extractors -= 1
665
668
  num_segmenters = 1
666
669
  num_extractors = max(1, num_extractors)
667
670
  num_segmenters = max(1, num_segmenters)
668
671
  self.job.kwargs["segmenter_kwargs"]["num_workers"] = num_segmenters
669
- slot_chunks = mp_spawn.Array("i", num_slots)
670
- slot_states = mp_spawn.Array("u", num_slots)
672
+ self.job.kwargs["segmenter_kwargs"]["debug"] = self.job["debug"]
673
+ slot_chunks = mp_spawn.Array("i", num_slots, lock=False)
674
+ slot_states = mp_spawn.Array("u", num_slots, lock=False)
675
+
676
+ self.logger.debug(f"Number of slots: {num_slots}")
677
+ self.logger.debug(f"Number of segmenters: {num_segmenters}")
678
+ self.logger.debug(f"Number of extractors: {num_extractors}")
671
679
 
672
680
  # Initialize segmenter manager thread
673
681
  thr_segm = SegmenterManagerThread(
@@ -676,7 +684,6 @@ class DCNumJobRunner(threading.Thread):
676
684
  bg_off=self.dtin["bg_off"] if "bg_off" in self.dtin else None,
677
685
  slot_states=slot_states,
678
686
  slot_chunks=slot_chunks,
679
- debug=self.job["debug"],
680
687
  )
681
688
  thr_segm.start()
682
689
 
@@ -686,7 +693,7 @@ class DCNumJobRunner(threading.Thread):
686
693
  gate=gate.Gate(self.dtin, **self.job["gate_kwargs"]),
687
694
  num_extractors=num_extractors,
688
695
  log_queue=self.log_queue,
689
- log_level=logging.DEBUG if self.job["debug"] else logging.INFO,
696
+ log_level=self.logger.level,
690
697
  )
691
698
  fe_kwargs["extract_kwargs"] = self.job["feature_kwargs"]
692
699
 
@@ -1,6 +1,7 @@
1
1
  import collections
2
2
  import copy
3
3
  import inspect
4
+ import logging
4
5
  import multiprocessing as mp
5
6
  import pathlib
6
7
  from typing import Dict, Literal
@@ -31,6 +32,7 @@ class DCNumPipelineJob:
31
32
  basin_strategy: Literal["drain", "tap"] = "drain",
32
33
  no_basins_in_output: bool = None,
33
34
  num_procs: int = None,
35
+ log_level: int = logging.INFO,
34
36
  debug: bool = False,
35
37
  ):
36
38
  """Pipeline job recipe
@@ -73,8 +75,11 @@ class DCNumPipelineJob:
73
75
  Deprecated
74
76
  num_procs: int
75
77
  Number of processes to use
78
+ log_level: int
79
+ Logging level to use.
76
80
  debug: bool
77
- Whether to be verbose and use threads instead of processes
81
+ Whether to set logging level to "DEBUG" and
82
+ use threads instead of processes
78
83
  """
79
84
  if no_basins_in_output is not None:
80
85
  warnings.warn("The `no_basins_in_output` keyword argument is "
@@ -104,6 +109,9 @@ class DCNumPipelineJob:
104
109
  if path_out is None:
105
110
  pin = pathlib.Path(path_in)
106
111
  path_out = pin.with_name(pin.stem + "_dcn.rtdc")
112
+ # Set logging level to DEBUG in debugging mode
113
+ if self.kwargs["debug"]:
114
+ self.kwargs["log_level"] = logging.DEBUG
107
115
  self.kwargs["path_out"] = pathlib.Path(path_out)
108
116
  # Set default mask kwargs for segmenter
109
117
  self.kwargs["segmenter_kwargs"].setdefault("kwargs_mask", {})
@@ -38,7 +38,7 @@ class Segmenter(abc.ABC):
38
38
  kwargs_mask: dict
39
39
  Keyword arguments for mask post-processing (see `process_mask`)
40
40
  debug: bool
41
- Debugging parameters
41
+ Enable debugging mode (e.g. CPU segmenter runs in one thread)
42
42
  kwargs:
43
43
  Additional, optional keyword arguments for `segment_batch`.
44
44
  """
@@ -189,9 +189,11 @@ class CPUSegmenter(Segmenter, abc.ABC):
189
189
  if self.debug:
190
190
  worker_cls = CPUSegmenterWorkerThread
191
191
  num_workers = 1
192
+ self.logger.debug("Running with one worker in main thread")
192
193
  else:
193
194
  worker_cls = CPUSegmenterWorkerProcess
194
195
  num_workers = min(self.num_workers, image_data.shape[0])
196
+ self.logger.debug(f"Running with {num_workers} workers")
195
197
 
196
198
  if not self._mp_workers:
197
199
  step_size = batch_size // num_workers
@@ -18,7 +18,6 @@ class SegmenterManagerThread(threading.Thread):
18
18
  slot_states: mp.Array,
19
19
  slot_chunks: mp.Array,
20
20
  bg_off: np.ndarray = None,
21
- debug: bool = False,
22
21
  *args, **kwargs):
23
22
  """Manage the segmentation of image data
24
23
 
@@ -43,10 +42,6 @@ class SegmenterManagerThread(threading.Thread):
43
42
  1d array containing additional background image offset values
44
43
  that are added to each background image before subtraction
45
44
  from the input image
46
- debug:
47
- Whether to run in debugging mode (more verbose messages and
48
- CPU-based segmentation is done in one single thread instead
49
- of in multiple subprocesses).
50
45
 
51
46
  Notes
52
47
  -----
@@ -80,33 +75,36 @@ class SegmenterManagerThread(threading.Thread):
80
75
  self.labels_list = [None] * len(self.slot_states)
81
76
  #: Time counter for segmentation
82
77
  self.t_count = 0
83
- #: Whether running in debugging mode
84
- self.debug = debug
85
78
 
86
79
  def run(self):
87
80
  num_slots = len(self.slot_states)
88
81
  # We iterate over all the chunks of the image data.
89
82
  for chunk in self.image_data.iter_chunks():
90
- cur_slot = 0
91
- empty_slots = 0
83
+ unavailable_slots = 0
84
+ found_free_slot = False
92
85
  # Wait for a free slot to perform segmentation (compute labels)
93
- while True:
94
- # - "e" there is data from the segmenter (the extractor
95
- # can take it and process it)
96
- # - "s" the extractor processed the data and is waiting
97
- # for the segmenter
98
- if self.slot_states[cur_slot] != "e":
99
- # It's the segmenter's turn. Note that we use '!= "e"',
100
- # because the initial value is "\x00".
101
- break
102
- else:
103
- # Try another slot.
104
- empty_slots += 1
105
- cur_slot = (cur_slot + 1) % num_slots
106
- if empty_slots >= num_slots:
107
- # There is nothing to do, try to avoid 100% CPU
108
- empty_slots = 0
109
- time.sleep(.01)
86
+ while not found_free_slot:
87
+ # We sort the slots according to the slot chunks so that we
88
+ # always process the slot with the smallest slot chunk number
89
+ # first. Initially, the slot_chunks array is filled with
90
+ # zeros, but we populate it here.
91
+ for cur_slot in np.argsort(self.slot_chunks):
92
+ # - "e" there is data from the segmenter (the extractor
93
+ # can take it and process it)
94
+ # - "s" the extractor processed the data and is waiting
95
+ # for the segmenter
96
+ if self.slot_states[cur_slot] != "e":
97
+ # It's the segmenter's turn. Note that we use '!= "e"',
98
+ # because the initial value is "\x00".
99
+ found_free_slot = True
100
+ break
101
+ else:
102
+ # Try another slot.
103
+ unavailable_slots += 1
104
+ if unavailable_slots >= num_slots:
105
+ # There is nothing to do, try to avoid 100% CPU
106
+ unavailable_slots = 0
107
+ time.sleep(.1)
110
108
 
111
109
  t1 = time.monotonic()
112
110
 
@@ -125,7 +123,7 @@ class SegmenterManagerThread(threading.Thread):
125
123
  # This must be done last: Let the extractor know that this
126
124
  # slot is ready for processing.
127
125
  self.slot_states[cur_slot] = "e"
128
- self.logger.debug(f"Segmented one chunk: {chunk}")
126
+ self.logger.debug(f"Segmented chunk {chunk} in slot {cur_slot}")
129
127
 
130
128
  self.t_count += time.monotonic() - t1
131
129
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcnum
3
- Version: 0.20.2
3
+ Version: 0.20.4
4
4
  Summary: numerics toolbox for imaging deformability cytometry
5
5
  Author: Maximilian Schlögel, Paul Müller
6
6
  Maintainer-email: Paul Müller <dev@craban.de>
@@ -33,7 +33,6 @@ def test_event_extractor_manager_thread():
33
33
  image_data=hd.image_corr,
34
34
  slot_states=slot_states,
35
35
  slot_chunks=slot_chunks,
36
- debug=True,
37
36
  )
38
37
  thr_segm.start()
39
38
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes