dcnum 0.24.0__tar.gz → 0.25.1__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.
- {dcnum-0.24.0 → dcnum-0.25.1}/.github/workflows/check.yml +2 -4
- {dcnum-0.24.0 → dcnum-0.25.1}/CHANGELOG +9 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/PKG-INFO +1 -1
- dcnum-0.25.1/benchmark/.gitignore +2 -0
- dcnum-0.25.1/benchmark/Readme.md +11 -0
- dcnum-0.25.1/benchmark/benchmark.py +54 -0
- dcnum-0.25.1/benchmark/bm_write_deque_writer_thread.py +46 -0
- dcnum-0.25.1/benchmark/bm_write_queue_collector_thread.py +45 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/_version.py +2 -2
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_background/bg_sparse_median.py +2 -2
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/logic/ctrl.py +7 -2
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/meta/ppid.py +3 -2
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/read/__init__.py +1 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/read/cache.py +4 -3
- dcnum-0.25.1/src/dcnum/read/detect_flicker.py +44 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/write/queue_collector_thread.py +7 -14
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/write/writer.py +3 -3
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum.egg-info/PKG-INFO +1 -1
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum.egg-info/SOURCES.txt +7 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_base.py +8 -0
- dcnum-0.25.1/tests/test_read_detect_flicker.py +87 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_read_hdf5.py +13 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_write_queue_collector_thread.py +1 -8
- {dcnum-0.24.0 → dcnum-0.25.1}/.github/workflows/deploy_pypi.yml +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/.gitignore +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/.readthedocs.yml +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/LICENSE +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/README.rst +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/docs/conf.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/docs/extensions/github_changelog.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/docs/index.rst +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/docs/requirements.txt +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/pyproject.toml +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/setup.cfg +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/event_extractor_manager_thread.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_background/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_background/base.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_background/bg_copy.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_background/bg_roll_median.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_brightness/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_brightness/bright_all.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_brightness/common.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_contour/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_contour/contour.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_contour/moments.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_contour/volume.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_texture/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_texture/common.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/feat_texture/tex_all.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/gate.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/feat/queue_event_extractor.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/logic/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/logic/job.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/logic/json_encoder.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/meta/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/meta/paths.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/read/const.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/read/hdf5_data.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/read/mapped.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_thresh.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/segm_torch_base.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/segm_torch_mpo.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/segm_torch_sto.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/torch_model.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/torch_postproc.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segm_torch/torch_preproc.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segmenter.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segmenter_manager_thread.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segmenter_mpo.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/segm/segmenter_sto.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/write/__init__.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum/write/deque_writer_thread.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum.egg-info/dependency_links.txt +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum.egg-info/requires.txt +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/src/dcnum.egg-info/top_level.txt +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/conftest.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_cytoshot_full-features_2023.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_cytoshot_full-features_2024.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_shapein_empty.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_shapein_raw-with-variable-length-logs.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/segm-torch-model_unet-dcnum-test_g1_910c2.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/data/segm-torch-test-data_unet-dcnum-test_g1_910c2.zip +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/helper_methods.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/requirements.txt +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_background_base.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_background_bg_copy.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_background_bg_roll_median.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_background_bg_sparsemed.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_brightness.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_event_extractor_manager.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_gate.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_haralick.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_moments_based.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_moments_based_extended.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_feat_volume.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_init.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_logic_job.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_logic_join.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_logic_json.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_logic_pipeline.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_paths.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_bg.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_data.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_feat.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_gate.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_meta_ppid_segm.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_read_basin.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_read_concat_hdf5.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_read_hdf5_basins.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_read_hdf5_index_mapping.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_base.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_mpo.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_no_mask_proc.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_sto.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_thresh.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_torch.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_segm_torch_preproc.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_write_deque_writer_thread.py +0 -0
- {dcnum-0.24.0 → dcnum-0.25.1}/tests/test_write_writer.py +0 -0
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
with:
|
|
20
20
|
fetch-depth: 0
|
|
21
21
|
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@main
|
|
23
23
|
with:
|
|
24
24
|
python-version: ${{ matrix.python-version }}
|
|
25
25
|
- name: Install dependencies
|
|
@@ -29,8 +29,6 @@ jobs:
|
|
|
29
29
|
python -m pip install coverage flake8 pytest
|
|
30
30
|
- name: Install dcnum
|
|
31
31
|
run: |
|
|
32
|
-
# mahotas 1.4.15 does not yet support numpy 2.0
|
|
33
|
-
pip install "numpy<2"
|
|
34
32
|
pip install .[torch]
|
|
35
33
|
- name: List installed packages
|
|
36
34
|
run: |
|
|
@@ -42,4 +40,4 @@ jobs:
|
|
|
42
40
|
run: |
|
|
43
41
|
coverage run --source=dcnum -m pytest tests
|
|
44
42
|
- name: Upload coverage to Codecov
|
|
45
|
-
uses: codecov/codecov-action@
|
|
43
|
+
uses: codecov/codecov-action@v4
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
0.25.1
|
|
2
|
+
- fix: catch ValueError when computing relative path on different anchors
|
|
3
|
+
- ref: replace np.string_ with np.bytes_
|
|
4
|
+
- ref: remove unused `data` argument to QueueCollectorThread
|
|
5
|
+
- ref: minor speed-up for QueueCollectorThread
|
|
6
|
+
0.25.0
|
|
7
|
+
- feat: identify flickering in raw data via dcnum.read.detect_flickering
|
|
8
|
+
- fix: handle out-of-bounds slice indexing for BaseImageChunkCache
|
|
9
|
+
- fix: np.bool_ and np.floating not recognized in PPID parsing
|
|
1
10
|
0.24.0
|
|
2
11
|
- feat: add support for internal basins
|
|
3
12
|
- feat: "image_bg" as internal basin for "sparsemed" background computer
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
This directory contains benchmarking scripts used for optimizing dcnum performance.
|
|
2
|
+
To run all benchmarks, execute `python benchmark.py`. You can also specify
|
|
3
|
+
individual benchmarks or a list of benchmarks (path to `bm_*.py` file)
|
|
4
|
+
as arguments to `benchmark.py`.
|
|
5
|
+
|
|
6
|
+
The benchmarks are also ideal use cases for identifying bottlenecks with
|
|
7
|
+
tools such as [line profiler](https://kernprof.readthedocs.io/en/latest/),
|
|
8
|
+
since benchmarks can be designed to run in single threads.
|
|
9
|
+
|
|
10
|
+
pip install line_profiler
|
|
11
|
+
kernprof -lv benchmark.py
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import timeit
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
here = pathlib.Path(__file__).parent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_underline(msg):
|
|
15
|
+
print(msg)
|
|
16
|
+
print("-" * len(msg))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run_benchmark(bm_path, repeats=5):
|
|
20
|
+
print_underline(f"Running {bm_path}")
|
|
21
|
+
bm_path = pathlib.Path(bm_path).resolve()
|
|
22
|
+
os.chdir(f"{bm_path.parent}")
|
|
23
|
+
bm_mod = importlib.import_module(f"{bm_path.stem}")
|
|
24
|
+
|
|
25
|
+
reps = []
|
|
26
|
+
print("Running...", end="\r")
|
|
27
|
+
for ii in range(repeats):
|
|
28
|
+
t = timeit.timeit(bm_mod.main, setup=bm_mod.setup, number=1)
|
|
29
|
+
reps.append(t)
|
|
30
|
+
print(f"Running {ii + 1}/{repeats}", end="\r")
|
|
31
|
+
print(reps)
|
|
32
|
+
print(f"best={min(reps):.3g}, mean={np.mean(reps):.3g}")
|
|
33
|
+
return reps
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
benchmark_paths = []
|
|
38
|
+
for arg in sys.argv[1:]:
|
|
39
|
+
if arg.startswith("bm_"):
|
|
40
|
+
benchmark_paths.append(arg)
|
|
41
|
+
|
|
42
|
+
if not benchmark_paths:
|
|
43
|
+
print("No benchmarking script specified, running all benchmarks.")
|
|
44
|
+
benchmark_paths = here.glob("bm_*.py")
|
|
45
|
+
|
|
46
|
+
results = {}
|
|
47
|
+
|
|
48
|
+
for bmp in sorted(benchmark_paths):
|
|
49
|
+
bmp = pathlib.Path(bmp)
|
|
50
|
+
print("")
|
|
51
|
+
res = run_benchmark(bmp)
|
|
52
|
+
with bmp.with_suffix(".txt").open("a") as fd:
|
|
53
|
+
fd.write(time.strftime(f"%Y-%m-%d_%H.%M.%S {res}\n"))
|
|
54
|
+
print("")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
from collections import deque
|
|
3
|
+
import pathlib
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import multiprocessing as mp
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from dcnum import write
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
mp_spawn = mp.get_context('spawn')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def setup():
|
|
18
|
+
global path_out
|
|
19
|
+
global writer_dq
|
|
20
|
+
total_frames = 3000
|
|
21
|
+
batch_size = 500
|
|
22
|
+
num_batches = 6
|
|
23
|
+
assert batch_size * num_batches == total_frames
|
|
24
|
+
|
|
25
|
+
writer_dq = deque()
|
|
26
|
+
# Create 1000 events with at most two repetitions in a frame
|
|
27
|
+
np.random.seed(42)
|
|
28
|
+
rng = np.random.default_rng()
|
|
29
|
+
|
|
30
|
+
# create a sample event
|
|
31
|
+
for ii in range(num_batches):
|
|
32
|
+
writer_dq.append(("mask", rng.random((batch_size, 80, 320)) > .5))
|
|
33
|
+
writer_dq.append(("temp", rng.normal(23, size=batch_size)))
|
|
34
|
+
|
|
35
|
+
temp_dir = tempfile.mkdtemp(prefix=pathlib.Path(__file__).name)
|
|
36
|
+
atexit.register(shutil.rmtree, temp_dir, ignore_errors=True, onerror=None)
|
|
37
|
+
path_out = pathlib.Path(temp_dir) / "out.rtdc"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main():
|
|
41
|
+
thr_drw = write.DequeWriterThread(
|
|
42
|
+
path_out=path_out,
|
|
43
|
+
dq=writer_dq,
|
|
44
|
+
)
|
|
45
|
+
thr_drw.may_stop_loop = True
|
|
46
|
+
thr_drw.run()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
import multiprocessing as mp
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from dcnum import write
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
mp_spawn = mp.get_context('spawn')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def setup():
|
|
13
|
+
global event_queue
|
|
14
|
+
global writer_dq
|
|
15
|
+
global feat_nevents
|
|
16
|
+
batch_size = 1000
|
|
17
|
+
num_batches = 3
|
|
18
|
+
num_events = batch_size * num_batches
|
|
19
|
+
event_queue = mp.Queue()
|
|
20
|
+
writer_dq = deque()
|
|
21
|
+
feat_nevents = mp_spawn.Array("i", num_events)
|
|
22
|
+
|
|
23
|
+
# Create 1000 events with at most two repetitions in a frame
|
|
24
|
+
np.random.seed(42)
|
|
25
|
+
rng = np.random.default_rng()
|
|
26
|
+
number_order = rng.choice(batch_size, size=batch_size, replace=False)
|
|
27
|
+
|
|
28
|
+
# create a sample event
|
|
29
|
+
event = {
|
|
30
|
+
"temp": np.atleast_1d(rng.normal(23)),
|
|
31
|
+
"mask": rng.random((1, 80, 320)) > .5,
|
|
32
|
+
}
|
|
33
|
+
for ii in range(num_batches):
|
|
34
|
+
for idx in number_order:
|
|
35
|
+
event_queue.put((ii*batch_size + idx, event))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
thr_coll = write.QueueCollectorThread(
|
|
40
|
+
event_queue=event_queue,
|
|
41
|
+
writer_dq=writer_dq,
|
|
42
|
+
feat_nevents=feat_nevents,
|
|
43
|
+
write_threshold=500,
|
|
44
|
+
)
|
|
45
|
+
thr_coll.run()
|
|
@@ -59,7 +59,7 @@ class BackgroundSparseMed(Background):
|
|
|
59
59
|
offset_correction: bool
|
|
60
60
|
The sparse median background correction produces one median
|
|
61
61
|
image for multiple input frames (BTW this also leads to very
|
|
62
|
-
efficient data storage with HDF5
|
|
62
|
+
efficient data storage with internal HDF5 basins). In
|
|
63
63
|
case the input frames are subject to frame-by-frame brightness
|
|
64
64
|
variations (e.g. flickering of the illumination source), it
|
|
65
65
|
is useful to have an offset value per frame that can then be
|
|
@@ -226,7 +226,7 @@ class BackgroundSparseMed(Background):
|
|
|
226
226
|
offset_correction: bool
|
|
227
227
|
The sparse median background correction produces one median
|
|
228
228
|
image for multiple input frames (BTW this also leads to very
|
|
229
|
-
efficient data storage with HDF5
|
|
229
|
+
efficient data storage with internal HDF5 basins). In
|
|
230
230
|
case the input frames are subject to frame-by-frame brightness
|
|
231
231
|
variations (e.g. flickering of the illumination source), it
|
|
232
232
|
is useful to have an offset value per frame that can then be
|
|
@@ -679,8 +679,14 @@ class DCNumJobRunner(threading.Thread):
|
|
|
679
679
|
self.logger.debug(f"Creating basin for {feats}")
|
|
680
680
|
# Relative and absolute paths.
|
|
681
681
|
pin = pathlib.Path(hin.filename).resolve()
|
|
682
|
+
paths = [pin]
|
|
682
683
|
pout = pathlib.Path(hout.filename).resolve().parent
|
|
683
|
-
|
|
684
|
+
try:
|
|
685
|
+
paths.append(os.path.relpath(pin, pout))
|
|
686
|
+
except ValueError:
|
|
687
|
+
# This means it is impossible to compute a relative
|
|
688
|
+
# path (e.g. different drive letter on Windows).
|
|
689
|
+
pass
|
|
684
690
|
hw.store_basin(name="dcnum basin",
|
|
685
691
|
features=feats,
|
|
686
692
|
mapping=basinmap0,
|
|
@@ -776,7 +782,6 @@ class DCNumJobRunner(threading.Thread):
|
|
|
776
782
|
|
|
777
783
|
# Start the data collection thread
|
|
778
784
|
thr_coll = QueueCollectorThread(
|
|
779
|
-
data=self.dtin,
|
|
780
785
|
event_queue=fe_kwargs["event_queue"],
|
|
781
786
|
writer_dq=writer_dq,
|
|
782
787
|
feat_nevents=fe_kwargs["feat_nevents"],
|
|
@@ -7,6 +7,7 @@ import pathlib
|
|
|
7
7
|
from typing import Dict, List, Protocol
|
|
8
8
|
import warnings
|
|
9
9
|
|
|
10
|
+
import numpy as np
|
|
10
11
|
|
|
11
12
|
#: Increment this string if there are breaking changes that make
|
|
12
13
|
#: previous pipelines unreproducible.
|
|
@@ -140,9 +141,9 @@ def kwargs_to_ppid(cls: ClassWithPPIDCapabilities,
|
|
|
140
141
|
path = pathlib.Path(val)
|
|
141
142
|
if path.exists():
|
|
142
143
|
val = path.name
|
|
143
|
-
if isinstance(val, bool):
|
|
144
|
+
if isinstance(val, (bool, np.bool_)):
|
|
144
145
|
val = int(val) # do not print e.g. "True"
|
|
145
|
-
elif isinstance(val, float):
|
|
146
|
+
elif isinstance(val, (float, np.floating)):
|
|
146
147
|
if val == int(val):
|
|
147
148
|
val = int(val) # omit the ".0" at the end
|
|
148
149
|
concat_strings.append(f"{abr}={val}")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# flake8: noqa: F401
|
|
2
2
|
from .cache import md5sum
|
|
3
3
|
from .const import PROTECTED_FEATURES
|
|
4
|
+
from .detect_flicker import detect_flickering
|
|
4
5
|
from .hdf5_data import HDF5Data, HDF5ImageCache, concatenated_hdf5_data
|
|
5
6
|
from .mapped import get_mapping_indices, get_mapped_object
|
|
@@ -36,9 +36,10 @@ class BaseImageChunkCache(abc.ABC):
|
|
|
36
36
|
def __getitem__(self, index):
|
|
37
37
|
if isinstance(index, (slice, list, np.ndarray)):
|
|
38
38
|
if isinstance(index, slice):
|
|
39
|
-
indices = np.arange(
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
indices = np.arange(
|
|
40
|
+
index.start or 0,
|
|
41
|
+
min(index.stop, len(self)) if index.stop else len(self),
|
|
42
|
+
index.step)
|
|
42
43
|
else:
|
|
43
44
|
indices = index
|
|
44
45
|
array_out = np.empty((len(indices),) + self.image_shape,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .hdf5_data import HDF5Data
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def detect_flickering(image_data: np.ndarray | HDF5Data,
|
|
7
|
+
roi_height: int = 10,
|
|
8
|
+
brightness_threshold: float = 2.5,
|
|
9
|
+
count_threshold: int = 5,
|
|
10
|
+
max_frames: int = 1000):
|
|
11
|
+
"""Determine whether an image series experiences flickering
|
|
12
|
+
|
|
13
|
+
Flickering is an unwelcome phenomenon due to a faulty data
|
|
14
|
+
acquisition device. For instance, if there is random voltage noise in
|
|
15
|
+
the electronics managing the LED power, then the brightness of the
|
|
16
|
+
LED will vary randomly when the noise signal overlaps with the flash
|
|
17
|
+
triggering signal.
|
|
18
|
+
|
|
19
|
+
If flickering is detected, you should use the "sparsemed" background
|
|
20
|
+
computation with `offset_correction` set to True.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
image_data:
|
|
25
|
+
sliceable object (e.g. numpy array or HDF5Data) containing
|
|
26
|
+
image data.
|
|
27
|
+
roi_height: int
|
|
28
|
+
height of the ROI in pixels for which to search for flickering;
|
|
29
|
+
the entire width of the image is used
|
|
30
|
+
brightness_threshold: float
|
|
31
|
+
brightness difference between individual ROIs median and median
|
|
32
|
+
of all ROI medians leading to a positive flickering event
|
|
33
|
+
count_threshold: int
|
|
34
|
+
minimum number of flickering events that would lead to a positive
|
|
35
|
+
flickering decision
|
|
36
|
+
max_frames: int
|
|
37
|
+
maximum number of frames to include in the flickering analysis
|
|
38
|
+
"""
|
|
39
|
+
# slice event axis first in case we have and HDF5Data instance
|
|
40
|
+
roi_data = image_data[:max_frames][:, :roi_height, :]
|
|
41
|
+
roi_median = np.median(roi_data, axis=(1, 2))
|
|
42
|
+
roi_offset = roi_median - np.median(roi_median)
|
|
43
|
+
flickering_events = np.sum(np.abs(roi_offset) >= abs(brightness_threshold))
|
|
44
|
+
return flickering_events >= count_threshold
|
|
@@ -8,8 +8,6 @@ from typing import List
|
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
|
|
11
|
-
from ..read import HDF5Data
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
class EventStash:
|
|
15
13
|
def __init__(self,
|
|
@@ -61,11 +59,10 @@ class EventStash:
|
|
|
61
59
|
Event dictionary
|
|
62
60
|
"""
|
|
63
61
|
idx_loc = index - self.index_offset
|
|
64
|
-
idx_stop = self.nev_idx[idx_loc]
|
|
65
|
-
self._tracker[idx_loc] = True
|
|
66
62
|
|
|
67
63
|
if events:
|
|
68
64
|
slice_loc = None
|
|
65
|
+
idx_stop = self.nev_idx[idx_loc]
|
|
69
66
|
for feat in events:
|
|
70
67
|
dev = events[feat]
|
|
71
68
|
if dev.size:
|
|
@@ -76,6 +73,8 @@ class EventStash:
|
|
|
76
73
|
if slice_loc:
|
|
77
74
|
self.indices_for_data[slice_loc] = index
|
|
78
75
|
|
|
76
|
+
self._tracker[idx_loc] = True
|
|
77
|
+
|
|
79
78
|
def require_feature(self, feat, sample_data):
|
|
80
79
|
"""Create a new empty feature array in `self.events` and return it
|
|
81
80
|
|
|
@@ -87,10 +86,10 @@ class EventStash:
|
|
|
87
86
|
Sample data for one event of the feature (used to determine
|
|
88
87
|
shape and dtype of the feature array)
|
|
89
88
|
"""
|
|
90
|
-
sample_data = np.array(sample_data)
|
|
91
|
-
event_shape = sample_data.shape
|
|
92
|
-
dtype = sample_data.dtype
|
|
93
89
|
if feat not in self.events:
|
|
90
|
+
sample_data = np.array(sample_data)
|
|
91
|
+
event_shape = sample_data.shape
|
|
92
|
+
dtype = sample_data.dtype
|
|
94
93
|
darr = np.zeros((self.size,) + tuple(event_shape),
|
|
95
94
|
dtype=dtype)
|
|
96
95
|
self.events[feat] = darr
|
|
@@ -99,7 +98,6 @@ class EventStash:
|
|
|
99
98
|
|
|
100
99
|
class QueueCollectorThread(threading.Thread):
|
|
101
100
|
def __init__(self,
|
|
102
|
-
data: HDF5Data,
|
|
103
101
|
event_queue: mp.Queue,
|
|
104
102
|
writer_dq: deque,
|
|
105
103
|
feat_nevents: mp.Array,
|
|
@@ -115,9 +113,6 @@ class QueueCollectorThread(threading.Thread):
|
|
|
115
113
|
|
|
116
114
|
Parameters
|
|
117
115
|
----------
|
|
118
|
-
data:
|
|
119
|
-
Data source object. This is used for appending additional
|
|
120
|
-
information
|
|
121
116
|
event_queue:
|
|
122
117
|
A queue object to which other processes or threads write
|
|
123
118
|
events as tuples `(frame_index, events_dict)`.
|
|
@@ -146,8 +141,6 @@ class QueueCollectorThread(threading.Thread):
|
|
|
146
141
|
super(QueueCollectorThread, self).__init__(
|
|
147
142
|
name="QueueCollector", *args, **kwargs)
|
|
148
143
|
self.logger = logging.getLogger("dcnum.write.QueueCollector")
|
|
149
|
-
#: HDF5 data instance
|
|
150
|
-
self.data = data
|
|
151
144
|
#: Event queue from which to collect event data
|
|
152
145
|
self.event_queue = event_queue
|
|
153
146
|
#: Writer deque to which event arrays are appended
|
|
@@ -169,7 +162,7 @@ class QueueCollectorThread(threading.Thread):
|
|
|
169
162
|
# We are not writing to `event_queue` so we can safely cancel
|
|
170
163
|
# our queue thread if we are told to stop.
|
|
171
164
|
self.event_queue.cancel_join_thread()
|
|
172
|
-
# Indexes the current frame in
|
|
165
|
+
# Indexes the current frame in the input HDF5Data instance.
|
|
173
166
|
last_idx = 0
|
|
174
167
|
self.logger.debug("Started collector thread")
|
|
175
168
|
while True:
|
|
@@ -140,10 +140,10 @@ class HDF5Writer:
|
|
|
140
140
|
feat_dtype=feat_dtype),
|
|
141
141
|
**ds_kwds)
|
|
142
142
|
if len(item_shape) == 2:
|
|
143
|
-
dset.attrs.create('CLASS', np.
|
|
144
|
-
dset.attrs.create('IMAGE_VERSION', np.
|
|
143
|
+
dset.attrs.create('CLASS', np.bytes_('IMAGE'))
|
|
144
|
+
dset.attrs.create('IMAGE_VERSION', np.bytes_('1.2'))
|
|
145
145
|
dset.attrs.create('IMAGE_SUBCLASS',
|
|
146
|
-
np.
|
|
146
|
+
np.bytes_('IMAGE_GRAYSCALE'))
|
|
147
147
|
offset = 0
|
|
148
148
|
else:
|
|
149
149
|
dset = egroup[feat]
|
|
@@ -6,6 +6,11 @@ README.rst
|
|
|
6
6
|
pyproject.toml
|
|
7
7
|
.github/workflows/check.yml
|
|
8
8
|
.github/workflows/deploy_pypi.yml
|
|
9
|
+
benchmark/.gitignore
|
|
10
|
+
benchmark/Readme.md
|
|
11
|
+
benchmark/benchmark.py
|
|
12
|
+
benchmark/bm_write_deque_writer_thread.py
|
|
13
|
+
benchmark/bm_write_queue_collector_thread.py
|
|
9
14
|
docs/conf.py
|
|
10
15
|
docs/index.rst
|
|
11
16
|
docs/requirements.txt
|
|
@@ -46,6 +51,7 @@ src/dcnum/meta/ppid.py
|
|
|
46
51
|
src/dcnum/read/__init__.py
|
|
47
52
|
src/dcnum/read/cache.py
|
|
48
53
|
src/dcnum/read/const.py
|
|
54
|
+
src/dcnum/read/detect_flicker.py
|
|
49
55
|
src/dcnum/read/hdf5_data.py
|
|
50
56
|
src/dcnum/read/mapped.py
|
|
51
57
|
src/dcnum/segm/__init__.py
|
|
@@ -93,6 +99,7 @@ tests/test_meta_ppid_gate.py
|
|
|
93
99
|
tests/test_meta_ppid_segm.py
|
|
94
100
|
tests/test_read_basin.py
|
|
95
101
|
tests/test_read_concat_hdf5.py
|
|
102
|
+
tests/test_read_detect_flicker.py
|
|
96
103
|
tests/test_read_hdf5.py
|
|
97
104
|
tests/test_read_hdf5_basins.py
|
|
98
105
|
tests/test_read_hdf5_index_mapping.py
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from dcnum.meta import ppid
|
|
@@ -80,8 +82,14 @@ def test_unique_prefix_unordered(in_list, out_list):
|
|
|
80
82
|
"tem=90^te=a^o=0^wit=1^a=1000^win=red^tes=1"),
|
|
81
83
|
({"temperature": 10.1},
|
|
82
84
|
"tem=10.1^te=a^o=0^wit=1^a=1000^win=red^tes=1"),
|
|
85
|
+
({"temperature": 10.0},
|
|
86
|
+
"tem=10^te=a^o=0^wit=1^a=1000^win=red^tes=1"),
|
|
87
|
+
({"temperature": np.float16(9.0)},
|
|
88
|
+
"tem=9^te=a^o=0^wit=1^a=1000^win=red^tes=1"),
|
|
83
89
|
({"with_water": False, "wine_type": "blue"},
|
|
84
90
|
"tem=90^te=a^o=0^wit=0^a=1000^win=blue^tes=1"),
|
|
91
|
+
({"with_water": np.bool_(1), "wine_type": "blue"},
|
|
92
|
+
"tem=90^te=a^o=0^wit=1^a=1000^win=blue^tes=1"),
|
|
85
93
|
])
|
|
86
94
|
def test_kwargs_to_ppid(kwargs, pid):
|
|
87
95
|
ptest = ppid.kwargs_to_ppid(ExampleClass, "cook", kwargs)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from dcnum.read import concatenated_hdf5_data, detect_flickering
|
|
4
|
+
|
|
5
|
+
from helper_methods import retrieve_data
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_detect_flicker_basic():
|
|
9
|
+
image_data = np.full((500, 80, 320), 145)
|
|
10
|
+
flicker_indices = [4, 9, 10, 23, 439]
|
|
11
|
+
for idx in flicker_indices:
|
|
12
|
+
image_data[idx] += 5
|
|
13
|
+
assert detect_flickering(image_data,
|
|
14
|
+
roi_height=10,
|
|
15
|
+
brightness_threshold=5,
|
|
16
|
+
count_threshold=5,
|
|
17
|
+
max_frames=500,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert not detect_flickering(image_data,
|
|
21
|
+
roi_height=10,
|
|
22
|
+
brightness_threshold=5,
|
|
23
|
+
count_threshold=6, # threshold too low
|
|
24
|
+
max_frames=500,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert not detect_flickering(image_data,
|
|
28
|
+
roi_height=10,
|
|
29
|
+
brightness_threshold=6, # threshold too low
|
|
30
|
+
count_threshold=5,
|
|
31
|
+
max_frames=500,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
assert not detect_flickering(image_data,
|
|
35
|
+
roi_height=10,
|
|
36
|
+
brightness_threshold=5,
|
|
37
|
+
count_threshold=5,
|
|
38
|
+
max_frames=400, # too few frames
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_detect_flicker_hdf5data_instance():
|
|
43
|
+
path = retrieve_data("fmt-hdf5_cytoshot_full-features_2023.zip")
|
|
44
|
+
path_out = path.with_name("input.rtdc")
|
|
45
|
+
# create simple concatenated dataset, repeating a file
|
|
46
|
+
with concatenated_hdf5_data([path]*25, path_out=path_out) as hd:
|
|
47
|
+
assert len(hd) == 1000
|
|
48
|
+
assert not detect_flickering(hd.image)
|
|
49
|
+
assert detect_flickering(hd.image, brightness_threshold=1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_detect_flicker_none():
|
|
53
|
+
image_data = np.full((500, 80, 320), 145)
|
|
54
|
+
assert not detect_flickering(image_data,
|
|
55
|
+
roi_height=10,
|
|
56
|
+
brightness_threshold=5,
|
|
57
|
+
count_threshold=5,
|
|
58
|
+
max_frames=500,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_detect_flicker_not_outside_roi():
|
|
63
|
+
image_data = np.full((500, 80, 320), 145)
|
|
64
|
+
flicker_indices = [4, 9, 10, 23, 439]
|
|
65
|
+
for idx in flicker_indices:
|
|
66
|
+
# only modify data outside the ROI
|
|
67
|
+
image_data[idx, 11:, :] += 5
|
|
68
|
+
assert not detect_flickering(image_data,
|
|
69
|
+
roi_height=10,
|
|
70
|
+
brightness_threshold=5,
|
|
71
|
+
count_threshold=5,
|
|
72
|
+
max_frames=500,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_detect_flicker_only_inside_roi():
|
|
77
|
+
image_data = np.full((500, 80, 320), 145)
|
|
78
|
+
flicker_indices = [4, 9, 10, 23, 439]
|
|
79
|
+
for idx in flicker_indices:
|
|
80
|
+
# only modify data inside the ROI
|
|
81
|
+
image_data[idx, :10, :] += 5
|
|
82
|
+
assert detect_flickering(image_data,
|
|
83
|
+
roi_height=10,
|
|
84
|
+
brightness_threshold=5,
|
|
85
|
+
count_threshold=5,
|
|
86
|
+
max_frames=500,
|
|
87
|
+
)
|
|
@@ -106,6 +106,19 @@ def test_image_cache(tmp_path):
|
|
|
106
106
|
assert 2 in hic.cache
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def test_image_cache_slice_out_of_bounds(tmp_path):
|
|
110
|
+
path = tmp_path / "test.hdf5"
|
|
111
|
+
with h5py.File(path, "w") as hw:
|
|
112
|
+
hw["events/image"] = np.random.rand(210, 80, 180)
|
|
113
|
+
|
|
114
|
+
with h5py.File(path, "r") as h5:
|
|
115
|
+
hic = read.HDF5ImageCache(h5["events/image"],
|
|
116
|
+
chunk_size=100,
|
|
117
|
+
cache_size=2)
|
|
118
|
+
assert len(hic) == 210
|
|
119
|
+
assert len(hic[:300]) == 210
|
|
120
|
+
|
|
121
|
+
|
|
109
122
|
def test_image_cache_index_out_of_range(tmp_path):
|
|
110
123
|
path = tmp_path / "test.hdf5"
|
|
111
124
|
size = 20
|
|
@@ -4,9 +4,8 @@ import pathlib
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
-
from dcnum import
|
|
7
|
+
from dcnum import write
|
|
8
8
|
|
|
9
|
-
from helper_methods import retrieve_data
|
|
10
9
|
|
|
11
10
|
data_path = pathlib.Path(__file__).parent / "data"
|
|
12
11
|
|
|
@@ -44,15 +43,12 @@ def test_event_stash():
|
|
|
44
43
|
|
|
45
44
|
def test_queue_collector_thread():
|
|
46
45
|
# keyword arguments
|
|
47
|
-
data = read.HDF5Data(
|
|
48
|
-
retrieve_data("fmt-hdf5_cytoshot_full-features_2023.zip"))
|
|
49
46
|
event_queue = mp.Queue()
|
|
50
47
|
writer_dq = collections.deque()
|
|
51
48
|
feat_nevents = np.array([1, 3, 1, 5])
|
|
52
49
|
write_threshold = 2
|
|
53
50
|
# queue collector thread
|
|
54
51
|
qct = write.QueueCollectorThread(
|
|
55
|
-
data=data,
|
|
56
52
|
event_queue=event_queue,
|
|
57
53
|
writer_dq=writer_dq,
|
|
58
54
|
feat_nevents=feat_nevents,
|
|
@@ -99,15 +95,12 @@ def test_queue_collector_thread():
|
|
|
99
95
|
|
|
100
96
|
def test_queue_collector_thread_with_full_stash():
|
|
101
97
|
# keyword arguments
|
|
102
|
-
data = read.HDF5Data(
|
|
103
|
-
retrieve_data("fmt-hdf5_cytoshot_full-features_2023.zip"))
|
|
104
98
|
event_queue = mp.Queue()
|
|
105
99
|
writer_dq = collections.deque()
|
|
106
100
|
feat_nevents = np.array([1, 3, 1, 5])
|
|
107
101
|
write_threshold = 2
|
|
108
102
|
# queue collector thread
|
|
109
103
|
qct = write.QueueCollectorThread(
|
|
110
|
-
data=data,
|
|
111
104
|
event_queue=event_queue,
|
|
112
105
|
writer_dq=writer_dq,
|
|
113
106
|
feat_nevents=feat_nevents,
|
|
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
|
|
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
|
{dcnum-0.24.0 → dcnum-0.25.1}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip
RENAMED
|
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
|
|
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
|