dcnum 0.25.10__tar.gz → 0.25.11__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 (131) hide show
  1. {dcnum-0.25.10 → dcnum-0.25.11}/CHANGELOG +5 -0
  2. {dcnum-0.25.10 → dcnum-0.25.11}/PKG-INFO +3 -3
  3. {dcnum-0.25.10 → dcnum-0.25.11}/README.rst +2 -2
  4. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/_version.py +2 -2
  5. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/logic/ctrl.py +3 -11
  6. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/__init__.py +4 -1
  7. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/hdf5_data.py +37 -0
  8. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/write/writer.py +16 -1
  9. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum.egg-info/PKG-INFO +3 -3
  10. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_logic_pipeline.py +21 -5
  11. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_hdf5_basins.py +61 -0
  12. {dcnum-0.25.10 → dcnum-0.25.11}/.github/workflows/check.yml +0 -0
  13. {dcnum-0.25.10 → dcnum-0.25.11}/.github/workflows/deploy_pypi.yml +0 -0
  14. {dcnum-0.25.10 → dcnum-0.25.11}/.gitignore +0 -0
  15. {dcnum-0.25.10 → dcnum-0.25.11}/.readthedocs.yml +0 -0
  16. {dcnum-0.25.10 → dcnum-0.25.11}/LICENSE +0 -0
  17. {dcnum-0.25.10 → dcnum-0.25.11}/benchmark/.gitignore +0 -0
  18. {dcnum-0.25.10 → dcnum-0.25.11}/benchmark/Readme.md +0 -0
  19. {dcnum-0.25.10 → dcnum-0.25.11}/benchmark/benchmark.py +0 -0
  20. {dcnum-0.25.10 → dcnum-0.25.11}/benchmark/bm_write_deque_writer_thread.py +0 -0
  21. {dcnum-0.25.10 → dcnum-0.25.11}/benchmark/bm_write_queue_collector_thread.py +0 -0
  22. {dcnum-0.25.10 → dcnum-0.25.11}/docs/.gitignore +0 -0
  23. {dcnum-0.25.10 → dcnum-0.25.11}/docs/conf.py +0 -0
  24. {dcnum-0.25.10 → dcnum-0.25.11}/docs/extensions/github_changelog.py +0 -0
  25. {dcnum-0.25.10 → dcnum-0.25.11}/docs/index.rst +0 -0
  26. {dcnum-0.25.10 → dcnum-0.25.11}/docs/requirements.txt +0 -0
  27. {dcnum-0.25.10 → dcnum-0.25.11}/docs/sec_design.rst +0 -0
  28. {dcnum-0.25.10 → dcnum-0.25.11}/pyproject.toml +0 -0
  29. {dcnum-0.25.10 → dcnum-0.25.11}/setup.cfg +0 -0
  30. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/__init__.py +0 -0
  31. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/__init__.py +0 -0
  32. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/event_extractor_manager_thread.py +0 -0
  33. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_background/__init__.py +0 -0
  34. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_background/base.py +0 -0
  35. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_background/bg_copy.py +0 -0
  36. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_background/bg_roll_median.py +0 -0
  37. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_background/bg_sparse_median.py +0 -0
  38. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_brightness/__init__.py +0 -0
  39. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_brightness/bright_all.py +0 -0
  40. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_brightness/common.py +0 -0
  41. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_contour/__init__.py +0 -0
  42. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_contour/contour.py +0 -0
  43. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_contour/moments.py +0 -0
  44. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_contour/volume.py +0 -0
  45. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_texture/__init__.py +0 -0
  46. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_texture/common.py +0 -0
  47. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/feat_texture/tex_all.py +0 -0
  48. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/gate.py +0 -0
  49. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/feat/queue_event_extractor.py +0 -0
  50. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/logic/__init__.py +0 -0
  51. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/logic/job.py +0 -0
  52. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/logic/json_encoder.py +0 -0
  53. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/meta/__init__.py +0 -0
  54. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/meta/paths.py +0 -0
  55. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/meta/ppid.py +0 -0
  56. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/os_env_st.py +0 -0
  57. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/cache.py +0 -0
  58. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/const.py +0 -0
  59. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/detect_flicker.py +0 -0
  60. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/hdf5_concat.py +0 -0
  61. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/read/mapped.py +0 -0
  62. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/__init__.py +0 -0
  63. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_thresh.py +0 -0
  64. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/__init__.py +0 -0
  65. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/segm_torch_base.py +0 -0
  66. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/segm_torch_mpo.py +0 -0
  67. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/segm_torch_sto.py +0 -0
  68. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/torch_model.py +0 -0
  69. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/torch_postproc.py +0 -0
  70. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segm_torch/torch_preproc.py +0 -0
  71. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segmenter.py +0 -0
  72. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segmenter_manager_thread.py +0 -0
  73. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segmenter_mpo.py +0 -0
  74. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/segm/segmenter_sto.py +0 -0
  75. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/write/__init__.py +0 -0
  76. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/write/deque_writer_thread.py +0 -0
  77. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum/write/queue_collector_thread.py +0 -0
  78. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum.egg-info/SOURCES.txt +0 -0
  79. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum.egg-info/dependency_links.txt +0 -0
  80. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum.egg-info/requires.txt +0 -0
  81. {dcnum-0.25.10 → dcnum-0.25.11}/src/dcnum.egg-info/top_level.txt +0 -0
  82. {dcnum-0.25.10 → dcnum-0.25.11}/tests/conftest.py +0 -0
  83. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip +0 -0
  84. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_cytoshot_full-features_2023.zip +0 -0
  85. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_cytoshot_full-features_2024.zip +0 -0
  86. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip +0 -0
  87. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_shapein_empty.zip +0 -0
  88. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/fmt-hdf5_shapein_raw-with-variable-length-logs.zip +0 -0
  89. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/segm-torch-model_unet-dcnum-test_g1_910c2.zip +0 -0
  90. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/segm-torch-model_unet-dcnum-test_g2_17ec6.zip +0 -0
  91. {dcnum-0.25.10 → dcnum-0.25.11}/tests/data/segm-torch-test-data_unet-dcnum-test_g1_910c2.zip +0 -0
  92. {dcnum-0.25.10 → dcnum-0.25.11}/tests/helper_methods.py +0 -0
  93. {dcnum-0.25.10 → dcnum-0.25.11}/tests/requirements.txt +0 -0
  94. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_background_base.py +0 -0
  95. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_background_bg_copy.py +0 -0
  96. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_background_bg_roll_median.py +0 -0
  97. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_background_bg_sparsemed.py +0 -0
  98. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_brightness.py +0 -0
  99. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_event_extractor_manager.py +0 -0
  100. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_gate.py +0 -0
  101. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_haralick.py +0 -0
  102. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_moments_based.py +0 -0
  103. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_moments_based_extended.py +0 -0
  104. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_feat_volume.py +0 -0
  105. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_init.py +0 -0
  106. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_logic_job.py +0 -0
  107. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_logic_join.py +0 -0
  108. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_logic_json.py +0 -0
  109. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_paths.py +0 -0
  110. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_base.py +0 -0
  111. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_bg.py +0 -0
  112. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_data.py +0 -0
  113. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_feat.py +0 -0
  114. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_gate.py +0 -0
  115. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_meta_ppid_segm.py +0 -0
  116. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_basin.py +0 -0
  117. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_concat_hdf5.py +0 -0
  118. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_detect_flicker.py +0 -0
  119. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_hdf5.py +0 -0
  120. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_hdf5_concat.py +0 -0
  121. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_read_hdf5_index_mapping.py +0 -0
  122. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_base.py +0 -0
  123. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_mpo.py +0 -0
  124. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_no_mask_proc.py +0 -0
  125. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_sto.py +0 -0
  126. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_thresh.py +0 -0
  127. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_torch.py +0 -0
  128. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_segm_torch_preproc.py +0 -0
  129. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_write_deque_writer_thread.py +0 -0
  130. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_write_queue_collector_thread.py +0 -0
  131. {dcnum-0.25.10 → dcnum-0.25.11}/tests/test_write_writer.py +0 -0
@@ -1,3 +1,8 @@
1
+ 0.25.11
2
+ - enh: align measurement identifier computation with that of dclab
3
+ - enh: new `get_measurement_identifier` method
4
+ - enh: allow to optionally store measurement identifier in basin metadata
5
+ - enh: measurement identifier check for basin data
1
6
  0.25.10
2
7
  - fix: search for validation strings in logs did not use re.MULTILINE
3
8
  - enh: allow to validate torch model applicability based on meta values
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dcnum
3
- Version: 0.25.10
3
+ Version: 0.25.11
4
4
  Summary: numerics toolbox for imaging deformability cytometry
5
5
  Author: Maximilian Schlögel, Paul Müller, Raghava Alajangi
6
6
  Maintainer-email: Paul Müller <dev@craban.de>
@@ -55,12 +55,12 @@ For more options, please check out the `documentation
55
55
 
56
56
 
57
57
 
58
- .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/master/docs/logo/dcnum.png
58
+ .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/main/docs/logo/dcnum.png
59
59
  .. |PyPI Version| image:: https://img.shields.io/pypi/v/dcnum.svg
60
60
  :target: https://pypi.python.org/pypi/dcnum
61
61
  .. |Build Status| image:: https://img.shields.io/github/actions/workflow/status/DC-analysis/dcnum/check.yml
62
62
  :target: https://github.com/DC-analysis/dcnum/actions?query=workflow%3AChecks
63
- .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum/master.svg
63
+ .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum
64
64
  :target: https://codecov.io/gh/DC-analysis/dcnum
65
65
  .. |Docs Status| image:: https://readthedocs.org/projects/dcnum/badge/?version=latest
66
66
  :target: https://readthedocs.org/projects/dcnum/builds/
@@ -24,12 +24,12 @@ For more options, please check out the `documentation
24
24
 
25
25
 
26
26
 
27
- .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/master/docs/logo/dcnum.png
27
+ .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/main/docs/logo/dcnum.png
28
28
  .. |PyPI Version| image:: https://img.shields.io/pypi/v/dcnum.svg
29
29
  :target: https://pypi.python.org/pypi/dcnum
30
30
  .. |Build Status| image:: https://img.shields.io/github/actions/workflow/status/DC-analysis/dcnum/check.yml
31
31
  :target: https://github.com/DC-analysis/dcnum/actions?query=workflow%3AChecks
32
- .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum/master.svg
32
+ .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum
33
33
  :target: https://codecov.io/gh/DC-analysis/dcnum
34
34
  .. |Docs Status| image:: https://readthedocs.org/projects/dcnum/badge/?version=latest
35
35
  :target: https://readthedocs.org/projects/dcnum/builds/
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.25.10'
21
- __version_tuple__ = version_tuple = (0, 25, 10)
20
+ __version__ = version = '0.25.11'
21
+ __version_tuple__ = version_tuple = (0, 25, 11)
@@ -1,6 +1,5 @@
1
1
  import collections
2
2
  import datetime
3
- import hashlib
4
3
  import importlib
5
4
  import json
6
5
  import logging
@@ -24,7 +23,7 @@ from ..feat import gate
24
23
  from ..feat import EventExtractorManagerThread
25
24
  from ..segm import SegmenterManagerThread, get_available_segmenters
26
25
  from ..meta import ppid
27
- from ..read import HDF5Data, get_mapping_indices
26
+ from ..read import HDF5Data, get_measurement_identifier, get_mapping_indices
28
27
  from .._version import version, version_tuple
29
28
  from ..write import (
30
29
  DequeWriterThread, HDF5Writer, QueueCollectorThread, copy_features,
@@ -453,15 +452,7 @@ class DCNumJobRunner(threading.Thread):
453
452
  # segmentation did actually take place.
454
453
  mid_ap = f"dcn-{self.pphash[:7]}"
455
454
  # This is the current measurement identifier
456
- mid_cur = hw.h5.attrs.get("experiment:run identifier")
457
- if not mid_cur:
458
- # Compute a measurement identifier from the metadata
459
- m_time = hw.h5.attrs.get("experiment:time", "none")
460
- m_date = hw.h5.attrs.get("experiment:date", "none")
461
- m_sid = hw.h5.attrs.get("setup:identifier", "none")
462
- hasher = hashlib.md5(
463
- f"{m_time}_{m_date}_{m_sid}".encode("utf-8"))
464
- mid_cur = str(uuid.UUID(hex=hasher.hexdigest()))
455
+ mid_cur = get_measurement_identifier(hw.h5)
465
456
  # The new measurement identifier is a combination of both.
466
457
  mid_new = f"{mid_cur}_{mid_ap}" if mid_cur else mid_ap
467
458
  hw.h5.attrs["experiment:run identifier"] = mid_new
@@ -666,6 +657,7 @@ class DCNumJobRunner(threading.Thread):
666
657
  mapping=basinmap0,
667
658
  paths=paths,
668
659
  description=f"Created with dcnum {version}",
660
+ identifier=get_measurement_identifier(hin),
669
661
  )
670
662
  self._progress_bn += 1 / len(feats_raw)
671
663
  t_tot = time.perf_counter() - t0
@@ -2,6 +2,9 @@
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 (
6
+ HDF5Data, HDF5ImageCache, get_measurement_identifier,
7
+ BasinIdentifierMismatchError
8
+ )
6
9
  from .hdf5_concat import concatenated_hdf5_data
7
10
  from .mapped import get_mapping_indices, get_mapped_object
@@ -16,6 +16,10 @@ from .const import PROTECTED_FEATURES
16
16
  from .mapped import get_mapped_object, get_mapping_indices
17
17
 
18
18
 
19
+ class BasinIdentifierMismatchError(BaseException):
20
+ """Used when basin identifiers do not match"""
21
+
22
+
19
23
  class HDF5Data:
20
24
  """HDF5 (.rtdc) input file data instance"""
21
25
  def __init__(self,
@@ -473,6 +477,12 @@ class HDF5Data:
473
477
  features = []
474
478
  else:
475
479
  h5 = h5py.File(path, "r")
480
+ # verify that the basin was identified correctly
481
+ if ((id_exp := bn_dict.get("identifier")) is not None
482
+ and (id_act := get_measurement_identifier(h5)) != id_exp):
483
+ raise BasinIdentifierMismatchError(
484
+ f"The basin '{path}' with identifier '{id_act}' "
485
+ f"does not match the expected identifier '{id_exp}'")
476
486
  h5group = h5["events"]
477
487
  # features defined in the basin
478
488
  features = bn_dict.get("features")
@@ -560,3 +570,30 @@ def concatenated_hdf5_data(*args, **kwargs):
560
570
  DeprecationWarning)
561
571
  from . import hdf5_concat
562
572
  return hdf5_concat.concatenated_hdf5_data(*args, **kwargs)
573
+
574
+
575
+ def get_measurement_identifier(h5: h5py.Group) -> str | None:
576
+ """Return the measurement identifier for the given H5File object
577
+
578
+ The basin identifier is taken from the HDF5 attributes. If the
579
+ "experiment:run identifier" attribute is not set, it is
580
+ computed from the HDF5 attributes "experiment:time",
581
+ "experiment:date", and "setup:identifier".
582
+
583
+ If the measurement identifier cannot be found or computed,
584
+ return None.
585
+ """
586
+ # This is the current measurement identifier
587
+ mid = h5.attrs.get("experiment:run identifier")
588
+ if not mid:
589
+ # Compute a measurement identifier from the metadata
590
+ m_time = h5.attrs.get("experiment:time", None) or None
591
+ m_date = h5.attrs.get("experiment:date", None) or None
592
+ m_sid = h5.attrs.get("setup:identifier", None) or None
593
+ if None not in [m_time, m_date, m_sid]:
594
+ # Only compute an identifier if all of the above
595
+ # are defined.
596
+ hasher = hashlib.md5(
597
+ f"{m_time}_{m_date}_{m_sid}".encode("utf-8"))
598
+ mid = str(uuid.UUID(hex=hasher.hexdigest()))
599
+ return mid
@@ -8,7 +8,7 @@ import h5py
8
8
  import hdf5plugin
9
9
  import numpy as np
10
10
 
11
- from ..read import HDF5Data
11
+ from ..read import HDF5Data, get_measurement_identifier
12
12
  from .._version import version
13
13
 
14
14
 
@@ -157,6 +157,7 @@ class HDF5Writer:
157
157
  description: str | None = None,
158
158
  mapping: np.ndarray = None,
159
159
  internal_data: Dict | None = None,
160
+ identifier: str | None = None,
160
161
  ):
161
162
  """Write an HDF5-based file basin
162
163
 
@@ -177,6 +178,10 @@ class HDF5Writer:
177
178
  internal_data: dict of ndarrays
178
179
  internal basin data to store; If this is set, then `features`
179
180
  and `paths` must be set to `None`.
181
+ identifier: str
182
+ the measurement identifier of the basin as computed by
183
+ the :func:`~dcnum.read.hdf5_data.get_measurement_identifier`
184
+ function.
180
185
  """
181
186
  bdat = {
182
187
  "description": description,
@@ -190,6 +195,9 @@ class HDF5Writer:
190
195
  if paths is not None:
191
196
  raise ValueError("`paths` must be set to None when storing "
192
197
  "internal basin features")
198
+ if identifier is not None:
199
+ warnings.warn(f"Not storing identifier for internal "
200
+ f"basin '{name}' (got '{identifier}')")
193
201
  # store the internal basin information
194
202
  for feat in internal_data:
195
203
  if feat in self.h5.require_group("basin_events"):
@@ -206,6 +214,8 @@ class HDF5Writer:
206
214
  bdat["format"] = "hdf5"
207
215
  bdat["paths"] = [str(pp) for pp in paths]
208
216
  bdat["type"] = "file"
217
+ # identifier only makes sense here (not for internal basins)
218
+ bdat["identifier"] = identifier
209
219
 
210
220
  # Explicit features stored in basin file
211
221
  if features is not None and len(features):
@@ -322,6 +332,7 @@ def create_with_basins(
322
332
  warnings.warn(f"Creating basin-based file '{path_out}' without any "
323
333
  f"basins, since the list `basin_paths' is empty!",
324
334
  CreatingFileWithoutBasinWarning)
335
+ basin_paths = []
325
336
  with HDF5Writer(path_out, mode="w") as hw:
326
337
  # Get the metadata from the first available basin path
327
338
 
@@ -359,16 +370,19 @@ def create_with_basins(
359
370
  features = sorted(h5["events"].keys())
360
371
  features = [f for f in features if
361
372
  not f.startswith("basinmap")]
373
+ basin_identifier = get_measurement_identifier(h5)
362
374
  name = prep.name
363
375
  else:
364
376
  features = None
365
377
  name = bps[0]
378
+ basin_identifier = None
366
379
 
367
380
  # Write the basin data
368
381
  hw.store_basin(name=name,
369
382
  paths=bps,
370
383
  features=features,
371
384
  description=f"Created by dcnum {version}",
385
+ identifier=basin_identifier,
372
386
  )
373
387
 
374
388
 
@@ -404,6 +418,7 @@ def copy_basins(h5_src: h5py.File,
404
418
  paths=bn_dict["paths"],
405
419
  features=bn_dict["features"],
406
420
  mapping=mapping,
421
+ identifier=bn_dict.get("identifier"),
407
422
  )
408
423
  else:
409
424
  warnings.warn(f"Ignored basin of type '{bn_dict['type']}'",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dcnum
3
- Version: 0.25.10
3
+ Version: 0.25.11
4
4
  Summary: numerics toolbox for imaging deformability cytometry
5
5
  Author: Maximilian Schlögel, Paul Müller, Raghava Alajangi
6
6
  Maintainer-email: Paul Müller <dev@craban.de>
@@ -55,12 +55,12 @@ For more options, please check out the `documentation
55
55
 
56
56
 
57
57
 
58
- .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/master/docs/logo/dcnum.png
58
+ .. |dcnum| image:: https://raw.github.com/DC-analysis/dcnum/main/docs/logo/dcnum.png
59
59
  .. |PyPI Version| image:: https://img.shields.io/pypi/v/dcnum.svg
60
60
  :target: https://pypi.python.org/pypi/dcnum
61
61
  .. |Build Status| image:: https://img.shields.io/github/actions/workflow/status/DC-analysis/dcnum/check.yml
62
62
  :target: https://github.com/DC-analysis/dcnum/actions?query=workflow%3AChecks
63
- .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum/master.svg
63
+ .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/DC-analysis/dcnum
64
64
  :target: https://codecov.io/gh/DC-analysis/dcnum
65
65
  .. |Docs Status| image:: https://readthedocs.org/projects/dcnum/badge/?version=latest
66
66
  :target: https://readthedocs.org/projects/dcnum/builds/
@@ -41,11 +41,10 @@ def get_log(hd: read.hdf5_data.HDF5Data,
41
41
  },
42
42
  ["experiment:run identifier"],
43
43
  "f0cfdc6f-93c7-c093-d135-a20f3e5bdbfa"],
44
- # delete everything, this is the hash of none-none-none
44
+ # delete everything, this should yield only the pipeline ID
45
45
  [{},
46
- ["setup:identifier", "experiment:time", "experiment:run identifier",
47
- "setup:identifier"],
48
- "07d18822-3045-4e6b-da0b-f1a16c80132a"],
46
+ ["setup:identifier", "experiment:time", "experiment:run identifier"],
47
+ None],
49
48
  ])
50
49
  def test_basin_experiment_identifier_correct(
51
50
  override_attrs, delete_keys, meas_id):
@@ -77,7 +76,10 @@ def test_basin_experiment_identifier_correct(
77
76
 
78
77
  with h5py.File(path_out) as hout:
79
78
  appid = hout.attrs["pipeline:dcnum hash"]
80
- eri_exp = f"{meas_id}_dcn-{appid[:7]}"
79
+ if meas_id is not None:
80
+ eri_exp = f"{meas_id}_dcn-{appid[:7]}"
81
+ else:
82
+ eri_exp = f"dcn-{appid[:7]}"
81
83
  # this is the actual test
82
84
  assert hout.attrs["experiment:run identifier"] == eri_exp
83
85
 
@@ -185,9 +187,23 @@ def test_basin_strategy_tap():
185
187
  assert "image" not in h5["events"]
186
188
  for feat in h5["events"]:
187
189
  assert len(h5["events"][feat]) == 275
190
+ # Make sure the correct basin identifier is stored
191
+ # (must be the original identifier plus a dcn pipeline hash tag)
192
+ assert h5.attrs["experiment:run identifier"] \
193
+ == "d5a40aed-0b6c-0412-e87c-59789fdd28d0_dcn-17decbe"
194
+
188
195
  # The other features are accessed via basins
189
196
  hd = read.HDF5Data(h5)
190
197
  assert "image" in hd
198
+ # Check whether the basin identifier is set correctly.
199
+ for bn in hd.basins:
200
+ print(bn)
201
+ if bn["type"] == "file":
202
+ assert bn["identifier"] \
203
+ == "d5a40aed-0b6c-0412-e87c-59789fdd28d0"
204
+ break
205
+ else:
206
+ assert False, "Something went wrong, the basin is missing"
191
207
 
192
208
 
193
209
  def test_basin_strategy_tap_rollmed():
@@ -76,6 +76,67 @@ def test_basin_features_path_absolute_mapped():
76
76
  assert np.allclose(hd["image"][:], hd_src["image"][4:10])
77
77
 
78
78
 
79
+ def test_basin_identifier_mismatch(tmp_path):
80
+ path = retrieve_data(
81
+ "fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
82
+ path_test = path.parent / "test.h5"
83
+
84
+ with h5py.File(path, "a") as h5:
85
+ rid = read.get_measurement_identifier(h5)
86
+ area_um = h5["events/area_um"][:]
87
+ del h5["events/area_um"]
88
+ assert rid == "d5a40aed-0b6c-0412-e87c-59789fdd28d0"
89
+
90
+ # We basically create a file that consists only of the metadata.
91
+ with write.HDF5Writer(path_test) as hw:
92
+ hw.store_basin(name="pidemon",
93
+ paths=[path],
94
+ features=["deform"],
95
+ description="Wrong basin identifier specified",
96
+ identifier=rid + "-wrong",
97
+ )
98
+ hw.store_feature_chunk("area_um", area_um)
99
+
100
+ with h5py.File(path_test) as hb:
101
+ basins = read.HDF5Data.extract_basin_dicts(hb)
102
+ assert len(basins) == 1
103
+
104
+ with read.HDF5Data(path_test) as hd:
105
+ # Accessing data in the new file works
106
+ assert hd["area_um"][0]
107
+ with pytest.raises(read.BasinIdentifierMismatchError,
108
+ match="does not match"):
109
+ hd["deform"][0]
110
+
111
+
112
+ def test_basin_identifier_normal_use_case(tmp_path):
113
+ path = retrieve_data(
114
+ "fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
115
+ path_test = path.parent / "test.h5"
116
+
117
+ with h5py.File(path) as h5:
118
+ rid = read.get_measurement_identifier(h5)
119
+ assert rid == "d5a40aed-0b6c-0412-e87c-59789fdd28d0"
120
+
121
+ # We basically create a file that consists only of the metadata.
122
+ with write.HDF5Writer(path_test) as hw:
123
+ hw.store_basin(name="pidemon",
124
+ paths=[path],
125
+ description="Basin identifier specified",
126
+ identifier=rid,
127
+ )
128
+
129
+ with h5py.File(path_test) as hb:
130
+ basins = read.HDF5Data.extract_basin_dicts(hb)
131
+ assert len(basins) == 1
132
+ bn = basins[0]
133
+ assert bn["identifier"] == rid
134
+
135
+ with read.HDF5Data(path_test) as hd:
136
+ # Accessing data in the new file works
137
+ assert hd["deform"][0]
138
+
139
+
79
140
  def test_basin_mapped_with_mapped_dataset():
80
141
  """You can have a mapped dataset opening a mapped basin"""
81
142
  path_src = retrieve_data(
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