celldetective 1.3.9.post3__tar.gz → 1.3.9.post5__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.
Files changed (137) hide show
  1. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/PKG-INFO +1 -1
  2. celldetective-1.3.9.post5/celldetective/_version.py +1 -0
  3. celldetective-1.3.9.post5/celldetective/extra_properties.py +437 -0
  4. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/classifier_widget.py +5 -1
  5. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/plot_signals_ui.py +6 -3
  6. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/signal_annotator.py +10 -2
  7. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/io.py +16 -7
  8. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/regionprops/_regionprops.py +10 -3
  9. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/utils.py +8 -9
  10. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/PKG-INFO +1 -1
  11. celldetective-1.3.9.post3/celldetective/_version.py +0 -1
  12. celldetective-1.3.9.post3/celldetective/extra_properties.py +0 -273
  13. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/LICENSE +0 -0
  14. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/README.md +0 -0
  15. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/__init__.py +0 -0
  16. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/__main__.py +0 -0
  17. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/datasets/segmentation_annotations/blank +0 -0
  18. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/datasets/signal_annotations/blank +0 -0
  19. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/events.py +0 -0
  20. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/filters.py +0 -0
  21. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/InitWindow.py +0 -0
  22. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/__init__.py +0 -0
  23. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/about.py +0 -0
  24. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/analyze_block.py +0 -0
  25. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/btrack_options.py +0 -0
  26. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/configure_new_exp.py +0 -0
  27. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/control_panel.py +0 -0
  28. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/generic_signal_plot.py +0 -0
  29. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/gui_utils.py +0 -0
  30. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/DL-segmentation-strategy.json +0 -0
  31. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/Threshold-vs-DL.json +0 -0
  32. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/cell-populations.json +0 -0
  33. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/exp-structure.json +0 -0
  34. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/feature-btrack.json +0 -0
  35. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/neighborhood.json +0 -0
  36. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/prefilter-for-segmentation.json +0 -0
  37. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/preprocessing.json +0 -0
  38. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/propagate-classification.json +0 -0
  39. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/track-postprocessing.json +0 -0
  40. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/help/tracking.json +0 -0
  41. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/json_readers.py +0 -0
  42. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/layouts.py +0 -0
  43. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/measurement_options.py +0 -0
  44. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/neighborhood_options.py +0 -0
  45. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/plot_measurements.py +0 -0
  46. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/process_block.py +0 -0
  47. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/downloader.py +0 -0
  48. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/measure_cells.py +0 -0
  49. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/segment_cells.py +0 -0
  50. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/track_cells.py +0 -0
  51. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/train_segmentation_model.py +0 -0
  52. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/processes/train_signal_model.py +0 -0
  53. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/retrain_segmentation_model_options.py +0 -0
  54. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/retrain_signal_model_options.py +0 -0
  55. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/seg_model_loader.py +0 -0
  56. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/signal_annotator2.py +0 -0
  57. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/signal_annotator_options.py +0 -0
  58. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/styles.py +0 -0
  59. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/survival_ui.py +0 -0
  60. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/tableUI.py +0 -0
  61. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/thresholds_gui.py +0 -0
  62. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/viewers.py +0 -0
  63. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/gui/workers.py +0 -0
  64. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/logo-large.png +0 -0
  65. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/logo.png +0 -0
  66. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/signals_icon.png +0 -0
  67. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/splash-test.png +0 -0
  68. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/splash.png +0 -0
  69. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/splash0.png +0 -0
  70. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/survival2.png +0 -0
  71. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/vignette_signals2.png +0 -0
  72. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/icons/vignette_signals2.svg +0 -0
  73. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/links/zenodo.json +0 -0
  74. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/measure.py +0 -0
  75. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/pair_signal_detection/blank +0 -0
  76. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/blank +0 -0
  77. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -0
  78. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
  79. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -0
  80. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -0
  81. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
  82. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_generic/blank +0 -0
  83. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/segmentation_targets/blank +0 -0
  84. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
  85. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
  86. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/config_input.json +0 -0
  87. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -0
  88. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -0
  89. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
  90. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
  91. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/scores.npy +0 -0
  92. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
  93. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
  94. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
  95. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
  96. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/signal_detection/blank +0 -0
  97. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/tracking_configs/biased_motion.json +0 -0
  98. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/tracking_configs/mcf7.json +0 -0
  99. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/tracking_configs/no_z_motion.json +0 -0
  100. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/tracking_configs/ricm.json +0 -0
  101. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/models/tracking_configs/ricm2.json +0 -0
  102. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/neighborhood.py +0 -0
  103. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/preprocessing.py +0 -0
  104. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/regionprops/__init__.py +0 -0
  105. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/regionprops/props.json +0 -0
  106. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/relative_measurements.py +0 -0
  107. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/analyze_signals.py +0 -0
  108. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/measure_cells.py +0 -0
  109. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/measure_relative.py +0 -0
  110. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/segment_cells.py +0 -0
  111. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/segment_cells_thresholds.py +0 -0
  112. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/track_cells.py +0 -0
  113. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/train_segmentation_model.py +0 -0
  114. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/scripts/train_signal_model.py +0 -0
  115. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/segmentation.py +0 -0
  116. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/signals.py +0 -0
  117. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective/tracking.py +0 -0
  118. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/SOURCES.txt +0 -0
  119. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/dependency_links.txt +0 -0
  120. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/entry_points.txt +0 -0
  121. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/not-zip-safe +0 -0
  122. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/requires.txt +0 -0
  123. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/celldetective.egg-info/top_level.txt +0 -0
  124. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/setup.cfg +0 -0
  125. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/setup.py +0 -0
  126. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/__init__.py +0 -0
  127. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_events.py +0 -0
  128. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_filters.py +0 -0
  129. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_io.py +0 -0
  130. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_measure.py +0 -0
  131. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_neighborhood.py +0 -0
  132. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_preprocessing.py +0 -0
  133. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_qt.py +0 -0
  134. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_segmentation.py +0 -0
  135. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_signals.py +0 -0
  136. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_tracking.py +0 -0
  137. {celldetective-1.3.9.post3 → celldetective-1.3.9.post5}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: celldetective
3
- Version: 1.3.9.post3
3
+ Version: 1.3.9.post5
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -0,0 +1 @@
1
+ __version__ = "1.3.9.post5"
@@ -0,0 +1,437 @@
1
+ """
2
+ Copyright © 2022 Laboratoire Adhesion et Inflammation
3
+ Authored by R. Torro, K. Dervanova, L. Limozin
4
+
5
+ This module defines additional measurement functions for use with `regionprops` via `measure_features`.
6
+
7
+ Usage
8
+ -----
9
+ Each function must follow these conventions:
10
+
11
+ - **First argument:** `regionmask` (numpy array)
12
+ A binary mask of the cell of interest, as provided by `regionprops`.
13
+ - **Optional second argument:** `intensity_image` (numpy array)
14
+ An image crop/bounding box associated with the cell (single-channel at a time).
15
+
16
+ Unlike the default `regionprops` from `scikit-image`, the cell image is **not** masked with zeros outside its boundaries.
17
+ This allows thresholding techniques to be used in measurements.
18
+
19
+ Naming Conventions & Indexing
20
+ ------------------------------
21
+ - The measurement name is derived from the function name.
22
+ - If a function returns multiple values (e.g., for multichannel images), outputs are labeled sequentially:
23
+ `function-0`, `function-1`, etc.
24
+ - To rename these outputs, use `rename_intensity_column` from `celldetective.utils`.
25
+ - `"intensity"` in function names is automatically replaced with the actual channel name:
26
+ - Example: `"intensity-0"` → `"brightfield_channel"`.
27
+ - **Avoid digits smaller than the number of channels in function names** to prevent indexing conflicts.
28
+ Prefer text-based names instead:
29
+
30
+ .. code-block:: python
31
+
32
+ # Bad practice:
33
+ def intensity2(regionmask, intensity_image):
34
+ pass
35
+
36
+ # Recommended:
37
+ def intensity_two(regionmask, intensity_image):
38
+ pass
39
+
40
+ GUI Integration
41
+ ---------------
42
+ New functions are **automatically** added to the list of available measurements in the graphical interface.
43
+ """
44
+
45
+ import warnings
46
+
47
+ import numpy as np
48
+ from scipy.ndimage import distance_transform_edt, center_of_mass
49
+ from scipy.spatial.distance import euclidean
50
+ from celldetective.utils import interpolate_nan, contour_of_instance_segmentation
51
+ import skimage.measure as skm
52
+ from stardist import fill_label_holes
53
+ from celldetective.segmentation import segment_frame_from_thresholds
54
+ from sklearn.metrics import r2_score
55
+
56
+
57
+ # def area_detected_in_ricm(regionmask, intensity_image, target_channel='adhesion_channel'):
58
+
59
+ # instructions = {
60
+ # "thresholds": [
61
+ # 0.02,
62
+ # 1000
63
+ # ],
64
+ # "filters": [
65
+ # [
66
+ # "subtract",
67
+ # 1
68
+ # ],
69
+ # [
70
+ # "abs",
71
+ # 2
72
+ # ],
73
+ # [
74
+ # "gauss",
75
+ # 0.8
76
+ # ]
77
+ # ],
78
+ # #"marker_min_distance": 1,
79
+ # #"marker_footprint_size": 10,
80
+ # "feature_queries": [
81
+ # "eccentricity > 0.99 or area < 60"
82
+ # ],
83
+ # }
84
+
85
+ # lbl = segment_frame_from_thresholds(intensity_image, fill_holes=True, do_watershed=False, equalize_reference=None, edge_exclusion=False, **instructions)
86
+ # lbl[lbl>0] = 1 # instance to binary
87
+ # lbl[~regionmask] = 0 # make sure we don't measure stuff outside cell
88
+
89
+ # return np.sum(lbl)
90
+
91
+ def fraction_of_area_detected_in_intensity(regionmask, intensity_image, target_channel='adhesion_channel'):
92
+
93
+ instructions = {
94
+ "thresholds": [
95
+ 0.02,
96
+ 1000
97
+ ],
98
+ "filters": [
99
+ [
100
+ "subtract",
101
+ 1
102
+ ],
103
+ [
104
+ "abs",
105
+ 2
106
+ ],
107
+ [
108
+ "gauss",
109
+ 0.8
110
+ ]
111
+ ],
112
+ }
113
+
114
+ lbl = segment_frame_from_thresholds(intensity_image, do_watershed=False, fill_holes=True, equalize_reference=None, edge_exclusion=False, **instructions)
115
+ lbl[lbl>0] = 1 # instance to binary
116
+ lbl[~regionmask] = 0 # make sure we don't measure stuff outside cell
117
+
118
+ return float(np.sum(lbl)) / float(np.sum(regionmask))
119
+
120
+ def area_detected_in_intensity(regionmask, intensity_image, target_channel='adhesion_channel'):
121
+
122
+ """
123
+ Computes the detected area within the regionmask based on threshold-based segmentation.
124
+
125
+ The function applies a predefined filtering and thresholding pipeline to the intensity image (normalized adhesion channel)
126
+ to detect significant regions. The resulting segmented regions are restricted to the
127
+ `regionmask`, ensuring that only the relevant area is measured.
128
+
129
+ Parameters
130
+ ----------
131
+ regionmask : ndarray
132
+ A binary mask (2D array) where nonzero values define the region of interest.
133
+ intensity_image : ndarray
134
+ A 2D array of the same shape as `regionmask`, representing the intensity
135
+ values associated with the region.
136
+ target_channel : str, optional
137
+ Name of the intensity channel used for measurement. Defaults to `'adhesion_channel'`.
138
+
139
+ Returns
140
+ -------
141
+ detected_area : float
142
+ The total area (number of pixels) detected based on intensity-based segmentation.
143
+
144
+ Notes
145
+ -----
146
+ - The segmentation is performed using `segment_frame_from_thresholds()` with predefined parameters:
147
+
148
+ - Thresholding range: `[0.02, 1000]`
149
+ - Filters applied in sequence:
150
+
151
+ - `"subtract"` with value `1` (subtract 1 from intensity values)
152
+ - `"abs"` (take absolute value of intensities)
153
+ - `"gauss"` with sigma `0.8` (apply Gauss filter with sigma `0.8`)
154
+
155
+ - The segmentation includes hole filling.
156
+ - The detected regions are converted to a binary mask (`lbl > 0`).
157
+ - Any pixels outside the `regionmask` are excluded from the measurement.
158
+
159
+ """
160
+
161
+ instructions = {
162
+ "thresholds": [
163
+ 0.02,
164
+ 1000
165
+ ],
166
+ "filters": [
167
+ [
168
+ "subtract",
169
+ 1
170
+ ],
171
+ [
172
+ "abs",
173
+ 2
174
+ ],
175
+ [
176
+ "gauss",
177
+ 0.8
178
+ ]
179
+ ],
180
+ }
181
+
182
+ lbl = segment_frame_from_thresholds(intensity_image, do_watershed=False, fill_holes=True, equalize_reference=None, edge_exclusion=False, **instructions)
183
+ lbl[lbl>0] = 1 # instance to binary
184
+ lbl[~regionmask] = 0 # make sure we don't measure stuff outside cell
185
+
186
+ return float(np.sum(lbl))
187
+
188
+
189
+ def area_dark_intensity(regionmask, intensity_image, target_channel='adhesion_channel', fill_holes=True, threshold=0.95): #, target_channel='adhesion_channel'
190
+
191
+ """
192
+ Computes the absolute area within the regionmask where the intensity is below a given threshold.
193
+
194
+ This function identifies pixels in the region where the intensity is lower than `threshold`.
195
+ If `fill_holes` is `True`, small enclosed holes in the detected dark regions are filled before
196
+ computing the total area.
197
+
198
+ Parameters
199
+ ----------
200
+ regionmask : ndarray
201
+ A binary mask (2D array) where nonzero values define the region of interest.
202
+ intensity_image : ndarray
203
+ A 2D array of the same shape as `regionmask`, representing the intensity
204
+ values associated with the region.
205
+ target_channel : str, optional
206
+ Name of the intensity channel used for measurement. Defaults to `'adhesion_channel'`.
207
+ fill_holes : bool, optional
208
+ If `True`, fills enclosed holes in the detected dark intensity regions before computing
209
+ the area. Defaults to `True`.
210
+ threshold : float, optional
211
+ Intensity threshold below which a pixel is considered part of a dark region.
212
+ Defaults to `0.95`.
213
+
214
+ Returns
215
+ -------
216
+ dark_area : float
217
+ The absolute area (number of pixels) where intensity values are below `threshold`, within the regionmask.
218
+
219
+ Notes
220
+ -----
221
+ - The default threshold for defining "dark" intensity regions is `0.95`, but it can be adjusted.
222
+ - If `fill_holes` is `True`, the function applies hole-filling to the detected dark regions
223
+ using `skimage.measure.label` and `fill_label_holes()`.
224
+ - The `target_channel` parameter tells regionprops to only measure this channel.
225
+
226
+ """
227
+
228
+ subregion = (intensity_image < threshold)*regionmask # under one, under 0.8, under 0.6, whatever value!
229
+ if fill_holes:
230
+ subregion = skm.label(subregion, connectivity=2, background=0)
231
+ subregion = fill_label_holes(subregion)
232
+ subregion[subregion>0] = 1
233
+
234
+ return float(np.sum(subregion))
235
+
236
+
237
+ def fraction_of_area_dark_intensity(regionmask, intensity_image, target_channel='adhesion_channel', fill_holes=True, threshold=0.95): #, target_channel='adhesion_channel'
238
+
239
+ subregion = (intensity_image < threshold)*regionmask # under one, under 0.8, under 0.6, whatever value!
240
+ if fill_holes:
241
+ subregion = skm.label(subregion, connectivity=2, background=0)
242
+ subregion = fill_label_holes(subregion)
243
+ subregion[subregion>0] = 1
244
+
245
+ return float(np.sum(subregion)) / float(np.sum(regionmask))
246
+
247
+
248
+ def intensity_percentile_ninety_nine(regionmask, intensity_image):
249
+ return np.nanpercentile(intensity_image[regionmask],99)
250
+
251
+ def intensity_percentile_ninety_five(regionmask, intensity_image):
252
+ return np.nanpercentile(intensity_image[regionmask],95)
253
+
254
+ def intensity_percentile_ninety(regionmask, intensity_image):
255
+ return np.nanpercentile(intensity_image[regionmask],90)
256
+
257
+ def intensity_percentile_seventy_five(regionmask, intensity_image):
258
+ return np.nanpercentile(intensity_image[regionmask],75)
259
+
260
+ def intensity_percentile_fifty(regionmask, intensity_image):
261
+ return np.nanpercentile(intensity_image[regionmask],50)
262
+
263
+ def intensity_percentile_twenty_five(regionmask, intensity_image):
264
+ return np.nanpercentile(intensity_image[regionmask],25)
265
+
266
+ # STD
267
+
268
+ def intensity_std(regionmask, intensity_image):
269
+ return np.nanstd(intensity_image[regionmask])
270
+
271
+
272
+ def intensity_median(regionmask, intensity_image):
273
+ return np.nanmedian(intensity_image[regionmask])
274
+
275
+ def intensity_nanmean(regionmask, intensity_image):
276
+
277
+ if np.all(intensity_image==0):
278
+ return np.nan
279
+ else:
280
+ return np.nanmean(intensity_image[regionmask])
281
+
282
+ def intensity_center_of_mass_displacement(regionmask, intensity_image):
283
+
284
+ """
285
+ Computes the displacement between the geometric centroid and the
286
+ intensity-weighted center of mass of a region.
287
+
288
+ Parameters
289
+ ----------
290
+ regionmask : ndarray
291
+ A binary mask (2D array) where nonzero values indicate the region of interest.
292
+ intensity_image : ndarray
293
+ A 2D array of the same shape as `regionmask`, representing the intensity
294
+ values associated with the region.
295
+
296
+ Returns
297
+ -------
298
+ distance : float
299
+ Euclidean distance between the geometric centroid and the intensity-weighted center of mass.
300
+ direction_arctan : float
301
+ Angle (in degrees) of displacement from the geometric centroid to the intensity-weighted center of mass,
302
+ computed using `arctan2(delta_y, delta_x)`.
303
+ delta_x : float
304
+ Difference in x-coordinates (intensity-weighted centroid - geometric centroid).
305
+ delta_y : float
306
+ Difference in y-coordinates (intensity-weighted centroid - geometric centroid).
307
+
308
+ Notes
309
+ -----
310
+ - If the `intensity_image` contains NaN values, it is first processed using `interpolate_nan()`.
311
+ - Negative intensity values are set to zero to prevent misbehavior in center of mass calculation.
312
+ - If the intensity image is entirely zero, all outputs are `NaN`.
313
+
314
+ """
315
+
316
+ if np.any(intensity_image!=intensity_image):
317
+ intensity_image = interpolate_nan(intensity_image.copy())
318
+
319
+ if not np.all(intensity_image.flatten()==0):
320
+
321
+ y, x = np.mgrid[:regionmask.shape[0], :regionmask.shape[1]]
322
+ xtemp = x.copy()
323
+ ytemp = y.copy()
324
+
325
+ intensity_image[intensity_image<=0.] = 0. #important to clip as negative intensities misbehave with center of mass
326
+ intensity_weighted_center = center_of_mass(intensity_image*regionmask, regionmask, 1)
327
+ centroid_x = intensity_weighted_center[1]
328
+ centroid_y = intensity_weighted_center[0]
329
+
330
+ geometric_centroid_x = np.sum(xtemp * regionmask) / np.sum(regionmask)
331
+ geometric_centroid_y = np.sum(ytemp * regionmask) / np.sum(regionmask)
332
+ distance = np.sqrt((geometric_centroid_y - centroid_y)**2 + (geometric_centroid_x - centroid_x)**2)
333
+
334
+ delta_x = geometric_centroid_x - centroid_x
335
+ delta_y = geometric_centroid_y - centroid_y
336
+ direction_arctan = np.arctan2(delta_y, delta_x) * 180 / np.pi
337
+
338
+ return distance, direction_arctan, centroid_x - geometric_centroid_x, centroid_y - geometric_centroid_y
339
+
340
+ else:
341
+ return np.nan, np.nan, np.nan, np.nan
342
+
343
+
344
+ def intensity_center_of_mass_displacement_edge(regionmask, intensity_image):
345
+
346
+ if np.any(intensity_image!=intensity_image):
347
+ intensity_image = interpolate_nan(intensity_image.copy())
348
+
349
+ edge_mask = contour_of_instance_segmentation(regionmask, 3)
350
+
351
+ if not np.all(intensity_image.flatten()==0) and np.sum(edge_mask)>0:
352
+
353
+ y, x = np.mgrid[:edge_mask.shape[0], :edge_mask.shape[1]]
354
+ xtemp = x.copy()
355
+ ytemp = y.copy()
356
+
357
+ intensity_image[intensity_image<=0.] = 0. #important to clip as negative intensities misbehave with center of mass
358
+ intensity_weighted_center = center_of_mass(intensity_image*edge_mask, edge_mask, 1)
359
+ centroid_x = intensity_weighted_center[1]
360
+ centroid_y = intensity_weighted_center[0]
361
+
362
+ #centroid_x = np.sum(xtemp * intensity_image) / np.sum(intensity_image)
363
+ geometric_centroid_x = np.sum(xtemp * regionmask) / np.sum(regionmask)
364
+ geometric_centroid_y = np.sum(ytemp * regionmask) / np.sum(regionmask)
365
+
366
+ distance = np.sqrt((geometric_centroid_y - centroid_y)**2 + (geometric_centroid_x - centroid_x)**2)
367
+
368
+ delta_x = geometric_centroid_x - centroid_x
369
+ delta_y = geometric_centroid_y - centroid_y
370
+ direction_arctan = np.arctan2(delta_y, delta_x) * 180 / np.pi
371
+
372
+ return distance, direction_arctan, centroid_x - geometric_centroid_x, centroid_y - geometric_centroid_y
373
+ else:
374
+ return np.nan, np.nan, np.nan, np.nan
375
+
376
+
377
+ def intensity_radial_gradient(regionmask, intensity_image):
378
+
379
+ """
380
+ Determines whether the intensity follows a radial gradient from the center to the edge of the cell.
381
+
382
+ The function fits a linear model to the intensity values as a function of distance from the center
383
+ (computed via the Euclidean distance transform). The slope of the fitted line indicates whether
384
+ the intensity is higher at the center or at the edges.
385
+
386
+ Parameters
387
+ ----------
388
+ regionmask : ndarray
389
+ A binary mask (2D array) where nonzero values define the region of interest.
390
+ intensity_image : ndarray
391
+ A 2D array of the same shape as `regionmask`, representing the intensity
392
+ values associated with the region.
393
+
394
+ Returns
395
+ -------
396
+ slope : float
397
+ Slope of the fitted linear model.
398
+
399
+ - If `slope > 0`: Intensity increases towards the edge.
400
+ - If `slope < 0`: Intensity is higher at the center.
401
+
402
+ intercept : float
403
+ Intercept of the fitted linear model.
404
+ r2 : float
405
+ Coefficient of determination (R²), indicating how well the linear model fits the intensity profile.
406
+
407
+ Notes
408
+ -----
409
+ - If the `intensity_image` contains NaN values, they are interpolated using `interpolate_nan()`.
410
+ - The Euclidean distance transform (`distance_transform_edt`) is used to compute the distance
411
+ of each pixel from the edge.
412
+ - The x-values for the linear fit are reversed so that the origin is at the center.
413
+ - A warning suppression is applied to ignore messages about poorly conditioned polynomial fits.
414
+
415
+ """
416
+
417
+ if np.any(intensity_image!=intensity_image):
418
+ intensity_image = interpolate_nan(intensity_image.copy())
419
+
420
+ # try:
421
+ warnings.filterwarnings('ignore', message="Polyfit may be poorly conditioned")
422
+
423
+ # intensities
424
+ y = intensity_image[regionmask].flatten()
425
+
426
+ # distance to edge
427
+ x = distance_transform_edt(regionmask.copy())
428
+ x = x[regionmask].flatten()
429
+ x = max(x) - x # origin at center of cells
430
+
431
+ params = np.polyfit(x, y, 1)
432
+ line = np.poly1d(params)
433
+ # coef > 0 --> more signal at edge than center, coef < 0 --> more signal at center than edge
434
+
435
+ r2 = r2_score(y, line(x))
436
+
437
+ return line.coefficients[0], line.coefficients[1], r2
@@ -482,8 +482,12 @@ class ClassifierWidget(QWidget, Styles):
482
482
 
483
483
  if 'custom' in list(self.df.columns):
484
484
  self.df = self.df.drop(['custom'],axis=1)
485
-
485
+
486
+ self.fig_props.set_size_inches(4,3)
487
+ self.fig_props.suptitle(self.property_query_le.text(), fontsize=10)
488
+ self.fig_props.tight_layout()
486
489
  for pos,pos_group in self.df.groupby('position'):
490
+ self.fig_props.savefig(pos+os.sep.join(['output',f'{self.class_name}.png']), bbox_inches='tight', dpi=300)
487
491
  pos_group.to_csv(pos+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
488
492
 
489
493
  self.parent_window.parent_window.update_position_options()
@@ -297,9 +297,12 @@ class ConfigSignalPlot(QWidget, Styles):
297
297
  self.compute_signal_functions()
298
298
  if self.open_widget:
299
299
  self.interpret_pos_location()
300
- self.plot_window = GenericSignalPlotWidget(parent_window=self, df=self.df, df_pos_info = self.df_pos_info, df_well_info = self.df_well_info, feature_selected=self.feature_selected, title='plot signals')
301
- self.plot_window.show()
302
-
300
+ try:
301
+ self.plot_window = GenericSignalPlotWidget(parent_window=self, df=self.df, df_pos_info = self.df_pos_info, df_well_info = self.df_well_info, feature_selected=self.feature_selected, title='plot signals')
302
+ self.plot_window.show()
303
+ except Exception as e:
304
+ print(f"{e=}")
305
+
303
306
  def process_signal(self):
304
307
 
305
308
  self.FrameToMin = float(self.time_calibration_le.text().replace(',','.'))
@@ -807,8 +807,6 @@ class SignalAnnotator(QMainWindow, Styles):
807
807
  cols_to_remove += time_cols
808
808
  #cols_to_remove.extend(self.df_tracks.select_dtypes(include=['object']).columns)
809
809
 
810
- print(f"{cols_to_remove=}")
811
-
812
810
  for tr in cols_to_remove:
813
811
  try:
814
812
  self.columns_to_rescale.remove(tr)
@@ -856,6 +854,16 @@ class SignalAnnotator(QMainWindow, Styles):
856
854
  'class', 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name',
857
855
  'pos_name', 'index','class_color','status_color']
858
856
 
857
+ meta = get_experiment_metadata(self.exp_dir)
858
+ if meta is not None:
859
+ keys = list(meta.keys())
860
+ to_remove.extend(keys)
861
+
862
+ labels = get_experiment_labels(self.exp_dir)
863
+ if labels is not None:
864
+ keys = list(labels.keys())
865
+ to_remove.extend(labels)
866
+
859
867
  for c in to_remove:
860
868
  if c in signals:
861
869
  signals.remove(c)
@@ -1941,10 +1941,10 @@ def relabel_segmentation(labels, df, exclude_nans=True, column_labels={'track':
1941
1941
 
1942
1942
  def rewrite_labels(indices):
1943
1943
 
1944
- all_track_ids = df[column_labels['track']].unique()
1944
+ all_track_ids = df[column_labels['track']].dropna().unique()
1945
1945
 
1946
1946
  for t in tqdm(indices):
1947
-
1947
+
1948
1948
  f = int(t)
1949
1949
  cells = df.loc[df[column_labels['frame']] == f, [column_labels['track'], column_labels['label']]].to_numpy()
1950
1950
  tracks_at_t = list(cells[:,0])
@@ -1974,15 +1974,23 @@ def relabel_segmentation(labels, df, exclude_nans=True, column_labels={'track':
1974
1974
 
1975
1975
  loc_i, loc_j = np.where(labels[f] == identities[k])
1976
1976
  track_id = tracks_at_t[k]
1977
- new_labels[f, loc_i, loc_j] = round(track_id)
1977
+
1978
+ if track_id==track_id:
1979
+ new_labels[f, loc_i, loc_j] = round(track_id)
1978
1980
 
1979
1981
  # Multithreading
1980
- indices = list(df[column_labels['frame']].unique())
1982
+ indices = list(df[column_labels['frame']].dropna().unique())
1981
1983
  chunks = np.array_split(indices, n_threads)
1982
1984
 
1983
- with concurrent.futures.ThreadPoolExecutor() as executor:
1984
- executor.map(rewrite_labels, chunks)
1985
-
1985
+ with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
1986
+
1987
+ results = executor.map(rewrite_labels, chunks) #list(map(lambda x: executor.submit(self.parallel_job, x), chunks))
1988
+ try:
1989
+ for i,return_value in enumerate(results):
1990
+ print(f"Thread {i} output check: ",return_value)
1991
+ except Exception as e:
1992
+ print("Exception: ", e)
1993
+
1986
1994
  print("\nDone.")
1987
1995
 
1988
1996
  return new_labels
@@ -2088,6 +2096,7 @@ def tracks_to_btrack(df, exclude_nans=False):
2088
2096
  graph = {}
2089
2097
  if exclude_nans:
2090
2098
  df.dropna(subset='class_id',inplace=True)
2099
+ df.dropna(subset='TRACK_ID',inplace=True)
2091
2100
 
2092
2101
  df["z"] = 0.
2093
2102
  data = df[["TRACK_ID","FRAME","z","POSITION_Y","POSITION_X"]].to_numpy()
@@ -108,10 +108,17 @@ class CustomRegionProps(RegionProperties):
108
108
 
109
109
  idx = self.channel_names.index(default_channel)
110
110
  res = func(self.image, self.image_intensity[..., idx])
111
- len_output = len(res)
111
+ if isinstance(res, tuple):
112
+ len_output = len(res)
113
+ else:
114
+ len_output = 1
112
115
 
113
- multichannel_list = [[np.nan]*len_output for c in range(len(self.channel_names))]
114
- multichannel_list[idx] = res
116
+ if len_output > 1:
117
+ multichannel_list = [[np.nan]*len_output for c in range(len(self.channel_names))]
118
+ multichannel_list[idx] = res
119
+ else:
120
+ multichannel_list = [np.nan for c in range(len(self.channel_names))]
121
+ multichannel_list[idx] = res
115
122
 
116
123
  else:
117
124
  print(f'Warning... Channel required by custom measurement ({default_channel}) could not be found in your data...')
@@ -1264,7 +1264,6 @@ def rename_intensity_column(df, channels):
1264
1264
  if s.isdigit():
1265
1265
  if int(s)<len(channel_names):
1266
1266
  test_digit[j] = True
1267
- print(f"{test_digit=}")
1268
1267
 
1269
1268
  if np.any(test_digit):
1270
1269
  index = int(sections[np.where(test_digit)[0]][-1])
@@ -1289,36 +1288,36 @@ def rename_intensity_column(df, channels):
1289
1288
  new_name = np.delete(measure, -1)
1290
1289
  new_name = '_'.join(list(new_name))
1291
1290
  if 'edge' in intensity_cols[k]:
1292
- new_name = new_name.replace('centre_of_mass_displacement', "edge_centre_of_mass_displacement_in_px")
1291
+ new_name = new_name.replace('center_of_mass_displacement', "edge_center_of_mass_displacement_in_px")
1293
1292
  else:
1294
- new_name = new_name.replace('centre_of_mass', "centre_of_mass_displacement_in_px")
1293
+ new_name = new_name.replace('center_of_mass', "center_of_mass_displacement_in_px")
1295
1294
  to_rename.update({intensity_cols[k]: new_name.replace('-', '_')})
1296
1295
 
1297
1296
  elif sections[-2] == "1":
1298
1297
  new_name = np.delete(measure, -1)
1299
1298
  new_name = '_'.join(list(new_name))
1300
1299
  if 'edge' in intensity_cols[k]:
1301
- new_name = new_name.replace('centre_of_mass_displacement', "edge_centre_of_mass_orientation")
1300
+ new_name = new_name.replace('center_of_mass_displacement', "edge_center_of_mass_orientation")
1302
1301
  else:
1303
- new_name = new_name.replace('centre_of_mass', "centre_of_mass_orientation")
1302
+ new_name = new_name.replace('center_of_mass', "center_of_mass_orientation")
1304
1303
  to_rename.update({intensity_cols[k]: new_name.replace('-', '_')})
1305
1304
 
1306
1305
  elif sections[-2] == "2":
1307
1306
  new_name = np.delete(measure, -1)
1308
1307
  new_name = '_'.join(list(new_name))
1309
1308
  if 'edge' in intensity_cols[k]:
1310
- new_name = new_name.replace('centre_of_mass_displacement', "edge_centre_of_mass_x")
1309
+ new_name = new_name.replace('center_of_mass_displacement', "edge_center_of_mass_x")
1311
1310
  else:
1312
- new_name = new_name.replace('centre_of_mass', "centre_of_mass_x")
1311
+ new_name = new_name.replace('center_of_mass', "center_of_mass_x")
1313
1312
  to_rename.update({intensity_cols[k]: new_name.replace('-', '_')})
1314
1313
 
1315
1314
  elif sections[-2] == "3":
1316
1315
  new_name = np.delete(measure, -1)
1317
1316
  new_name = '_'.join(list(new_name))
1318
1317
  if 'edge' in intensity_cols[k]:
1319
- new_name = new_name.replace('centre_of_mass_displacement', "edge_centre_of_mass_y")
1318
+ new_name = new_name.replace('center_of_mass_displacement', "edge_center_of_mass_y")
1320
1319
  else:
1321
- new_name = new_name.replace('centre_of_mass', "centre_of_mass_y")
1320
+ new_name = new_name.replace('center_of_mass', "center_of_mass_y")
1322
1321
  to_rename.update({intensity_cols[k]: new_name.replace('-', '_')})
1323
1322
 
1324
1323
  if 'radial_gradient' in intensity_cols[k]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: celldetective
3
- Version: 1.3.9.post3
3
+ Version: 1.3.9.post5
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -1 +0,0 @@
1
- __version__ = "1.3.9.post3"