celldetective 1.5.0b9__py3-none-any.whl → 1.5.0b11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/_version.py +1 -1
- celldetective/gui/base/list_widget.py +25 -10
- celldetective/gui/event_annotator.py +32 -3
- celldetective/gui/interactions_block.py +11 -2
- celldetective/gui/pair_event_annotator.py +39 -31
- celldetective/gui/settings/_settings_measurements.py +14 -7
- celldetective/gui/settings/_settings_neighborhood.py +1 -0
- celldetective/gui/viewers/contour_viewer.py +14 -2
- celldetective/gui/viewers/spot_detection_viewer.py +14 -0
- celldetective/measure.py +22 -13
- celldetective/utils/data_cleaning.py +7 -3
- celldetective/utils/masks.py +15 -8
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/METADATA +1 -1
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/RECORD +22 -20
- tests/gui/test_event_annotator_cleanup.py +310 -0
- tests/gui/test_spot_detection_viewer.py +207 -0
- tests/test_contour_format.py +299 -0
- tests/test_measure.py +318 -129
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/WHEEL +0 -0
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.5.0b9.dist-info → celldetective-1.5.0b11.dist-info}/top_level.txt +0 -0
celldetective/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.5.
|
|
1
|
+
__version__ = "1.5.0b11"
|
|
@@ -89,7 +89,7 @@ class ListWidget(CelldetectiveWidget):
|
|
|
89
89
|
"""
|
|
90
90
|
Retrieves and returns the items from the list widget.
|
|
91
91
|
|
|
92
|
-
This method parses any items that contain a range (formatted as 'min
|
|
92
|
+
This method parses any items that contain a range (formatted as '(min,max)')
|
|
93
93
|
into a list of two values, and casts all items to the specified `dtype`.
|
|
94
94
|
|
|
95
95
|
Returns
|
|
@@ -97,18 +97,33 @@ class ListWidget(CelldetectiveWidget):
|
|
|
97
97
|
list
|
|
98
98
|
A list of the items in the list widget, with ranges split into two values.
|
|
99
99
|
"""
|
|
100
|
+
import re
|
|
101
|
+
|
|
102
|
+
# Pattern for tuple format: "(min,max)" with optional negative numbers and decimals
|
|
103
|
+
tuple_pattern = re.compile(r"^\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)$")
|
|
100
104
|
|
|
101
105
|
items = []
|
|
102
106
|
for x in range(self.list_widget.count()):
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
text = self.list_widget.item(x).text().strip()
|
|
108
|
+
|
|
109
|
+
# Try tuple format first: (min,max)
|
|
110
|
+
tuple_match = tuple_pattern.match(text)
|
|
111
|
+
if tuple_match:
|
|
112
|
+
try:
|
|
113
|
+
minn = self.dtype(float(tuple_match.group(1)))
|
|
114
|
+
maxx = self.dtype(float(tuple_match.group(2)))
|
|
115
|
+
items.append([minn, maxx])
|
|
116
|
+
continue
|
|
117
|
+
except ValueError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Single value
|
|
121
|
+
try:
|
|
122
|
+
items.append(self.dtype(text))
|
|
123
|
+
except ValueError:
|
|
124
|
+
print(
|
|
125
|
+
f"Warning: Could not convert '{text}' to {self.dtype.__name__}, skipping..."
|
|
126
|
+
)
|
|
112
127
|
return items
|
|
113
128
|
|
|
114
129
|
def clear(self):
|
|
@@ -850,11 +850,40 @@ class EventAnnotator(BaseAnnotator):
|
|
|
850
850
|
|
|
851
851
|
try:
|
|
852
852
|
self.stop()
|
|
853
|
-
|
|
854
|
-
gc.collect()
|
|
855
|
-
except:
|
|
853
|
+
except Exception:
|
|
856
854
|
pass
|
|
857
855
|
|
|
856
|
+
# Stop and delete animation to break reference cycles
|
|
857
|
+
if hasattr(self, "anim") and self.anim:
|
|
858
|
+
try:
|
|
859
|
+
self.anim.event_source.stop()
|
|
860
|
+
except Exception:
|
|
861
|
+
pass
|
|
862
|
+
del self.anim
|
|
863
|
+
|
|
864
|
+
# Close matplotlib figures
|
|
865
|
+
if hasattr(self, "fig"):
|
|
866
|
+
try:
|
|
867
|
+
plt.close(self.fig)
|
|
868
|
+
except Exception:
|
|
869
|
+
pass
|
|
870
|
+
|
|
871
|
+
if hasattr(self, "cell_fig"):
|
|
872
|
+
try:
|
|
873
|
+
plt.close(self.cell_fig)
|
|
874
|
+
except Exception:
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
# Delete large objects
|
|
878
|
+
if hasattr(self, "stack"):
|
|
879
|
+
del self.stack
|
|
880
|
+
|
|
881
|
+
if hasattr(self, "df_tracks"):
|
|
882
|
+
del self.df_tracks
|
|
883
|
+
|
|
884
|
+
gc.collect()
|
|
885
|
+
super().closeEvent(event)
|
|
886
|
+
|
|
858
887
|
def animation_generator(self):
|
|
859
888
|
"""
|
|
860
889
|
Generator yielding frame indices for the animation,
|
|
@@ -684,5 +684,14 @@ class NeighPanel(QFrame, Styles):
|
|
|
684
684
|
|
|
685
685
|
test = self.parent_window.locate_selected_position()
|
|
686
686
|
if test:
|
|
687
|
-
|
|
688
|
-
|
|
687
|
+
try:
|
|
688
|
+
self.pair_event_annotator = PairEventAnnotator(self)
|
|
689
|
+
self.pair_event_annotator.show()
|
|
690
|
+
except ValueError as e:
|
|
691
|
+
# Show useful feedback if no neighborhood detected
|
|
692
|
+
msg = QMessageBox()
|
|
693
|
+
msg.setIcon(QMessageBox.Warning)
|
|
694
|
+
msg.setText(str(e))
|
|
695
|
+
msg.setWindowTitle("Warning")
|
|
696
|
+
msg.exec_()
|
|
697
|
+
return
|
|
@@ -172,6 +172,10 @@ class PairEventAnnotator(CelldetectiveMainWindow):
|
|
|
172
172
|
print(
|
|
173
173
|
f"The following neighborhoods were detected: {self.neighborhood_cols=}..."
|
|
174
174
|
)
|
|
175
|
+
if len(self.neighborhood_cols) == 0:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
"No neighborhoods detected. Please compute neighborhoods first."
|
|
178
|
+
)
|
|
175
179
|
|
|
176
180
|
self.locate_relative_tracks()
|
|
177
181
|
|
|
@@ -1449,6 +1453,7 @@ class PairEventAnnotator(CelldetectiveMainWindow):
|
|
|
1449
1453
|
self.reference_population = self.neighborhood_choice_cb.currentText().split(
|
|
1450
1454
|
"_"
|
|
1451
1455
|
)[0]
|
|
1456
|
+
self.neighbor_population = self.reference_population
|
|
1452
1457
|
|
|
1453
1458
|
if "_(" in self.current_neighborhood and ")_" in self.current_neighborhood:
|
|
1454
1459
|
self.neighbor_population = (
|
|
@@ -1457,9 +1462,6 @@ class PairEventAnnotator(CelldetectiveMainWindow):
|
|
|
1457
1462
|
self.reference_population = (
|
|
1458
1463
|
self.current_neighborhood.split("_(")[-1].split(")_")[0].split("-")[0]
|
|
1459
1464
|
)
|
|
1460
|
-
else:
|
|
1461
|
-
if "self" in self.current_neighborhood:
|
|
1462
|
-
self.neighbor_population = self.reference_population
|
|
1463
1465
|
|
|
1464
1466
|
print(f"Current neighborhood: {self.current_neighborhood}")
|
|
1465
1467
|
print(f"New reference population: {self.reference_population}")
|
|
@@ -2183,18 +2185,44 @@ class PairEventAnnotator(CelldetectiveMainWindow):
|
|
|
2183
2185
|
|
|
2184
2186
|
def closeEvent(self, event):
|
|
2185
2187
|
|
|
2186
|
-
self.stop()
|
|
2187
|
-
# result = QMessageBox.question(self,
|
|
2188
|
-
# "Confirm Exit...",
|
|
2189
|
-
# "Are you sure you want to exit ?",
|
|
2190
|
-
# QMessageBox.Yes| QMessageBox.No,
|
|
2191
|
-
# )
|
|
2192
2188
|
try:
|
|
2193
|
-
|
|
2194
|
-
except:
|
|
2189
|
+
self.stop()
|
|
2190
|
+
except Exception:
|
|
2195
2191
|
pass
|
|
2196
2192
|
|
|
2193
|
+
# Stop and delete animation to break reference cycles
|
|
2194
|
+
if hasattr(self, "anim") and self.anim:
|
|
2195
|
+
try:
|
|
2196
|
+
self.anim.event_source.stop()
|
|
2197
|
+
except Exception:
|
|
2198
|
+
pass
|
|
2199
|
+
del self.anim
|
|
2200
|
+
|
|
2201
|
+
# Close matplotlib figures
|
|
2202
|
+
if hasattr(self, "fig"):
|
|
2203
|
+
try:
|
|
2204
|
+
plt.close(self.fig)
|
|
2205
|
+
except Exception:
|
|
2206
|
+
pass
|
|
2207
|
+
|
|
2208
|
+
if hasattr(self, "cell_fig"):
|
|
2209
|
+
try:
|
|
2210
|
+
plt.close(self.cell_fig)
|
|
2211
|
+
except Exception:
|
|
2212
|
+
pass
|
|
2213
|
+
|
|
2214
|
+
# Delete large objects
|
|
2215
|
+
if hasattr(self, "stack"):
|
|
2216
|
+
del self.stack
|
|
2217
|
+
|
|
2218
|
+
if hasattr(self, "dataframes"):
|
|
2219
|
+
self.dataframes.clear()
|
|
2220
|
+
|
|
2221
|
+
if hasattr(self, "df_relative"):
|
|
2222
|
+
del self.df_relative
|
|
2223
|
+
|
|
2197
2224
|
gc.collect()
|
|
2225
|
+
super().closeEvent(event)
|
|
2198
2226
|
|
|
2199
2227
|
def animation_generator(self):
|
|
2200
2228
|
"""
|
|
@@ -2982,26 +3010,6 @@ class PairEventAnnotator(CelldetectiveMainWindow):
|
|
|
2982
3010
|
self.draw_frame(self.framedata)
|
|
2983
3011
|
self.fcanvas.canvas.draw()
|
|
2984
3012
|
|
|
2985
|
-
def set_first_frame(self):
|
|
2986
|
-
self.stop()
|
|
2987
|
-
self.framedata = 0
|
|
2988
|
-
self.draw_frame(self.framedata)
|
|
2989
|
-
self.fcanvas.canvas.draw()
|
|
2990
|
-
|
|
2991
|
-
def set_last_frame(self):
|
|
2992
|
-
self.stop()
|
|
2993
|
-
self.framedata = len(self.stack) - 1
|
|
2994
|
-
while len(np.where(self.stack[self.framedata].flatten() == 0)[0]) > 0.99 * len(
|
|
2995
|
-
self.stack[self.framedata].flatten()
|
|
2996
|
-
):
|
|
2997
|
-
self.framedata -= 1
|
|
2998
|
-
if self.framedata < 0:
|
|
2999
|
-
self.framedata = 0
|
|
3000
|
-
break
|
|
3001
|
-
|
|
3002
|
-
self.draw_frame(self.framedata)
|
|
3003
|
-
self.fcanvas.canvas.draw()
|
|
3004
|
-
|
|
3005
3013
|
def give_reference_cell_information(self):
|
|
3006
3014
|
|
|
3007
3015
|
df_reference = self.dataframes[self.reference_population]
|
|
@@ -680,16 +680,15 @@ class SettingsMeasurements(CelldetectiveSettingsPanel):
|
|
|
680
680
|
border_distances = measurement_instructions["border_distances"]
|
|
681
681
|
if border_distances is not None:
|
|
682
682
|
if isinstance(border_distances, int):
|
|
683
|
-
distances = [border_distances]
|
|
683
|
+
distances = [str(border_distances)]
|
|
684
684
|
elif isinstance(border_distances, list):
|
|
685
685
|
distances = []
|
|
686
686
|
for d in border_distances:
|
|
687
|
-
if isinstance(d, int
|
|
687
|
+
if isinstance(d, (int, float)):
|
|
688
688
|
distances.append(str(int(d)))
|
|
689
689
|
elif isinstance(d, list):
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
)
|
|
690
|
+
# Use (min,max) tuple format to match CellEdgeVisualizer output
|
|
691
|
+
distances.append(f"({int(d[0])},{int(d[1])})")
|
|
693
692
|
self.contours_list.list_widget.clear()
|
|
694
693
|
self.contours_list.list_widget.addItems(distances)
|
|
695
694
|
|
|
@@ -838,7 +837,6 @@ class SettingsMeasurements(CelldetectiveSettingsPanel):
|
|
|
838
837
|
self.imshow_digit_window.canvas.draw()
|
|
839
838
|
self.imshow_digit_window.show()
|
|
840
839
|
|
|
841
|
-
|
|
842
840
|
def control_haralick_intensity_histogram(self):
|
|
843
841
|
"""
|
|
844
842
|
Load an image for the first experiment movie found.
|
|
@@ -1123,7 +1121,9 @@ class SettingsMeasurements(CelldetectiveSettingsPanel):
|
|
|
1123
1121
|
# else:
|
|
1124
1122
|
# invert_value = None
|
|
1125
1123
|
|
|
1126
|
-
from celldetective.gui.viewers.spot_detection_viewer import
|
|
1124
|
+
from celldetective.gui.viewers.spot_detection_viewer import (
|
|
1125
|
+
SpotDetectionVisualizer,
|
|
1126
|
+
)
|
|
1127
1127
|
|
|
1128
1128
|
self.spot_visual = SpotDetectionVisualizer(
|
|
1129
1129
|
frame_slider=True,
|
|
@@ -1139,6 +1139,13 @@ class SettingsMeasurements(CelldetectiveSettingsPanel):
|
|
|
1139
1139
|
parent_diameter_le=self.diameter_value,
|
|
1140
1140
|
parent_threshold_le=self.threshold_value,
|
|
1141
1141
|
parent_preprocessing_list=self.spot_preprocessing.list,
|
|
1142
|
+
initial_diameter=self.diameter_value.text(),
|
|
1143
|
+
initial_threshold=self.threshold_value.text(),
|
|
1144
|
+
initial_preprocessing=(
|
|
1145
|
+
self.spot_preprocessing.list.items
|
|
1146
|
+
if self.spot_preprocessing.list.items
|
|
1147
|
+
else None
|
|
1148
|
+
),
|
|
1142
1149
|
# parent_invert_check=self.invert_check,
|
|
1143
1150
|
# invert = self.invert_check.isChecked(),
|
|
1144
1151
|
# invert_value = self.invert_value_le.text().replace(',','.'),
|
|
@@ -72,6 +72,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
72
72
|
labels=None,
|
|
73
73
|
initial_edge=5,
|
|
74
74
|
initial_mask_alpha=0.5,
|
|
75
|
+
single_value_mode=False,
|
|
75
76
|
*args,
|
|
76
77
|
**kwargs,
|
|
77
78
|
):
|
|
@@ -86,6 +87,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
86
87
|
self.invert = invert
|
|
87
88
|
self.parent_list_widget = parent_list_widget
|
|
88
89
|
self.parent_le = parent_le
|
|
90
|
+
self.single_value_mode = single_value_mode
|
|
89
91
|
|
|
90
92
|
# SDF cache (stores label + dist_in + dist_out + voronoi)
|
|
91
93
|
self.sdf_cache = OrderedDict()
|
|
@@ -201,8 +203,18 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
201
203
|
|
|
202
204
|
def set_measurement_in_parent_list(self):
|
|
203
205
|
# Add the edge size to the parent QListWidget
|
|
204
|
-
|
|
205
|
-
self.
|
|
206
|
+
# edge_slider is a QLabeledRangeSlider returning (min, max) tuple
|
|
207
|
+
slider_val = self.edge_slider.value()
|
|
208
|
+
if isinstance(slider_val, tuple):
|
|
209
|
+
if self.single_value_mode:
|
|
210
|
+
# For neighborhood: just output the max absolute distance
|
|
211
|
+
edge_value = str(max(abs(slider_val[0]), abs(slider_val[1])))
|
|
212
|
+
else:
|
|
213
|
+
# For contour measurements: output as "(min,max)" tuple format
|
|
214
|
+
edge_value = f"({slider_val[0]},{slider_val[1]})"
|
|
215
|
+
else:
|
|
216
|
+
edge_value = str(abs(slider_val))
|
|
217
|
+
self.parent_list_widget.addItems([edge_value])
|
|
206
218
|
self.close()
|
|
207
219
|
|
|
208
220
|
def generate_label_imshow(self):
|
|
@@ -43,6 +43,9 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
43
43
|
parent_preprocessing_list=None,
|
|
44
44
|
cell_type="targets",
|
|
45
45
|
labels=None,
|
|
46
|
+
initial_diameter=None,
|
|
47
|
+
initial_threshold=None,
|
|
48
|
+
initial_preprocessing=None,
|
|
46
49
|
*args,
|
|
47
50
|
**kwargs,
|
|
48
51
|
):
|
|
@@ -113,6 +116,17 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
113
116
|
min(self.target_channel, self.n_channels - 1)
|
|
114
117
|
)
|
|
115
118
|
|
|
119
|
+
# Initialize from provided values (sync with settings panel)
|
|
120
|
+
if initial_diameter is not None:
|
|
121
|
+
self.spot_diam_le.setText(str(initial_diameter))
|
|
122
|
+
if initial_threshold is not None:
|
|
123
|
+
self.spot_thresh_le.setText(str(initial_threshold))
|
|
124
|
+
if initial_preprocessing is not None and len(initial_preprocessing) > 0:
|
|
125
|
+
items_for_list = [a[0] for a in initial_preprocessing]
|
|
126
|
+
for it in items_for_list:
|
|
127
|
+
self.preprocessing.list.addItemToList(it)
|
|
128
|
+
self.preprocessing.list.items = list(initial_preprocessing)
|
|
129
|
+
|
|
116
130
|
def closeEvent(self, event):
|
|
117
131
|
"""Clean up resources on close."""
|
|
118
132
|
# Clear large arrays
|
celldetective/measure.py
CHANGED
|
@@ -23,6 +23,7 @@ from celldetective.utils.maths import step_function
|
|
|
23
23
|
from celldetective.utils.image_cleaning import interpolate_nan
|
|
24
24
|
from celldetective.preprocessing import field_correction
|
|
25
25
|
from celldetective.log_manager import get_logger
|
|
26
|
+
import pandas as pd
|
|
26
27
|
|
|
27
28
|
logger = get_logger(__name__)
|
|
28
29
|
|
|
@@ -279,8 +280,6 @@ def measure(
|
|
|
279
280
|
|
|
280
281
|
timestep_dataframes.append(measurements_at_t)
|
|
281
282
|
|
|
282
|
-
import pandas as pd
|
|
283
|
-
|
|
284
283
|
measurements = pd.concat(timestep_dataframes)
|
|
285
284
|
if trajectories is not None:
|
|
286
285
|
measurements = measurements.sort_values(
|
|
@@ -435,8 +434,11 @@ def measure_features(
|
|
|
435
434
|
|
|
436
435
|
if spot_detection is not None:
|
|
437
436
|
detection_channel = spot_detection.get("channel")
|
|
438
|
-
|
|
439
|
-
|
|
437
|
+
channels_list = (
|
|
438
|
+
list(channels) if not isinstance(channels, list) else channels
|
|
439
|
+
)
|
|
440
|
+
if detection_channel in channels_list:
|
|
441
|
+
ind = channels_list.index(detection_channel)
|
|
440
442
|
if "image_preprocessing" not in spot_detection:
|
|
441
443
|
spot_detection.update({"image_preprocessing": None})
|
|
442
444
|
|
|
@@ -458,8 +460,11 @@ def measure_features(
|
|
|
458
460
|
if normalisation_list:
|
|
459
461
|
for norm in normalisation_list:
|
|
460
462
|
target = norm.get("target_channel")
|
|
461
|
-
|
|
462
|
-
|
|
463
|
+
channels_list = (
|
|
464
|
+
list(channels) if not isinstance(channels, list) else channels
|
|
465
|
+
)
|
|
466
|
+
if target in channels_list:
|
|
467
|
+
ind = channels_list.index(target)
|
|
463
468
|
|
|
464
469
|
if norm["correction_type"] == "local":
|
|
465
470
|
normalised_image = normalise_by_cell(
|
|
@@ -529,7 +534,6 @@ def measure_features(
|
|
|
529
534
|
extra_properties=extra_props_list,
|
|
530
535
|
channel_names=channels,
|
|
531
536
|
)
|
|
532
|
-
import pandas as pd
|
|
533
537
|
|
|
534
538
|
df_props = pd.DataFrame(props)
|
|
535
539
|
|
|
@@ -540,10 +544,19 @@ def measure_features(
|
|
|
540
544
|
df_props = df_props[[c for c in df_props.columns if not c.endswith("_delme")]]
|
|
541
545
|
|
|
542
546
|
if border_dist is not None:
|
|
547
|
+
# Get the names of extra properties that were actually requested by the user
|
|
548
|
+
# (these were moved from features to extra_props_list earlier)
|
|
549
|
+
requested_extra_names = []
|
|
550
|
+
if extra_props_list:
|
|
551
|
+
for prop_func in extra_props_list:
|
|
552
|
+
if hasattr(prop_func, "__name__"):
|
|
553
|
+
requested_extra_names.append(prop_func.__name__)
|
|
554
|
+
|
|
543
555
|
# Filter for features containing "intensity" but not "centroid" or "peripheral"
|
|
556
|
+
# Only use user-requested features, not all available extra properties
|
|
544
557
|
intensity_features = [
|
|
545
558
|
f
|
|
546
|
-
for f in (features +
|
|
559
|
+
for f in (features + requested_extra_names)
|
|
547
560
|
if "intensity" in f and "centroid" not in f and "peripheral" not in f
|
|
548
561
|
]
|
|
549
562
|
|
|
@@ -554,7 +567,7 @@ def measure_features(
|
|
|
554
567
|
|
|
555
568
|
clean_intensity_features = []
|
|
556
569
|
for s in intensity_features:
|
|
557
|
-
if s in
|
|
570
|
+
if s in requested_extra_names:
|
|
558
571
|
intensity_extra.append(getattr(extra_props, s))
|
|
559
572
|
else:
|
|
560
573
|
clean_intensity_features.append(s)
|
|
@@ -598,7 +611,6 @@ def measure_features(
|
|
|
598
611
|
extra_properties=intensity_extra,
|
|
599
612
|
channel_names=channels,
|
|
600
613
|
)
|
|
601
|
-
import pandas as pd
|
|
602
614
|
|
|
603
615
|
df_props_border_d = pd.DataFrame(props_border)
|
|
604
616
|
|
|
@@ -821,8 +833,6 @@ def compute_haralick_features(
|
|
|
821
833
|
len(np.unique(labels)) - 1
|
|
822
834
|
), "Some cells have not been measured..."
|
|
823
835
|
|
|
824
|
-
import pandas as pd
|
|
825
|
-
|
|
826
836
|
return pd.DataFrame(haralick_properties)
|
|
827
837
|
|
|
828
838
|
|
|
@@ -1070,7 +1080,6 @@ def measure_at_position(pos, mode, return_measurements=False, threads=1):
|
|
|
1070
1080
|
|
|
1071
1081
|
table = pos + os.sep.join(["output", "tables", f"trajectories_{mode}.csv"])
|
|
1072
1082
|
if return_measurements:
|
|
1073
|
-
import pandas as pd
|
|
1074
1083
|
|
|
1075
1084
|
df = pd.read_csv(table)
|
|
1076
1085
|
return df
|
|
@@ -249,9 +249,13 @@ def rename_intensity_column(df, channels):
|
|
|
249
249
|
if np.any(test_digit):
|
|
250
250
|
index = int(sections[np.where(test_digit)[0]][-1])
|
|
251
251
|
else:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
252
|
+
# Check if the column already contains a channel name (e.g., spot detection columns)
|
|
253
|
+
# If so, skip silently as no renaming is needed
|
|
254
|
+
already_named = any(ch in intensity_cols[k] for ch in channel_names)
|
|
255
|
+
if not already_named:
|
|
256
|
+
print(
|
|
257
|
+
f"No valid channel index found for {intensity_cols[k]}... Skipping the renaming for {intensity_cols[k]}..."
|
|
258
|
+
)
|
|
255
259
|
continue
|
|
256
260
|
|
|
257
261
|
channel_name = channel_names[np.where(channel_indices == index)[0]][0]
|
celldetective/utils/masks.py
CHANGED
|
@@ -49,8 +49,10 @@ def contour_of_instance_segmentation(label, distance, sdf=None, voronoi_map=None
|
|
|
49
49
|
"""
|
|
50
50
|
from scipy.ndimage import distance_transform_edt
|
|
51
51
|
|
|
52
|
-
# helper to parse string "rad1-rad2"
|
|
52
|
+
# helper to parse string "rad1-rad2" or "-12-13" or "-5--2"
|
|
53
53
|
if isinstance(distance, str):
|
|
54
|
+
import re
|
|
55
|
+
|
|
54
56
|
try:
|
|
55
57
|
# Check for stringified tuple "(a, b)"
|
|
56
58
|
distance = distance.strip()
|
|
@@ -74,13 +76,18 @@ def contour_of_instance_segmentation(label, distance, sdf=None, voronoi_map=None
|
|
|
74
76
|
min_r = val
|
|
75
77
|
max_r = 0
|
|
76
78
|
except ValueError:
|
|
77
|
-
# It's a range string "5-10"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# It's a range string like "5-10", "-12-13", or "-5--2"
|
|
80
|
+
# Use regex to parse range with potentially negative numbers
|
|
81
|
+
range_pattern = re.compile(r"^(-?\d+(?:\.\d+)?)-(-?\d+(?:\.\d+)?)$")
|
|
82
|
+
match = range_pattern.match(distance)
|
|
83
|
+
if match:
|
|
84
|
+
r1 = float(match.group(1))
|
|
85
|
+
r2 = float(match.group(2))
|
|
86
|
+
# Use the values directly as specified (min, max in SDF space)
|
|
87
|
+
min_r = min(r1, r2)
|
|
88
|
+
max_r = max(r1, r2)
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"Could not parse range string: {distance}")
|
|
84
91
|
|
|
85
92
|
except Exception:
|
|
86
93
|
logger.warning(
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
celldetective/__init__.py,sha256=LfnOyfUnYPGDc8xcsF_PfYEL7-CqAb7BMBPBIWGv84o,666
|
|
2
2
|
celldetective/__main__.py,sha256=Rzzu9ArxZSgfBVjV-lyn-3oanQB2MumQR6itK5ZaRpA,2597
|
|
3
|
-
celldetective/_version.py,sha256=
|
|
3
|
+
celldetective/_version.py,sha256=4JH3pE2Gi7kHI-6YVma-6xegJyqqu0l3uH4fqRqdsGA,25
|
|
4
4
|
celldetective/event_detection_models.py,sha256=A7ZJFhJwfdhzfxJ-YZIj6IoI9Gc1hyAodFkKt8kNxDk,93549
|
|
5
5
|
celldetective/events.py,sha256=n15R53c7QZ2wT8gjb0oeNikQbuRBrVVbyNsRCqXjzXA,8166
|
|
6
6
|
celldetective/exceptions.py,sha256=f3VmIYOthWTiqMEV5xQCox2rw5c5e7yog88h-CcV4oI,356
|
|
7
7
|
celldetective/extra_properties.py,sha256=s_2R4_El2p-gQNZ_EpgDxgrN3UnRitN7KDKHhyLuoHc,21681
|
|
8
8
|
celldetective/filters.py,sha256=QSRSpqvwUfa0YrU5EKoobJWCQFy07fFHwZ2bXxTL3hE,6933
|
|
9
9
|
celldetective/log_manager.py,sha256=Tv7_mVn0TVOyTs_2VnyEKl9_NMeDUtEkLC5njUq-r-Y,2968
|
|
10
|
-
celldetective/measure.py,sha256=
|
|
10
|
+
celldetective/measure.py,sha256=LTV4HvMzGUIRD1L6SsfktUWW5EIG2qk-I69rLot0fuA,72711
|
|
11
11
|
celldetective/neighborhood.py,sha256=Z5wu2nSMvabrQz91oy6DRf2o90LUY0RMXTEwgW7ORAg,74844
|
|
12
12
|
celldetective/preprocessing.py,sha256=tjAMLG4ZMfopMSFulGGjMQox7czEcReV2MzEkWlL2eQ,59948
|
|
13
13
|
celldetective/relative_measurements.py,sha256=j7xIj1FiY3kn5nA_wMqHV3wvjqikjnk99kZ7v9G6I5Q,42928
|
|
@@ -25,14 +25,14 @@ celldetective/gui/classifier_widget.py,sha256=uQ9KQmUFp6qWy0Aic-AKB9DkGNpOLxaERt
|
|
|
25
25
|
celldetective/gui/configure_new_exp.py,sha256=vgT6ozRIGDvT3R0qENlqvDn17BpKnwyJRhRhDS6ax8A,29954
|
|
26
26
|
celldetective/gui/control_panel.py,sha256=dMNzgivt6GdYROPlYpEY5TTNcANenm9ifUI3W3OcpOo,24337
|
|
27
27
|
celldetective/gui/dynamic_progress.py,sha256=wjTmDPy62qssY0zdteP3T9umIGGQk0UvebFIdn54CIc,16676
|
|
28
|
-
celldetective/gui/event_annotator.py,sha256=
|
|
28
|
+
celldetective/gui/event_annotator.py,sha256=JcX1K-TAMEdbgg-8ENK1xx_H2nw-vhNvxl1ImhqhMgM,42013
|
|
29
29
|
celldetective/gui/generic_signal_plot.py,sha256=47kXIuMcnQXjeNEdzM_G1WbW9TL5eMSjHC9XgWXMly4,49492
|
|
30
30
|
celldetective/gui/gui_utils.py,sha256=l7P6emKVEciCRdmnbfYvJAhl0MnbT3QkL2zpSPuHRoY,34120
|
|
31
|
-
celldetective/gui/interactions_block.py,sha256=
|
|
31
|
+
celldetective/gui/interactions_block.py,sha256=G_ejjMPFwX3Zy99iZwyYyINkpA7QBDMtyS08vzANl0U,28786
|
|
32
32
|
celldetective/gui/interactive_timeseries_viewer.py,sha256=u_amAhLdEHRpYSRwPDtVm5v-JZIz0ANTcG4YGksX1Vo,16079
|
|
33
33
|
celldetective/gui/json_readers.py,sha256=t5rhtIxACj0pdwLrnPs-QjyhQo3P25UGWGgOCIBhQxs,4572
|
|
34
34
|
celldetective/gui/measure_annotator.py,sha256=ljNbsKmFXQk0R9zEfBZ6XfBHzFmlL7Gt6QyPHyqh08g,38357
|
|
35
|
-
celldetective/gui/pair_event_annotator.py,sha256=
|
|
35
|
+
celldetective/gui/pair_event_annotator.py,sha256=dXGWrn64Iu9kjr2S3LjUzCHny2QOC3Q-qrOSqtvLvg0,128930
|
|
36
36
|
celldetective/gui/plot_measurements.py,sha256=a_Mks-5XUTn2QEYih0PlXGp2lX3C34zuhK9ozzE1guM,56593
|
|
37
37
|
celldetective/gui/plot_signals_ui.py,sha256=fxgkUZ_m7jFTXOwdzfJdl8wt3n8RECny3n4Tpk-GE6w,29016
|
|
38
38
|
celldetective/gui/preprocessing_block.py,sha256=cgUBv-eQBwfidK48-cTWJi0PS62wlXhsNLnsKhy6MQY,14967
|
|
@@ -47,7 +47,7 @@ celldetective/gui/base/channel_norm_generator.py,sha256=HJ57wBMdBIvca92V477T-aPu
|
|
|
47
47
|
celldetective/gui/base/components.py,sha256=jNUsCU_QE7QUFR0_xEvEPFEBYolMJt7YXGUKMjF7uOE,8155
|
|
48
48
|
celldetective/gui/base/feature_choice.py,sha256=n1T2fPoNLiTDS_6fa_GuGReDhBW11HdUrKy2RywotF8,2800
|
|
49
49
|
celldetective/gui/base/figure_canvas.py,sha256=mSiIYvEfz7MYMgdPDf6RKSMpKN8FkeZL7lugDNnDpnM,2245
|
|
50
|
-
celldetective/gui/base/list_widget.py,sha256=
|
|
50
|
+
celldetective/gui/base/list_widget.py,sha256=jHA21z6KsMigTxVJn4g7Vcs5ufR2vGSm-8GJYWflUXA,5125
|
|
51
51
|
celldetective/gui/base/styles.py,sha256=3Kz1eXw6OLr90wtErhK0KPJyJbyhAlqkniqm0JNGwFc,7407
|
|
52
52
|
celldetective/gui/base/utils.py,sha256=KojauRKGM9XKNhaWn211p6mJNZWIHLH75yeLpDd7pvA,1103
|
|
53
53
|
celldetective/gui/help/DL-segmentation-strategy.json,sha256=PZD9xXjrwbX3TiudHJPuvcyZD28o4k-fVgeTd7dBKzI,1583
|
|
@@ -74,8 +74,8 @@ celldetective/gui/settings/_event_detection_model_params.py,sha256=f3jkh6f3oE-_5
|
|
|
74
74
|
celldetective/gui/settings/_segmentation_model_params.py,sha256=YooEXRlkmOlHCyReiFynagrxBQn2y-dTB0FgowqZno0,6471
|
|
75
75
|
celldetective/gui/settings/_settings_base.py,sha256=_Yfq6vLkwm4FW5n0-SjVQjdhfL3hR5pUGBc0ttq_YXE,2576
|
|
76
76
|
celldetective/gui/settings/_settings_event_model_training.py,sha256=hpA5DTCPIMe1aVZDqO_6FudEP3a-IO6SVPfys-gdfXY,30346
|
|
77
|
-
celldetective/gui/settings/_settings_measurements.py,sha256=
|
|
78
|
-
celldetective/gui/settings/_settings_neighborhood.py,sha256=
|
|
77
|
+
celldetective/gui/settings/_settings_measurements.py,sha256=SghdNs1x-XdZysh1e9WtgrzVViNSWXe3jFgQ-ee6b5U,48345
|
|
78
|
+
celldetective/gui/settings/_settings_neighborhood.py,sha256=_gMoKbJOx-ssJEqf_BabI94byP6zWUg37ZM1P83tMTU,23807
|
|
79
79
|
celldetective/gui/settings/_settings_segmentation.py,sha256=6DihD1mk-dN4Sstdth1iJ-0HR34rTVlTHP-pXUh_rY0,1901
|
|
80
80
|
celldetective/gui/settings/_settings_segmentation_model_training.py,sha256=g-y5ZTZDwGaaevm6eI3XK_QU8PbZOY0jBdFyb1kAsqA,30440
|
|
81
81
|
celldetective/gui/settings/_settings_signal_annotator.py,sha256=9LRAgOn3qcbHctAXpTDlySGSCm6kMX2Qwqpeuwz384E,11601
|
|
@@ -90,9 +90,9 @@ celldetective/gui/table_ops/_rename_col.py,sha256=UAgDSpXJo_h4pLJpHaNc2w2VhbaW4D
|
|
|
90
90
|
celldetective/gui/viewers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
celldetective/gui/viewers/base_viewer.py,sha256=Wn4na79xRL1R6PSXIoE_UabxJNtNQ-Y5l9Q-HSb508c,32013
|
|
92
92
|
celldetective/gui/viewers/channel_offset_viewer.py,sha256=cywBkxyMPyKIuwZTGA03DBSS4a-H1SAohMJYOPISLEE,16289
|
|
93
|
-
celldetective/gui/viewers/contour_viewer.py,sha256=
|
|
93
|
+
celldetective/gui/viewers/contour_viewer.py,sha256=gRAVkwIrAQKN9hFbWNYOO8RDOw50l92owIvDM1q5Cno,15360
|
|
94
94
|
celldetective/gui/viewers/size_viewer.py,sha256=uXITjaxg5dhQ09Q6TNUxwLxi-ZglyGFcxEH1RtGIZWw,6020
|
|
95
|
-
celldetective/gui/viewers/spot_detection_viewer.py,sha256=
|
|
95
|
+
celldetective/gui/viewers/spot_detection_viewer.py,sha256=JSo6tpb7qBxGjUzUXQ-Zi4uts74eVZ2gxUdD67yhQ4A,17195
|
|
96
96
|
celldetective/gui/viewers/threshold_viewer.py,sha256=F-13JF2wFhyvvKfUvgRcSjWL3leAliOXy5yUergndnE,12000
|
|
97
97
|
celldetective/icons/logo-large.png,sha256=FXSwV3u6zEKcfpuSn4unnqB0oUnN9cHqQ9BCKWytrpg,36631
|
|
98
98
|
celldetective/icons/logo.png,sha256=wV2OS8_dU5Td5cgdPbCOU3JpMpTwNuYLnfVcnQX0tJA,2437
|
|
@@ -141,7 +141,7 @@ celldetective/scripts/train_segmentation_model.py,sha256=IKZGXf8i74xrc-wQ_oGgdE3
|
|
|
141
141
|
celldetective/scripts/train_signal_model.py,sha256=RFSnwdj3yBhqs-4wbF83ezsuZEIIZhdRFrMlUwtVHV0,3726
|
|
142
142
|
celldetective/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
143
|
celldetective/utils/color_mappings.py,sha256=yarqOTSrTsnOPPiPrrN_vLoPCbgWqF3wjqFwpbp-KsU,1007
|
|
144
|
-
celldetective/utils/data_cleaning.py,sha256=
|
|
144
|
+
celldetective/utils/data_cleaning.py,sha256=xsRRZhxzJI78K8IOB2tPxdKf0BuA8318lRChtjOerAE,22309
|
|
145
145
|
celldetective/utils/data_loaders.py,sha256=6Jg99U93qYjjs2xZErc2cz37tcH5e4vEqDH8PJgoEJs,17077
|
|
146
146
|
celldetective/utils/dataset_helpers.py,sha256=3ezpHO6nytw2Mx0D3maP_4535V2ohOTQn6Qpfk8gnms,6898
|
|
147
147
|
celldetective/utils/downloaders.py,sha256=o5sogEeYr-LR8mp1D7uNHH1aUJN3V82VVwYZvoSNTkQ,9506
|
|
@@ -153,7 +153,7 @@ celldetective/utils/image_transforms.py,sha256=cgaHuEUArWWHgxlBlcQLf--zQa-4VphPJ
|
|
|
153
153
|
celldetective/utils/io.py,sha256=WQH6B27iS722eVV8HHRaSvxMRZ217LoiEIPOqNGtqJk,1632
|
|
154
154
|
celldetective/utils/mask_cleaning.py,sha256=n1Q2RfyhX0W3AcLO0U6ucSyDGRCofj6bPLSO_xeVZPI,12545
|
|
155
155
|
celldetective/utils/mask_transforms.py,sha256=fX-ajhUhzZwOe7rAnMcQc6w4e2B8SZeRp9jrQLF6DFs,144
|
|
156
|
-
celldetective/utils/masks.py,sha256=
|
|
156
|
+
celldetective/utils/masks.py,sha256=ZynSkciNkiY2F9lxxW02ZwPS_54VnF9S1M0PNi7vT5M,7483
|
|
157
157
|
celldetective/utils/maths.py,sha256=pbbWWYNIHTnIBiaR2kJHPDaTPO0rpmQSPjHB7liUSG0,12465
|
|
158
158
|
celldetective/utils/model_getters.py,sha256=jVq9FhAF-xUmFOETWP6hByhoWgapmJGlNmSK11fQ69g,11370
|
|
159
159
|
celldetective/utils/model_loaders.py,sha256=CjScJBGtnenb8qRMZkEozdj1-ULYHvsDFS4AVgYbB5s,10576
|
|
@@ -167,13 +167,14 @@ celldetective/utils/event_detection/__init__.py,sha256=GvsdyQLMTXJj1S_FfRXjrpOxE
|
|
|
167
167
|
celldetective/utils/plots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
168
168
|
celldetective/utils/plots/regression.py,sha256=oUCn29-hp7PxMqC-R0yoL60KMw5ZWpZAIoCDh2ErlcY,1764
|
|
169
169
|
celldetective/utils/stardist_utils/__init__.py,sha256=SY2kxFNXSRjXN4ncs3heDdXT3UNk8M3dELJQySysAf4,4231
|
|
170
|
-
celldetective-1.5.
|
|
170
|
+
celldetective-1.5.0b11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
171
171
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
172
172
|
tests/test_cellpose_fallback.py,sha256=BJZTDFF8sFR1x7rDbvZQ2RQOB1OP6wuFBRfc8zbl5zw,3513
|
|
173
|
+
tests/test_contour_format.py,sha256=N11Rue_mxxwsZBhocRA621bpt4Cps-1DnPGzhKLSq9o,10873
|
|
173
174
|
tests/test_events.py,sha256=eLFwwEEJfQAdwhews3-fn1HSvzozcNNFN_Qn0gOvQkE,685
|
|
174
175
|
tests/test_filters.py,sha256=uj4NVyUnKXa18EpTSiWCetGKI1VFopDyNSJSUxX44wA,1689
|
|
175
176
|
tests/test_io.py,sha256=gk5FmoI7ANEczUtNXYRxc48KzkfYzemwS_eYaLq4_NI,2093
|
|
176
|
-
tests/test_measure.py,sha256=
|
|
177
|
+
tests/test_measure.py,sha256=xmp-fuTzOpquiqnk3CJC0vAMLZIkcCPstx3Ru8sOtzU,10963
|
|
177
178
|
tests/test_neighborhood.py,sha256=gk5FmoI7ANEczUtNXYRxc48KzkfYzemwS_eYaLq4_NI,2093
|
|
178
179
|
tests/test_notebooks.py,sha256=7HVmYiytsz0QIJ11iRkGGs4_hzNjofXAUs_OZou3Gm0,301
|
|
179
180
|
tests/test_partial_install.py,sha256=G69-GNcJ9YNgs6K2bVTEZO0Jpb14xMRQWTm8A6VuIco,2841
|
|
@@ -184,12 +185,13 @@ tests/test_tracking.py,sha256=_YLjwQ3EChQssoHDfEnUJ7fI1yC5KEcJsFnAVR64L70,18909
|
|
|
184
185
|
tests/test_utils.py,sha256=aSB_eMw9fzTsnxxdYoNmdQQRrXkWqBXB7Uv4TGU6kYE,4778
|
|
185
186
|
tests/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
186
187
|
tests/gui/test_enhancements.py,sha256=3x9au_rkQtMZ94DRj3OaEHKPr511RrWqBAUAcNQn1ys,13453
|
|
188
|
+
tests/gui/test_event_annotator_cleanup.py,sha256=nIFWqC3za06VF5n8EDnK-URbir2WiXzECBeNaVNS_FI,11226
|
|
187
189
|
tests/gui/test_measure_annotator_bugfix.py,sha256=tPfgWNKC0UkvrVssSrUcVDC1qgpzx6l2yCqvKtKYkM4,4544
|
|
188
190
|
tests/gui/test_new_project.py,sha256=wRjW2vEaZb0LWT-f8G8-Ptk8CW9z8-FDPLpV5uqj6ck,8778
|
|
189
191
|
tests/gui/test_project.py,sha256=KzAnodIc0Ovta0ARL5Kr5PkOR5euA6qczT_GhEZpyE4,4710
|
|
190
|
-
tests/gui/test_spot_detection_viewer.py,sha256=
|
|
191
|
-
celldetective-1.5.
|
|
192
|
-
celldetective-1.5.
|
|
193
|
-
celldetective-1.5.
|
|
194
|
-
celldetective-1.5.
|
|
195
|
-
celldetective-1.5.
|
|
192
|
+
tests/gui/test_spot_detection_viewer.py,sha256=mCEsfTAJb5W5IeLyQmaZXq9Sjr8ehCI552RkiCEQvLw,13355
|
|
193
|
+
celldetective-1.5.0b11.dist-info/METADATA,sha256=hdMBe_-bLh4h2hnn_mfZ-uA1ZUHY2ThZ31CzQJNqHVY,11524
|
|
194
|
+
celldetective-1.5.0b11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
195
|
+
celldetective-1.5.0b11.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
|
|
196
|
+
celldetective-1.5.0b11.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
|
|
197
|
+
celldetective-1.5.0b11.dist-info/RECORD,,
|