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/logic/json_encoder.py
CHANGED
|
@@ -7,6 +7,15 @@ import numpy as np
|
|
|
7
7
|
|
|
8
8
|
class ExtendedJSONEncoder(json.JSONEncoder):
|
|
9
9
|
def default(self, obj):
|
|
10
|
+
"""Extended JSON encoder for the **dcnum** logic
|
|
11
|
+
|
|
12
|
+
This JSON encoder can handle the following additional objects:
|
|
13
|
+
|
|
14
|
+
- ``pathlib.Path``
|
|
15
|
+
- integer numbers
|
|
16
|
+
- ``numpy`` boolean
|
|
17
|
+
- slices (via "PYTHON-SLICE" identifier)
|
|
18
|
+
"""
|
|
10
19
|
if isinstance(obj, pathlib.Path):
|
|
11
20
|
return str(obj)
|
|
12
21
|
elif isinstance(obj, numbers.Integral):
|
dcnum/meta/paths.py
CHANGED
dcnum/meta/ppid.py
CHANGED
|
@@ -9,9 +9,11 @@ import warnings
|
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
|
-
#: Increment this string if there are breaking changes that make
|
|
13
|
-
#: previous pipelines unreproducible.
|
|
14
12
|
DCNUM_PPID_GENERATION = "11"
|
|
13
|
+
"""The dcnum pipeline generation.
|
|
14
|
+
Increment this string if there are breaking changes that make
|
|
15
|
+
previous pipelines unreproducible.
|
|
16
|
+
"""
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class ClassWithPPIDCapabilities(Protocol):
|
dcnum/os_env_st.py
CHANGED
|
@@ -4,7 +4,6 @@ import os
|
|
|
4
4
|
|
|
5
5
|
logger = logging.getLogger(__name__)
|
|
6
6
|
|
|
7
|
-
#: environment variables that set number of threads
|
|
8
7
|
os_env_threading = [
|
|
9
8
|
"MKL_NUM_THREADS",
|
|
10
9
|
"NUMBA_NUM_THREADS",
|
|
@@ -14,13 +13,14 @@ os_env_threading = [
|
|
|
14
13
|
"OPENBLAS_NUM_THREADS",
|
|
15
14
|
"VECLIB_MAXIMUM_THREADS",
|
|
16
15
|
]
|
|
16
|
+
"""environment variables that define number of threads libraries will use"""
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class RequestSingleThreaded:
|
|
20
20
|
"""Context manager for starting a process with specific environment
|
|
21
21
|
|
|
22
22
|
When entering the context, the environment variables defined in
|
|
23
|
-
|
|
23
|
+
``os_env_threading`` are all set to "1", telling the relevant libraries
|
|
24
24
|
that they should work in single-threaded mode.
|
|
25
25
|
When exiting the context, these environment variables are reset to
|
|
26
26
|
their original values (or unset if applicable).
|
dcnum/read/__init__.py
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
from .cache import md5sum
|
|
3
3
|
from .const import PROTECTED_FEATURES
|
|
4
4
|
from .detect_flicker import detect_flickering
|
|
5
|
-
from .hdf5_data import HDF5Data, HDF5ImageCache
|
|
5
|
+
from .hdf5_data import HDF5Data, HDF5ImageCache
|
|
6
|
+
from .hdf5_concat import concatenated_hdf5_data
|
|
6
7
|
from .mapped import get_mapping_indices, get_mapped_object
|
dcnum/read/cache.py
CHANGED
|
@@ -25,8 +25,10 @@ class BaseImageChunkCache(abc.ABC):
|
|
|
25
25
|
self._dtype = None
|
|
26
26
|
chunk_size = min(shape[0], chunk_size)
|
|
27
27
|
self._len = self.shape[0]
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
self.cache = collections.OrderedDict()
|
|
30
|
+
"""This is a FILO cache for the chunks"""
|
|
31
|
+
|
|
30
32
|
self.image_shape = self.shape[1:]
|
|
31
33
|
self.chunk_shape = (chunk_size,) + self.shape[1:]
|
|
32
34
|
self.chunk_size = chunk_size
|
dcnum/read/const.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#: Scalar features that apply to all events in a frame and which are
|
|
2
|
-
#: not computed for individual events.
|
|
3
1
|
PROTECTED_FEATURES = [
|
|
4
2
|
"bg_off",
|
|
5
3
|
"flow_rate",
|
|
@@ -10,6 +8,11 @@ PROTECTED_FEATURES = [
|
|
|
10
8
|
"temp_amb",
|
|
11
9
|
"time",
|
|
12
10
|
]
|
|
11
|
+
"""Frame-defined scalar features.
|
|
12
|
+
Scalar features that apply to all events in a frame and which are
|
|
13
|
+
not computed for individual events
|
|
14
|
+
"""
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
# User-defined features may be anything, but if the user needs something
|
|
15
18
|
# very specific for the pipeline, having them protected is a nice feature.
|
dcnum/read/detect_flicker.py
CHANGED
|
@@ -17,7 +17,7 @@ def detect_flickering(image_data: np.ndarray | HDF5Data,
|
|
|
17
17
|
triggering signal.
|
|
18
18
|
|
|
19
19
|
If flickering is detected, you should use the "sparsemed" background
|
|
20
|
-
computation with
|
|
20
|
+
computation with ``offset_correction`` set to True.
|
|
21
21
|
|
|
22
22
|
Parameters
|
|
23
23
|
----------
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import pathlib
|
|
3
|
+
import tempfile
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
import h5py
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from .hdf5_data import HDF5Data
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def concatenated_hdf5_data(paths: list[pathlib.Path],
|
|
14
|
+
path_out: bool | pathlib.Path | None = True,
|
|
15
|
+
compute_frame: bool = True,
|
|
16
|
+
features: list[str] | None = None):
|
|
17
|
+
"""Return a virtual dataset concatenating all the input paths
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
paths:
|
|
22
|
+
Path of the input HDF5 files that will be concatenated along
|
|
23
|
+
the feature axis. The metadata will be taken from the first
|
|
24
|
+
file.
|
|
25
|
+
path_out:
|
|
26
|
+
If `None`, then the dataset is created in memory. If `True`
|
|
27
|
+
(default), create a file on disk. If a pathlib.Path is specified,
|
|
28
|
+
the dataset is written to that file. Note that datasets in memory
|
|
29
|
+
are likely not pickable (so don't use them for multiprocessing).
|
|
30
|
+
compute_frame:
|
|
31
|
+
Whether to compute the "events/frame" feature, taking the frame
|
|
32
|
+
data from the input files and properly incrementing them along
|
|
33
|
+
the file index.
|
|
34
|
+
features:
|
|
35
|
+
List of features to take from the input files.
|
|
36
|
+
|
|
37
|
+
Notes
|
|
38
|
+
-----
|
|
39
|
+
- If one of the input files does not contain a feature from the first
|
|
40
|
+
input `paths`, then a `ValueError` is raised. Use the `features`
|
|
41
|
+
argument to specify which features you need instead.
|
|
42
|
+
- Basins are not considered.
|
|
43
|
+
"""
|
|
44
|
+
h5kwargs = {"mode": "w", "libver": "latest"}
|
|
45
|
+
if isinstance(path_out, (pathlib.Path, str)):
|
|
46
|
+
h5kwargs["name"] = path_out
|
|
47
|
+
elif path_out is True:
|
|
48
|
+
tf = tempfile.NamedTemporaryFile(prefix="dcnum_vc_",
|
|
49
|
+
suffix=".hdf5",
|
|
50
|
+
delete=False)
|
|
51
|
+
tf.write(b"dummy")
|
|
52
|
+
h5kwargs["name"] = tf.name
|
|
53
|
+
tf.close()
|
|
54
|
+
elif path_out is None:
|
|
55
|
+
h5kwargs["name"] = io.BytesIO()
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Invalid type for `path_out`: {type(path_out)} ({path_out}")
|
|
59
|
+
|
|
60
|
+
if len(paths) == 0:
|
|
61
|
+
raise ValueError("Please specify at least one file in `paths`!")
|
|
62
|
+
elif len(paths) == 1:
|
|
63
|
+
warnings.warn("Only one file passed to `concatenated_hdf5_data`; this "
|
|
64
|
+
"is equivalent to using `HDF5Data`, but slower.")
|
|
65
|
+
|
|
66
|
+
frames = []
|
|
67
|
+
|
|
68
|
+
with h5py.File(**h5kwargs) as hv:
|
|
69
|
+
# determine the sizes of the input files
|
|
70
|
+
shapes = {}
|
|
71
|
+
dtypes = {}
|
|
72
|
+
size = 0
|
|
73
|
+
for ii, pp in enumerate(paths):
|
|
74
|
+
pp = pathlib.Path(pp).resolve()
|
|
75
|
+
with h5py.File(pp, libver="latest") as h5:
|
|
76
|
+
# get all feature keys
|
|
77
|
+
featsi = sorted(h5["events"].keys())
|
|
78
|
+
# get metadata
|
|
79
|
+
if ii == 0:
|
|
80
|
+
meta = dict(h5.attrs)
|
|
81
|
+
if not features:
|
|
82
|
+
features = featsi
|
|
83
|
+
# make sure number of features are consistent
|
|
84
|
+
if not set(features) <= set(featsi):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"File {pp} contains more features than {paths[0]}!")
|
|
87
|
+
# populate shapes for all features
|
|
88
|
+
for feat in features:
|
|
89
|
+
if not isinstance(h5["events"][feat], h5py.Dataset):
|
|
90
|
+
warnings.warn(
|
|
91
|
+
f"Ignoring {feat}; not implemented yet!")
|
|
92
|
+
continue
|
|
93
|
+
if feat in ["frame", "time"]:
|
|
94
|
+
continue
|
|
95
|
+
shapes.setdefault(feat, []).append(
|
|
96
|
+
h5["events"][feat].shape)
|
|
97
|
+
if ii == 0:
|
|
98
|
+
dtypes[feat] = h5["events"][feat].dtype
|
|
99
|
+
# increment size
|
|
100
|
+
size += h5["events"][features[0]].shape[0]
|
|
101
|
+
# remember the frame feature if requested
|
|
102
|
+
if compute_frame:
|
|
103
|
+
frames.append(h5["events/frame"][:])
|
|
104
|
+
|
|
105
|
+
# write metadata
|
|
106
|
+
hv.attrs.update(meta)
|
|
107
|
+
|
|
108
|
+
# Create the virtual datasets
|
|
109
|
+
for feat in shapes:
|
|
110
|
+
if len(shapes[feat][0]) == 1:
|
|
111
|
+
# scalar feature
|
|
112
|
+
shape = (sum([sh[0] for sh in shapes[feat]]))
|
|
113
|
+
else:
|
|
114
|
+
# non-scalar feature
|
|
115
|
+
length = (sum([sh[0] for sh in shapes[feat]]))
|
|
116
|
+
shape = list(shapes[feat][0])
|
|
117
|
+
shape[0] = length
|
|
118
|
+
shape = tuple(shape)
|
|
119
|
+
layout = h5py.VirtualLayout(shape=shape, dtype=dtypes[feat])
|
|
120
|
+
loc = 0
|
|
121
|
+
for jj, pp in enumerate(paths):
|
|
122
|
+
vsource = h5py.VirtualSource(pp, f"events/{feat}",
|
|
123
|
+
shape=shapes[feat][jj])
|
|
124
|
+
cursize = shapes[feat][jj][0]
|
|
125
|
+
layout[loc:loc+cursize] = vsource
|
|
126
|
+
loc += cursize
|
|
127
|
+
hv.create_virtual_dataset(f"/events/{feat}", layout, fillvalue=0)
|
|
128
|
+
|
|
129
|
+
if compute_frame:
|
|
130
|
+
# concatenate frames and store in dataset
|
|
131
|
+
frame_concat = np.zeros(size, dtype=np.uint64)
|
|
132
|
+
locf = 0 # indexing location
|
|
133
|
+
prevmax = 0 # maximum frame number stored so far in array
|
|
134
|
+
for fr in frames:
|
|
135
|
+
offset = prevmax + 1 - fr[0]
|
|
136
|
+
frame_concat[locf:locf+fr.size] = fr + offset
|
|
137
|
+
locf += fr.size
|
|
138
|
+
prevmax = fr[-1] + offset
|
|
139
|
+
hv.create_dataset("/events/frame", data=frame_concat)
|
|
140
|
+
|
|
141
|
+
# write metadata
|
|
142
|
+
hv.attrs["experiment:event count"] = size
|
|
143
|
+
|
|
144
|
+
data = HDF5Data(h5kwargs["name"])
|
|
145
|
+
return data
|
dcnum/read/hdf5_data.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
|
-
import io
|
|
5
4
|
import json
|
|
6
5
|
import numbers
|
|
7
6
|
import pathlib
|
|
8
|
-
import tempfile
|
|
9
7
|
from typing import Dict, BinaryIO, List
|
|
10
8
|
import uuid
|
|
11
9
|
import warnings
|
|
@@ -160,7 +158,10 @@ class HDF5Data:
|
|
|
160
158
|
if not hasattr(self, "h5"):
|
|
161
159
|
self.h5 = None
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
path = state["path"]
|
|
162
|
+
if isinstance(path, str):
|
|
163
|
+
path = pathlib.Path(path)
|
|
164
|
+
self.path = path
|
|
164
165
|
|
|
165
166
|
self.md5_5m = state["md5_5m"]
|
|
166
167
|
if self.md5_5m is None:
|
|
@@ -552,136 +553,10 @@ class HDF5Data:
|
|
|
552
553
|
return self._keys
|
|
553
554
|
|
|
554
555
|
|
|
555
|
-
def concatenated_hdf5_data(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
----------
|
|
563
|
-
paths:
|
|
564
|
-
Path of the input HDF5 files that will be concatenated along
|
|
565
|
-
the feature axis. The metadata will be taken from the first
|
|
566
|
-
file.
|
|
567
|
-
path_out:
|
|
568
|
-
If `None`, then the dataset is created in memory. If `True`
|
|
569
|
-
(default), create a file on disk. If a pathlib.Path is specified,
|
|
570
|
-
the dataset is written to that file. Note that datasets in memory
|
|
571
|
-
are likely not pickable (so don't use them for multiprocessing).
|
|
572
|
-
compute_frame:
|
|
573
|
-
Whether to compute the "events/frame" feature, taking the frame
|
|
574
|
-
data from the input files and properly incrementing them along
|
|
575
|
-
the file index.
|
|
576
|
-
features:
|
|
577
|
-
List of features to take from the input files.
|
|
578
|
-
|
|
579
|
-
Notes
|
|
580
|
-
-----
|
|
581
|
-
- If one of the input files does not contain a feature from the first
|
|
582
|
-
input `paths`, then a `ValueError` is raised. Use the `features`
|
|
583
|
-
argument to specify which features you need instead.
|
|
584
|
-
- Basins are not considered.
|
|
585
|
-
"""
|
|
586
|
-
h5kwargs = {"mode": "w", "libver": "latest"}
|
|
587
|
-
if isinstance(path_out, (pathlib.Path, str)):
|
|
588
|
-
h5kwargs["name"] = path_out
|
|
589
|
-
elif path_out is True:
|
|
590
|
-
tf = tempfile.NamedTemporaryFile(prefix="dcnum_vc_",
|
|
591
|
-
suffix=".hdf5",
|
|
592
|
-
delete=False)
|
|
593
|
-
tf.write(b"dummy")
|
|
594
|
-
h5kwargs["name"] = tf.name
|
|
595
|
-
tf.close()
|
|
596
|
-
elif path_out is None:
|
|
597
|
-
h5kwargs["name"] = io.BytesIO()
|
|
598
|
-
else:
|
|
599
|
-
raise ValueError(
|
|
600
|
-
f"Invalid type for `path_out`: {type(path_out)} ({path_out}")
|
|
601
|
-
|
|
602
|
-
if len(paths) == 0:
|
|
603
|
-
raise ValueError("Please specify at least one file in `paths`!")
|
|
604
|
-
elif len(paths) == 1:
|
|
605
|
-
warnings.warn("Only one file passed to `concatenated_hdf5_data`; this "
|
|
606
|
-
"is equivalent to using `HDF5Data`, but slower.")
|
|
607
|
-
|
|
608
|
-
frames = []
|
|
609
|
-
|
|
610
|
-
with h5py.File(**h5kwargs) as hv:
|
|
611
|
-
# determine the sizes of the input files
|
|
612
|
-
shapes = {}
|
|
613
|
-
dtypes = {}
|
|
614
|
-
size = 0
|
|
615
|
-
for ii, pp in enumerate(paths):
|
|
616
|
-
pp = pathlib.Path(pp).resolve()
|
|
617
|
-
with h5py.File(pp, libver="latest") as h5:
|
|
618
|
-
# get all feature keys
|
|
619
|
-
featsi = sorted(h5["events"].keys())
|
|
620
|
-
# get metadata
|
|
621
|
-
if ii == 0:
|
|
622
|
-
meta = dict(h5.attrs)
|
|
623
|
-
if not features:
|
|
624
|
-
features = featsi
|
|
625
|
-
# make sure number of features are consistent
|
|
626
|
-
if not set(features) <= set(featsi):
|
|
627
|
-
raise ValueError(
|
|
628
|
-
f"File {pp} contains more features than {paths[0]}!")
|
|
629
|
-
# populate shapes for all features
|
|
630
|
-
for feat in features:
|
|
631
|
-
if not isinstance(h5["events"][feat], h5py.Dataset):
|
|
632
|
-
warnings.warn(
|
|
633
|
-
f"Ignoring {feat}; not implemented yet!")
|
|
634
|
-
continue
|
|
635
|
-
if feat in ["frame", "time"]:
|
|
636
|
-
continue
|
|
637
|
-
shapes.setdefault(feat, []).append(
|
|
638
|
-
h5["events"][feat].shape)
|
|
639
|
-
if ii == 0:
|
|
640
|
-
dtypes[feat] = h5["events"][feat].dtype
|
|
641
|
-
# increment size
|
|
642
|
-
size += h5["events"][features[0]].shape[0]
|
|
643
|
-
# remember the frame feature if requested
|
|
644
|
-
if compute_frame:
|
|
645
|
-
frames.append(h5["events/frame"][:])
|
|
646
|
-
|
|
647
|
-
# write metadata
|
|
648
|
-
hv.attrs.update(meta)
|
|
649
|
-
|
|
650
|
-
# Create the virtual datasets
|
|
651
|
-
for feat in shapes:
|
|
652
|
-
if len(shapes[feat][0]) == 1:
|
|
653
|
-
# scalar feature
|
|
654
|
-
shape = (sum([sh[0] for sh in shapes[feat]]))
|
|
655
|
-
else:
|
|
656
|
-
# non-scalar feature
|
|
657
|
-
length = (sum([sh[0] for sh in shapes[feat]]))
|
|
658
|
-
shape = list(shapes[feat][0])
|
|
659
|
-
shape[0] = length
|
|
660
|
-
shape = tuple(shape)
|
|
661
|
-
layout = h5py.VirtualLayout(shape=shape, dtype=dtypes[feat])
|
|
662
|
-
loc = 0
|
|
663
|
-
for jj, pp in enumerate(paths):
|
|
664
|
-
vsource = h5py.VirtualSource(pp, f"events/{feat}",
|
|
665
|
-
shape=shapes[feat][jj])
|
|
666
|
-
cursize = shapes[feat][jj][0]
|
|
667
|
-
layout[loc:loc+cursize] = vsource
|
|
668
|
-
loc += cursize
|
|
669
|
-
hv.create_virtual_dataset(f"/events/{feat}", layout, fillvalue=0)
|
|
670
|
-
|
|
671
|
-
if compute_frame:
|
|
672
|
-
# concatenate frames and store in dataset
|
|
673
|
-
frame_concat = np.zeros(size, dtype=np.uint64)
|
|
674
|
-
locf = 0 # indexing location
|
|
675
|
-
prevmax = 0 # maximum frame number stored so far in array
|
|
676
|
-
for fr in frames:
|
|
677
|
-
offset = prevmax + 1 - fr[0]
|
|
678
|
-
frame_concat[locf:locf+fr.size] = fr + offset
|
|
679
|
-
locf += fr.size
|
|
680
|
-
prevmax = fr[-1] + offset
|
|
681
|
-
hv.create_dataset("/events/frame", data=frame_concat)
|
|
682
|
-
|
|
683
|
-
# write metadata
|
|
684
|
-
hv.attrs["experiment:event count"] = size
|
|
685
|
-
|
|
686
|
-
data = HDF5Data(h5kwargs["name"])
|
|
687
|
-
return data
|
|
556
|
+
def concatenated_hdf5_data(*args, **kwargs):
|
|
557
|
+
warnings.warn(
|
|
558
|
+
"Please use `dcnum.read.hdf5_concat.concatenated_hdf5_data`. "
|
|
559
|
+
"Accessing this method via `dcnum.read.hdf5_data` is deprecated.",
|
|
560
|
+
DeprecationWarning)
|
|
561
|
+
from . import hdf5_concat
|
|
562
|
+
return hdf5_concat.concatenated_hdf5_data(*args, **kwargs)
|
|
@@ -52,9 +52,10 @@ class TorchSegmenterBase(Segmenter):
|
|
|
52
52
|
segmenter_kwargs: dict
|
|
53
53
|
Keyword arguments for the segmenter
|
|
54
54
|
meta: dict
|
|
55
|
-
Dictionary of metadata from an :class
|
|
55
|
+
Dictionary of metadata from an :class:`.hdf5_data.HDF5Data`
|
|
56
|
+
instance
|
|
56
57
|
logs: dict
|
|
57
|
-
Dictionary of logs from an :class
|
|
58
|
+
Dictionary of logs from an :class:`.hdf5_data.HDF5Data` instance
|
|
58
59
|
|
|
59
60
|
Returns
|
|
60
61
|
-------
|
|
@@ -11,6 +11,7 @@ def postprocess_masks(masks,
|
|
|
11
11
|
"""Postprocess mask images from ML segmenters
|
|
12
12
|
|
|
13
13
|
The transformation includes:
|
|
14
|
+
|
|
14
15
|
- Revert the cropping and padding operations done in
|
|
15
16
|
:func:`.preprocess_images` by padding with zeros and cropping.
|
|
16
17
|
- If the original image shape is larger than the mask image shape,
|
|
@@ -11,6 +11,7 @@ def preprocess_images(images: np.ndarray,
|
|
|
11
11
|
"""Transform image data to something torch models expect
|
|
12
12
|
|
|
13
13
|
The transformation includes:
|
|
14
|
+
|
|
14
15
|
- normalization (division by 255, subtraction of mean, division by std)
|
|
15
16
|
- cropping and padding of the input images to `image_shape`. For padding,
|
|
16
17
|
the median of each *individual* image is used.
|
dcnum/segm/segmenter.py
CHANGED
|
@@ -26,17 +26,23 @@ class SegmenterNotApplicableError(BaseException):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Segmenter(abc.ABC):
|
|
29
|
-
#: Required hardware ("cpu" or "gpu") defined in first-level subclass.
|
|
30
29
|
hardware_processor = "none"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#: have to call `process_mask` in your `segment_algorithm` implementation.
|
|
30
|
+
"""Required hardware ("cpu" or "gpu") defined in first-level subclass."""
|
|
31
|
+
|
|
34
32
|
mask_postprocessing = True
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
"""Whether to enable mask post-processing.
|
|
34
|
+
If disabled, you should make sure that your mask is properly defined
|
|
35
|
+
and cleaned or you have to call `process_mask` in your
|
|
36
|
+
``segment_algorithm`` implementation.
|
|
37
|
+
"""
|
|
38
|
+
|
|
37
39
|
mask_default_kwargs = {}
|
|
38
|
-
|
|
40
|
+
"""Default keyword arguments for mask post-processing.
|
|
41
|
+
See `process_mask` for available options.
|
|
42
|
+
"""
|
|
43
|
+
|
|
39
44
|
requires_background_correction = False
|
|
45
|
+
"""Whether the segmenter requires a background-corrected image"""
|
|
40
46
|
|
|
41
47
|
def __init__(self,
|
|
42
48
|
*,
|
|
@@ -46,10 +52,11 @@ class Segmenter(abc.ABC):
|
|
|
46
52
|
"""Base segmenter class
|
|
47
53
|
|
|
48
54
|
This is the base segmenter class for the multiprocessing operation
|
|
49
|
-
segmenter :class:`.MPOSegmenter` (multiple
|
|
50
|
-
and each of them works on a queue of images)
|
|
51
|
-
|
|
52
|
-
segmentation on
|
|
55
|
+
segmenter :class:`.segmenter_mpo.MPOSegmenter` (multiple
|
|
56
|
+
subprocesses are spawned and each of them works on a queue of images)
|
|
57
|
+
and the single-threaded operation segmenter
|
|
58
|
+
:class:`.segmenter_sto.STOSegmenter` (e.g. for batch segmentation on
|
|
59
|
+
a GPU).
|
|
53
60
|
|
|
54
61
|
Parameters
|
|
55
62
|
----------
|
|
@@ -64,12 +71,15 @@ class Segmenter(abc.ABC):
|
|
|
64
71
|
self.logger = logging.getLogger(__name__).getChild(
|
|
65
72
|
self.__class__.__name__)
|
|
66
73
|
spec = inspect.getfullargspec(self.segment_algorithm)
|
|
67
|
-
|
|
74
|
+
|
|
68
75
|
self.kwargs = spec.kwonlydefaults or {}
|
|
76
|
+
"""custom keyword arguments for the subclassing segmenter"""
|
|
77
|
+
|
|
69
78
|
self.kwargs.update(kwargs)
|
|
70
79
|
|
|
71
|
-
#: keyword arguments for mask post-processing
|
|
72
80
|
self.kwargs_mask = {}
|
|
81
|
+
"""keyword arguments for mask post-processing"""
|
|
82
|
+
|
|
73
83
|
if self.mask_postprocessing:
|
|
74
84
|
spec_mask = inspect.getfullargspec(self.process_mask)
|
|
75
85
|
self.kwargs_mask.update(spec_mask.kwonlydefaults or {})
|
|
@@ -108,7 +118,7 @@ class Segmenter(abc.ABC):
|
|
|
108
118
|
KEY:KW_APPROACH:KW_MASK
|
|
109
119
|
|
|
110
120
|
Where KEY is e.g. "legacy" or "watershed", and KW_APPROACH is a
|
|
111
|
-
list of keyword arguments for
|
|
121
|
+
list of keyword arguments for ``segment_algorithm``, e.g.::
|
|
112
122
|
|
|
113
123
|
thresh=-6^blur=0
|
|
114
124
|
|
|
@@ -296,10 +306,10 @@ class Segmenter(abc.ABC):
|
|
|
296
306
|
|
|
297
307
|
@functools.cache
|
|
298
308
|
def segment_algorithm_wrapper(self):
|
|
299
|
-
"""Wraps
|
|
309
|
+
"""Wraps ``self.segment_algorithm`` to only accept an image
|
|
300
310
|
|
|
301
|
-
The static method
|
|
302
|
-
keyword arguments
|
|
311
|
+
The static method ``self.segment_algorithm`` may optionally accept
|
|
312
|
+
keyword arguments ``self.kwargs``. This wrapper returns the
|
|
303
313
|
wrapped method that only accepts the image as an argument. This
|
|
304
314
|
makes sense if you want to unify
|
|
305
315
|
"""
|
|
@@ -336,7 +346,7 @@ class Segmenter(abc.ABC):
|
|
|
336
346
|
additional background offset values that should be subtracted
|
|
337
347
|
from the image data before segmentation. Should only be
|
|
338
348
|
used in combination with segmenters that have
|
|
339
|
-
|
|
349
|
+
``requires_background_correction`` set to True.
|
|
340
350
|
"""
|
|
341
351
|
images = image_data.get_chunk(chunk)
|
|
342
352
|
if bg_off is not None:
|
|
@@ -364,9 +374,10 @@ class Segmenter(abc.ABC):
|
|
|
364
374
|
segmenter_kwargs: dict
|
|
365
375
|
Keyword arguments for the segmenter
|
|
366
376
|
meta: dict
|
|
367
|
-
Dictionary of metadata from an :class
|
|
377
|
+
Dictionary of metadata from an :class:`.hdf5_data.HDF5Data`
|
|
378
|
+
instance
|
|
368
379
|
logs: dict
|
|
369
|
-
Dictionary of logs from an :class
|
|
380
|
+
Dictionary of logs from an :class:`.hdf5_data.HDF5Data` instance
|
|
370
381
|
|
|
371
382
|
Returns
|
|
372
383
|
-------
|
|
@@ -36,8 +36,8 @@ class SegmenterManagerThread(threading.Thread):
|
|
|
36
36
|
its job for a slot, the slot value will be set to "e" (for
|
|
37
37
|
"task is with feature extractor").
|
|
38
38
|
slot_chunks:
|
|
39
|
-
For each slot in
|
|
40
|
-
on which chunk in
|
|
39
|
+
For each slot in ``slot_states``, this shared array defines
|
|
40
|
+
on which chunk in ``image_data`` the segmentation took place.
|
|
41
41
|
bg_off:
|
|
42
42
|
1d array containing additional background image offset values
|
|
43
43
|
that are added to each background image before subtraction
|
|
@@ -45,10 +45,10 @@ class SegmenterManagerThread(threading.Thread):
|
|
|
45
45
|
|
|
46
46
|
Notes
|
|
47
47
|
-----
|
|
48
|
-
This manager keeps a list
|
|
49
|
-
slots just like
|
|
48
|
+
This manager keeps a list ``labels_list`` which enumerates the
|
|
49
|
+
slots just like ``slot_states` and ``slot_chunks`` do. For each
|
|
50
50
|
slot, this list contains the labeled image data (integer-valued)
|
|
51
|
-
for the input
|
|
51
|
+
for the input ``image_data`` chunks.
|
|
52
52
|
|
|
53
53
|
The working principle of this `SegmenterManagerThread` allows
|
|
54
54
|
the user to define a fixed number of slots on which the segmenter
|
|
@@ -61,21 +61,28 @@ class SegmenterManagerThread(threading.Thread):
|
|
|
61
61
|
super(SegmenterManagerThread, self).__init__(
|
|
62
62
|
name="SegmenterManager", *args, **kwargs)
|
|
63
63
|
self.logger = logging.getLogger("dcnum.segm.SegmenterManagerThread")
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
self.segmenter = segmenter
|
|
66
|
-
|
|
66
|
+
"""Segmenter instance"""
|
|
67
|
+
|
|
67
68
|
self.image_data = image_data
|
|
68
|
-
|
|
69
|
+
"""Image data which is being segmented"""
|
|
70
|
+
|
|
69
71
|
self.bg_off = (
|
|
70
72
|
bg_off if self.segmenter.requires_background_correction else None)
|
|
71
|
-
|
|
73
|
+
"""Additional, optional background offset"""
|
|
74
|
+
|
|
72
75
|
self.slot_states = slot_states
|
|
73
|
-
|
|
76
|
+
"""Slot states"""
|
|
77
|
+
|
|
74
78
|
self.slot_chunks = slot_chunks
|
|
75
|
-
|
|
79
|
+
"""Current slot chunk index for the slot states"""
|
|
80
|
+
|
|
76
81
|
self.labels_list = [None] * len(self.slot_states)
|
|
77
|
-
|
|
82
|
+
"""List containing the segmented labels of each slot"""
|
|
83
|
+
|
|
78
84
|
self.t_count = 0
|
|
85
|
+
"""Time counter for segmentation"""
|
|
79
86
|
|
|
80
87
|
def run(self):
|
|
81
88
|
num_slots = len(self.slot_states)
|
dcnum/segm/segmenter_mpo.py
CHANGED
|
@@ -35,7 +35,7 @@ class MPOSegmenter(Segmenter, abc.ABC):
|
|
|
35
35
|
debug: bool
|
|
36
36
|
Debugging parameters
|
|
37
37
|
kwargs:
|
|
38
|
-
Additional, optional keyword arguments for
|
|
38
|
+
Additional, optional keyword arguments for ``segment_algorithm``
|
|
39
39
|
defined in the subclass.
|
|
40
40
|
"""
|
|
41
41
|
super(MPOSegmenter, self).__init__(kwargs_mask=kwargs_mask,
|
|
@@ -145,7 +145,7 @@ class MPOSegmenter(Segmenter, abc.ABC):
|
|
|
145
145
|
"""Perform batch segmentation of `images`
|
|
146
146
|
|
|
147
147
|
Before segmentation, an optional background offset correction with
|
|
148
|
-
|
|
148
|
+
``bg_off`` is performed. After segmentation, mask postprocessing is
|
|
149
149
|
performed according to the class definition.
|
|
150
150
|
|
|
151
151
|
Parameters
|
|
@@ -264,7 +264,7 @@ class MPOSegmenter(Segmenter, abc.ABC):
|
|
|
264
264
|
"""Return the integer label image for an input image
|
|
265
265
|
|
|
266
266
|
Before segmentation, an optional background offset correction with
|
|
267
|
-
|
|
267
|
+
``bg_off`` is performed. After segmentation, mask postprocessing is
|
|
268
268
|
performed according to the class definition.
|
|
269
269
|
"""
|
|
270
270
|
segm_wrap = self.segment_algorithm_wrapper()
|
|
@@ -296,7 +296,7 @@ class MPOSegmenterWorker:
|
|
|
296
296
|
|
|
297
297
|
Parameters
|
|
298
298
|
----------
|
|
299
|
-
segmenter: MPOSegmenter
|
|
299
|
+
segmenter: .segmenter_mpo.MPOSegmenter
|
|
300
300
|
The segmentation instance
|
|
301
301
|
sl_start: int
|
|
302
302
|
Start of slice of input array to process
|