celldetective 1.3.0.post1__tar.gz → 1.3.2__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 (109) hide show
  1. {celldetective-1.3.0.post1 → celldetective-1.3.2}/PKG-INFO +3 -3
  2. {celldetective-1.3.0.post1 → celldetective-1.3.2}/README.md +2 -2
  3. celldetective-1.3.2/celldetective/_version.py +1 -0
  4. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/events.py +88 -11
  5. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/extra_properties.py +5 -1
  6. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/InitWindow.py +35 -9
  7. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/classifier_widget.py +99 -23
  8. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/control_panel.py +7 -1
  9. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/generic_signal_plot.py +161 -2
  10. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/gui_utils.py +90 -1
  11. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/layouts.py +128 -7
  12. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/measurement_options.py +3 -3
  13. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/plot_signals_ui.py +8 -3
  14. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/process_block.py +77 -32
  15. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/retrain_segmentation_model_options.py +24 -10
  16. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator.py +53 -26
  17. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator2.py +17 -30
  18. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/survival_ui.py +24 -3
  19. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/tableUI.py +300 -183
  20. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/viewers.py +263 -3
  21. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/io.py +56 -3
  22. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/links/zenodo.json +136 -123
  23. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/measure.py +3 -0
  24. celldetective-1.3.2/celldetective/models/tracking_configs/biased_motion.json +68 -0
  25. celldetective-1.3.2/celldetective/models/tracking_configs/no_z_motion.json +202 -0
  26. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/neighborhood.py +154 -69
  27. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/preprocessing.py +172 -3
  28. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/relative_measurements.py +128 -4
  29. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/measure_cells.py +3 -3
  30. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/signals.py +212 -215
  31. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/tracking.py +7 -3
  32. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/utils.py +22 -6
  33. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/PKG-INFO +3 -3
  34. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/SOURCES.txt +2 -0
  35. celldetective-1.3.0.post1/celldetective/_version.py +0 -1
  36. {celldetective-1.3.0.post1 → celldetective-1.3.2}/LICENSE +0 -0
  37. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/__init__.py +0 -0
  38. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/__main__.py +0 -0
  39. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/datasets/segmentation_annotations/blank +0 -0
  40. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/datasets/signal_annotations/blank +0 -0
  41. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/filters.py +0 -0
  42. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/__init__.py +0 -0
  43. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/about.py +0 -0
  44. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/analyze_block.py +0 -0
  45. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/btrack_options.py +0 -0
  46. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/configure_new_exp.py +0 -0
  47. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/DL-segmentation-strategy.json +0 -0
  48. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/Threshold-vs-DL.json +0 -0
  49. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/cell-populations.json +0 -0
  50. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/exp-structure.json +0 -0
  51. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/feature-btrack.json +0 -0
  52. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/neighborhood.json +0 -0
  53. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/prefilter-for-segmentation.json +0 -0
  54. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/preprocessing.json +0 -0
  55. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/propagate-classification.json +0 -0
  56. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/track-postprocessing.json +0 -0
  57. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/tracking.json +0 -0
  58. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/json_readers.py +0 -0
  59. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/neighborhood_options.py +0 -0
  60. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/plot_measurements.py +0 -0
  61. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/retrain_signal_model_options.py +0 -0
  62. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/seg_model_loader.py +0 -0
  63. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator_options.py +0 -0
  64. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/styles.py +0 -0
  65. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/thresholds_gui.py +0 -0
  66. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/logo-large.png +0 -0
  67. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/logo.png +0 -0
  68. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/signals_icon.png +0 -0
  69. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash-test.png +0 -0
  70. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash.png +0 -0
  71. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash0.png +0 -0
  72. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/survival2.png +0 -0
  73. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/vignette_signals2.png +0 -0
  74. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/vignette_signals2.svg +0 -0
  75. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/pair_signal_detection/blank +0 -0
  76. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_effectors/blank +0 -0
  77. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_generic/blank +0 -0
  78. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_targets/blank +0 -0
  79. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/signal_detection/blank +0 -0
  80. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/mcf7.json +0 -0
  81. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/ricm.json +0 -0
  82. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/ricm2.json +0 -0
  83. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/analyze_signals.py +0 -0
  84. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/measure_relative.py +0 -0
  85. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/segment_cells.py +0 -0
  86. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/segment_cells_thresholds.py +0 -0
  87. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/track_cells.py +0 -0
  88. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/train_segmentation_model.py +0 -0
  89. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/train_signal_model.py +0 -0
  90. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/segmentation.py +0 -0
  91. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/dependency_links.txt +0 -0
  92. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/entry_points.txt +0 -0
  93. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/not-zip-safe +0 -0
  94. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/requires.txt +0 -0
  95. {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/top_level.txt +0 -0
  96. {celldetective-1.3.0.post1 → celldetective-1.3.2}/setup.cfg +0 -0
  97. {celldetective-1.3.0.post1 → celldetective-1.3.2}/setup.py +0 -0
  98. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/__init__.py +0 -0
  99. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_events.py +0 -0
  100. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_filters.py +0 -0
  101. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_io.py +0 -0
  102. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_measure.py +0 -0
  103. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_neighborhood.py +0 -0
  104. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_preprocessing.py +0 -0
  105. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_qt.py +0 -0
  106. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_segmentation.py +0 -0
  107. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_signals.py +0 -0
  108. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_tracking.py +0 -0
  109. {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: celldetective
3
- Version: 1.3.0.post1
3
+ Version: 1.3.2
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -69,7 +69,7 @@ analysis on multimodal time lapse microscopy images.
69
69
 
70
70
  ## Overview
71
71
 
72
- ![Pipeline](https://github.com/remyeltorro/celldetective/raw/main/docs/source/_static/celldetective-blocks.png)
72
+ ![Pipeline](https://github.com/celldetective/celldetective/raw/main/docs/source/_static/celldetective-blocks.png)
73
73
 
74
74
 
75
75
  Celldetective was designed to analyze time-lapse microscopy images in difficult situations: mixed cell populations that are only separable through multimodal information. This software provides a toolkit for the analysis of cell population interactions.
@@ -88,7 +88,7 @@ Instead of reinventing the wheel and out of respect for the amazing work done by
88
88
 
89
89
  **Target Audience**: The software is targeted to scientists who are interested in quantifying dynamically (or not) cell populations from microscopy images. Experimental scientists who produce such images can also analyze their data, thanks to the graphical interface, that completely removes the need for coding, and the many helper functions that guide the user in the analysis steps. Finally, the modular structure of Celldetective welcomes users with a partial need.
90
90
 
91
- ![Signal analysis](https://github.com/remyeltorro/celldetective/raw/main/docs/source/_static/signal-annotator.gif)
91
+ ![Signal analysis](https://github.com/celldetective/celldetective/raw/main/docs/source/_static/signal-annotator.gif)
92
92
 
93
93
 
94
94
  # System requirements
@@ -25,7 +25,7 @@ analysis on multimodal time lapse microscopy images.
25
25
 
26
26
  ## Overview
27
27
 
28
- ![Pipeline](https://github.com/remyeltorro/celldetective/raw/main/docs/source/_static/celldetective-blocks.png)
28
+ ![Pipeline](https://github.com/celldetective/celldetective/raw/main/docs/source/_static/celldetective-blocks.png)
29
29
 
30
30
 
31
31
  Celldetective was designed to analyze time-lapse microscopy images in difficult situations: mixed cell populations that are only separable through multimodal information. This software provides a toolkit for the analysis of cell population interactions.
@@ -44,7 +44,7 @@ Instead of reinventing the wheel and out of respect for the amazing work done by
44
44
 
45
45
  **Target Audience**: The software is targeted to scientists who are interested in quantifying dynamically (or not) cell populations from microscopy images. Experimental scientists who produce such images can also analyze their data, thanks to the graphical interface, that completely removes the need for coding, and the many helper functions that guide the user in the analysis steps. Finally, the modular structure of Celldetective welcomes users with a partial need.
46
46
 
47
- ![Signal analysis](https://github.com/remyeltorro/celldetective/raw/main/docs/source/_static/signal-annotator.gif)
47
+ ![Signal analysis](https://github.com/celldetective/celldetective/raw/main/docs/source/_static/signal-annotator.gif)
48
48
 
49
49
 
50
50
  # System requirements
@@ -0,0 +1 @@
1
+ __version__ = "1.3.2"
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  from lifelines import KaplanMeierFitter
3
3
 
4
- def switch_to_events(classes, event_times, max_times, origin_times=None, left_censored=True, FrameToMin=None):
4
+ def switch_to_events(classes, event_times, max_times, origin_times=None, left_censored=True, FrameToMin=None, cut_observation_time=None):
5
5
 
6
6
 
7
7
  """
@@ -29,6 +29,8 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
29
29
  FrameToMin : float, optional
30
30
  A conversion factor to transform survival times from frames (or any other unit) to minutes. If None, no conversion
31
31
  is applied (default is None).
32
+ cut_observation_time : float or None, optional
33
+ A cutoff time to artificially reduce the observation window and exclude late events. If None, uses all available data (default is None).
32
34
 
33
35
  Returns
34
36
  -------
@@ -70,15 +72,40 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
70
72
  if ot>=0. and ot==ot:
71
73
  # origin time is larger than zero, no censorship
72
74
  if c==0 and t>0:
75
+
73
76
  delta_t = t - ot
74
- if delta_t>0:
75
- events.append(1)
76
- survival_times.append(delta_t)
77
- else:
78
- # negative delta t, invalid cell
79
- pass
77
+
78
+ # Special case: observation cut at arbitrary time
79
+ if cut_observation_time is not None:
80
+ if t>=cut_observation_time:
81
+ # event time larger than cut, becomes no event
82
+ delta_t = cut_observation_time - ot # new time
83
+ if delta_t > 0:
84
+ events.append(0)
85
+ survival_times.append(delta_t)
86
+ else:
87
+ # negative delta t, invalid cell
88
+ pass
89
+ else:
90
+ # still event
91
+ if delta_t > 0:
92
+ events.append(1)
93
+ survival_times.append(delta_t)
94
+ else:
95
+ # negative delta t, invalid cell
96
+ pass
97
+ else:
98
+ # standard mode
99
+ if delta_t>0:
100
+ events.append(1)
101
+ survival_times.append(delta_t)
102
+ else:
103
+ # negative delta t, invalid cell
104
+ pass
80
105
  elif c==1:
81
106
  delta_t = mt - ot
107
+ if cut_observation_time is not None:
108
+ delta_t = cut_observation_time - ot
82
109
  if delta_t>0:
83
110
  events.append(0)
84
111
  survival_times.append(delta_t)
@@ -93,10 +120,20 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
93
120
 
94
121
  else:
95
122
  if c==0 and t>0:
96
- events.append(1)
97
- survival_times.append(t - ot)
123
+ if cut_observation_time is not None:
124
+ if t>cut_observation_time:
125
+ events.append(0)
126
+ survival_times.append(cut_observation_time - ot)
127
+ else:
128
+ events.append(1)
129
+ survival_times.append(t - ot)
130
+ else:
131
+ events.append(1)
132
+ survival_times.append(t - ot)
98
133
  elif c==1:
99
134
  events.append(0)
135
+ if cut_observation_time is not None:
136
+ mt = cut_observation_time
100
137
  survival_times.append(mt - ot)
101
138
  else:
102
139
  pass
@@ -106,7 +143,47 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
106
143
  survival_times = [s*FrameToMin for s in survival_times]
107
144
  return events, survival_times
108
145
 
109
- def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMin=1):
146
+ def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMin=1, cut_observation_time=None):
147
+
148
+ """
149
+ Computes survival analysis for a specific class of interest within a dataset, returning a fitted Kaplan-Meier
150
+ survival curve based on event and reference times.
151
+
152
+ Parameters
153
+ ----------
154
+ df : pandas.DataFrame
155
+ The dataset containing tracking data, event times, and other relevant columns for survival analysis.
156
+ class_of_interest : str
157
+ The name of the column that specifies the class for which survival analysis is to be computed.
158
+ t_event : str
159
+ The column indicating the time of the event of interest (e.g., cell death or migration stop).
160
+ t_reference : str or None, optional
161
+ The reference column indicating the start or origin time for each track (e.g., detection time). If None,
162
+ events are not left-censored (default is None).
163
+ FrameToMin : float, optional
164
+ Conversion factor to scale the frame time to minutes (default is 1, assuming no scaling).
165
+ cut_observation_time : float or None, optional
166
+ A cutoff time to artificially reduce the observation window and exclude late events. If None, uses all available data (default is None).
167
+ Returns
168
+ -------
169
+ ks : lifelines.KaplanMeierFitter or None
170
+ A fitted Kaplan-Meier estimator object. If there are no events, returns None.
171
+
172
+ Notes
173
+ -----
174
+ - The function groups the data by 'position' and 'TRACK_ID', extracting the minimum `class_of_interest` and `t_event`
175
+ values for each track.
176
+ - If `t_reference` is provided, the analysis assumes left-censoring and will use `t_reference` as the origin time for
177
+ each track.
178
+ - The function calls `switch_to_events` to determine the event occurrences and their associated survival times.
179
+ - A Kaplan-Meier estimator (`KaplanMeierFitter`) is fitted to the data to compute the survival curve.
180
+
181
+ Example
182
+ -------
183
+ >>> ks = compute_survival(df, class_of_interest="class_custom", t_event="time_custom", t_reference="t_firstdetection")
184
+ >>> ks.plot_survival_function()
185
+
186
+ """
110
187
 
111
188
  cols = list(df.columns)
112
189
  assert class_of_interest in cols,"The requested class cannot be found in the dataframe..."
@@ -127,7 +204,7 @@ def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMi
127
204
  assert t_reference in cols,"The reference time cannot be found in the dataframe..."
128
205
  first_detections = df.groupby(['position','TRACK_ID'])[t_reference].max().values
129
206
 
130
- events, survival_times = switch_to_events(classes, event_times, max_times, origin_times=first_detections, left_censored=left_censored, FrameToMin=FrameToMin)
207
+ events, survival_times = switch_to_events(classes, event_times, max_times, origin_times=first_detections, left_censored=left_censored, FrameToMin=FrameToMin, cut_observation_time=cut_observation_time)
131
208
  ks = KaplanMeierFitter()
132
209
  if len(events)>0:
133
210
  ks.fit(survival_times, event_observed=events)
@@ -57,7 +57,11 @@ def intensity_median(regionmask, intensity_image):
57
57
  return np.nanmedian(intensity_image[regionmask])
58
58
 
59
59
  def intensity_nanmean(regionmask, intensity_image):
60
- return np.nanmean(intensity_image[regionmask])
60
+
61
+ if np.all(intensity_image==0):
62
+ return np.nan
63
+ else:
64
+ return np.nanmean(intensity_image[regionmask])
61
65
 
62
66
  def intensity_centre_of_mass_displacement(regionmask, intensity_image):
63
67
 
@@ -1,5 +1,5 @@
1
1
  from PyQt5.QtWidgets import QApplication, QMainWindow
2
- from celldetective.utils import get_software_location
2
+ from celldetective.utils import get_software_location, download_zenodo_file
3
3
  import os
4
4
  from PyQt5.QtWidgets import QFileDialog, QWidget, QVBoxLayout, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QMenu, QAction
5
5
  from PyQt5.QtCore import Qt, QUrl
@@ -108,6 +108,10 @@ class AppInitWindow(QMainWindow):
108
108
  for i in range(len(self.recentFileActs)):
109
109
  self.OpenRecentAction.addAction(self.recentFileActs[i])
110
110
 
111
+ fileMenu.addMenu(self.openDemo)
112
+ self.openDemo.addAction(self.openSpreadingAssayDemo)
113
+ self.openDemo.addAction(self.openCytotoxicityAssayDemo)
114
+
111
115
  fileMenu.addAction(self.openModels)
112
116
  fileMenu.addSeparator()
113
117
  fileMenu.addAction(self.exitAction)
@@ -124,7 +128,7 @@ class AppInitWindow(QMainWindow):
124
128
  helpMenu = QMenu("Help", self)
125
129
  helpMenu.clear()
126
130
  helpMenu.addAction(self.DocumentationAction)
127
- helpMenu.addAction(self.SoftwareAction)
131
+ #helpMenu.addAction(self.SoftwareAction)
128
132
  helpMenu.addSeparator()
129
133
  helpMenu.addAction(self.AboutAction)
130
134
  menuBar.addMenu(helpMenu)
@@ -137,13 +141,17 @@ class AppInitWindow(QMainWindow):
137
141
  #self.newAction = QAction(self)
138
142
  #self.newAction.setText("&New")
139
143
  # Creating actions using the second constructor
140
- self.openAction = QAction('Open...', self)
144
+ self.openAction = QAction('Open Project', self)
141
145
  self.openAction.setShortcut("Ctrl+O")
142
146
  self.openAction.setShortcutVisibleInContextMenu(True)
143
147
 
144
- self.MemoryAndThreadsAction = QAction('Memory & Threads...')
148
+ self.openDemo = QMenu('Open Demo')
149
+ self.openSpreadingAssayDemo = QAction('Spreading Assay Demo', self)
150
+ self.openCytotoxicityAssayDemo = QAction('Cytotoxicity Assay Demo', self)
151
+
152
+ self.MemoryAndThreadsAction = QAction('Threads')
145
153
 
146
- self.CorrectAnnotationAction = QAction('Correct a segmentation annotation...')
154
+ self.CorrectAnnotationAction = QAction('Correct a segmentation annotation')
147
155
 
148
156
  self.newExpAction = QAction('New', self)
149
157
  self.newExpAction.setShortcut("Ctrl+N")
@@ -154,14 +162,14 @@ class AppInitWindow(QMainWindow):
154
162
  self.openModels.setShortcut("Ctrl+L")
155
163
  self.openModels.setShortcutVisibleInContextMenu(True)
156
164
 
157
- self.OpenRecentAction = QMenu('Open Recent')
165
+ self.OpenRecentAction = QMenu('Open Recent Project')
158
166
  self.reload_previous_experiments()
159
167
 
160
168
  self.DocumentationAction = QAction("Documentation", self)
161
169
  self.DocumentationAction.setShortcut("Ctrl+D")
162
170
  self.DocumentationAction.setShortcutVisibleInContextMenu(True)
163
171
 
164
- self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
172
+ #self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
165
173
  self.AboutAction = QAction("About celldetective", self)
166
174
 
167
175
  #self.DocumentationAction.triggered.connect(self.load_previous_config)
@@ -172,9 +180,27 @@ class AppInitWindow(QMainWindow):
172
180
  self.AboutAction.triggered.connect(self.open_about_window)
173
181
  self.MemoryAndThreadsAction.triggered.connect(self.set_memory_and_threads)
174
182
  self.CorrectAnnotationAction.triggered.connect(self.correct_seg_annotation)
175
-
176
183
  self.DocumentationAction.triggered.connect(self.open_documentation)
177
184
 
185
+ self.openSpreadingAssayDemo.triggered.connect(self.download_spreading_assay_demo)
186
+ self.openCytotoxicityAssayDemo.triggered.connect(self.download_cytotoxicity_assay_demo)
187
+
188
+ def download_spreading_assay_demo(self):
189
+
190
+ self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
191
+ if not os.path.exists(os.sep.join([self.target_dir,'demo_ricm'])):
192
+ download_zenodo_file('demo_ricm', self.target_dir)
193
+ self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_ricm']))
194
+ self.validate_button.click()
195
+
196
+ def download_cytotoxicity_assay_demo(self):
197
+
198
+ self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
199
+ if not os.path.exists(os.sep.join([self.target_dir,'demo_adcc'])):
200
+ download_zenodo_file('demo_adcc', self.target_dir)
201
+ self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_adcc']))
202
+ self.validate_button.click()
203
+
178
204
  def reload_previous_gpu_threads(self):
179
205
 
180
206
  self.recentFileActs = []
@@ -256,7 +282,7 @@ class AppInitWindow(QMainWindow):
256
282
 
257
283
 
258
284
  def open_experiment(self):
259
- print('ok')
285
+
260
286
  self.browse_experiment_folder()
261
287
  if self.experiment_path_selection.text()!='':
262
288
  self.open_directory()
@@ -120,9 +120,11 @@ class ClassifierWidget(QWidget, Styles):
120
120
  self.property_query_le = QLineEdit()
121
121
  self.property_query_le.setPlaceholderText('classify points using a query such as: area > 100 or eccentricity > 0.95')
122
122
  self.property_query_le.setToolTip('Classify points using a query on measurements.\nYou can use "and" and "or" conditions to combine\nmeasurements (e.g. "area > 100 or eccentricity > 0.95").')
123
+ self.property_query_le.textChanged.connect(self.activate_submit_btn)
123
124
  hbox_classify.addWidget(self.property_query_le, 70)
124
125
  self.submit_query_btn = QPushButton('Submit...')
125
126
  self.submit_query_btn.clicked.connect(self.apply_property_query)
127
+ self.submit_query_btn.setEnabled(False)
126
128
  hbox_classify.addWidget(self.submit_query_btn, 20)
127
129
  layout.addLayout(hbox_classify)
128
130
 
@@ -182,15 +184,24 @@ class ClassifierWidget(QWidget, Styles):
182
184
 
183
185
  layout.addWidget(QLabel())
184
186
 
185
-
186
187
  self.submit_btn = QPushButton('apply')
187
188
  self.submit_btn.setStyleSheet(self.button_style_sheet)
188
189
  self.submit_btn.clicked.connect(self.submit_classification)
190
+ self.submit_btn.setEnabled(False)
189
191
  layout.addWidget(self.submit_btn, 30)
190
192
 
191
193
  self.frame_slider.valueChanged.connect(self.set_frame)
192
194
  self.alpha_slider.valueChanged.connect(self.set_transparency)
193
195
 
196
+ def activate_submit_btn(self):
197
+
198
+ if self.property_query_le.text()=='':
199
+ self.submit_query_btn.setEnabled(False)
200
+ self.submit_btn.setEnabled(False)
201
+ else:
202
+ self.submit_query_btn.setEnabled(True)
203
+ self.submit_btn.setEnabled(True)
204
+
194
205
  def activate_r2(self):
195
206
  if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
196
207
  for wg in [self.r2_slider, self.r2_label]:
@@ -242,6 +253,24 @@ class ClassifierWidget(QWidget, Styles):
242
253
 
243
254
  def update_props_scatter(self, feature_changed=True):
244
255
 
256
+ try:
257
+ if np.any(self.df[self.features_cb[0].currentText()].to_numpy() <= 0.):
258
+ if self.ax_props.get_yscale()=='log':
259
+ self.log_btns[0].click()
260
+ self.log_btns[0].setEnabled(False)
261
+ else:
262
+ self.log_btns[0].setEnabled(True)
263
+
264
+ if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
265
+ if self.ax_props.get_xscale()=='log':
266
+ self.log_btns[1].click()
267
+ self.log_btns[1].setEnabled(False)
268
+ else:
269
+ self.log_btns[1].setEnabled(True)
270
+ except Exception as e:
271
+ #print(e)
272
+ pass
273
+
245
274
  class_name = self.class_name
246
275
 
247
276
  try:
@@ -250,32 +279,45 @@ class ClassifierWidget(QWidget, Styles):
250
279
  self.scat_props.set_offsets(self.df.loc[self.df['FRAME']==self.currentFrame,[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
251
280
  colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME']==self.currentFrame,class_name].to_numpy()]
252
281
  self.scat_props.set_facecolor(colors)
253
- self.scat_props.set_alpha(self.currentAlpha)
254
- self.ax_props.set_xlabel(self.features_cb[1].currentText())
255
- self.ax_props.set_ylabel(self.features_cb[0].currentText())
256
282
  else:
257
283
  self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
258
284
  colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
259
285
  self.scat_props.set_facecolor(colors)
260
- self.scat_props.set_alpha(self.currentAlpha)
286
+
287
+ self.scat_props.set_alpha(self.currentAlpha)
288
+
289
+ if feature_changed:
290
+
261
291
  self.ax_props.set_xlabel(self.features_cb[1].currentText())
262
292
  self.ax_props.set_ylabel(self.features_cb[0].currentText())
263
293
 
264
-
265
- feat_x = self.features_cb[1].currentText()
266
- feat_y = self.features_cb[0].currentText()
267
- min_x = self.df.dropna(subset=feat_x)[feat_x].min()
268
- max_x = self.df.dropna(subset=feat_x)[feat_x].max()
269
- min_y = self.df.dropna(subset=feat_y)[feat_y].min()
270
- max_y = self.df.dropna(subset=feat_y)[feat_y].max()
271
-
272
- if min_x==min_x and max_x==max_x:
273
- self.ax_props.set_xlim(min_x, max_x)
274
- if min_y==min_y and max_y==max_y:
275
- self.ax_props.set_ylim(min_y, max_y)
294
+ feat_x = self.features_cb[1].currentText()
295
+ feat_y = self.features_cb[0].currentText()
296
+ min_x = self.df.dropna(subset=feat_x)[feat_x].min()
297
+ max_x = self.df.dropna(subset=feat_x)[feat_x].max()
298
+ min_y = self.df.dropna(subset=feat_y)[feat_y].min()
299
+ max_y = self.df.dropna(subset=feat_y)[feat_y].max()
300
+
301
+ x_padding = (max_x - min_x) * 0.05
302
+ y_padding = (max_y - min_y) * 0.05
303
+ if x_padding==0:
304
+ x_padding = 0.05
305
+ if y_padding==0:
306
+ y_padding = 0.05
307
+
308
+ if min_x==min_x and max_x==max_x:
309
+ if self.ax_props.get_xscale()=='linear':
310
+ self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
311
+ else:
312
+ self.ax_props.set_xlim(min_x, max_x)
313
+ if min_y==min_y and max_y==max_y:
314
+ if self.ax_props.get_yscale()=='linear':
315
+ self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
316
+ else:
317
+ self.ax_props.set_ylim(min_y, max_y)
276
318
 
277
- if feature_changed:
278
319
  self.propscanvas.canvas.toolbar.update()
320
+
279
321
  self.propscanvas.canvas.draw_idle()
280
322
 
281
323
  except Exception as e:
@@ -284,7 +326,20 @@ class ClassifierWidget(QWidget, Styles):
284
326
  def apply_property_query(self):
285
327
 
286
328
  query = self.property_query_le.text()
287
- self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
329
+
330
+ try:
331
+ self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
332
+ except Exception as e:
333
+ msgBox = QMessageBox()
334
+ msgBox.setIcon(QMessageBox.Warning)
335
+ msgBox.setText(f"The query could not be understood. No filtering was applied. {e}")
336
+ msgBox.setWindowTitle("Warning")
337
+ msgBox.setStandardButtons(QMessageBox.Ok)
338
+ returnValue = msgBox.exec()
339
+ if returnValue == QMessageBox.Ok:
340
+ self.auto_close = False
341
+ return None
342
+
288
343
  self.class_name = "status_"+self.name_le.text()
289
344
  if self.df is None:
290
345
  msgBox = QMessageBox()
@@ -294,8 +349,10 @@ class ClassifierWidget(QWidget, Styles):
294
349
  msgBox.setStandardButtons(QMessageBox.Ok)
295
350
  returnValue = msgBox.exec()
296
351
  if returnValue == QMessageBox.Ok:
352
+ self.auto_close = False
297
353
  return None
298
- self.update_props_scatter()
354
+
355
+ self.update_props_scatter(feature_changed=False)
299
356
 
300
357
  def set_frame(self, value):
301
358
  xlim=self.ax_props.get_xlim()
@@ -329,12 +386,14 @@ class ClassifierWidget(QWidget, Styles):
329
386
  self.project_times_btn.setIcon(icon(MDI6.math_integral_box,color="black"))
330
387
  self.project_times_btn.setIconSize(QSize(20, 20))
331
388
  self.frame_slider.setEnabled(False)
332
- self.update_props_scatter()
389
+ self.update_props_scatter(feature_changed=False)
333
390
 
334
391
  def submit_classification(self):
335
392
 
336
- print('submit')
393
+ self.auto_close = True
337
394
  self.apply_property_query()
395
+ if not self.auto_close:
396
+ return None
338
397
 
339
398
  if self.time_corr.isChecked():
340
399
  self.class_name_user = 'class_'+self.name_le.text()
@@ -424,22 +483,39 @@ class ClassifierWidget(QWidget, Styles):
424
483
 
425
484
  if i==1:
426
485
  try:
486
+ feat_x = self.features_cb[1].currentText()
487
+ min_x = self.df.dropna(subset=feat_x)[feat_x].min()
488
+ max_x = self.df.dropna(subset=feat_x)[feat_x].max()
489
+ x_padding = (max_x - min_x) * 0.05
490
+ if x_padding==0:
491
+ x_padding = 0.05
492
+
427
493
  if self.ax_props.get_xscale()=='linear':
494
+ self.ax_props.set_xlim(min_x, max_x)
428
495
  self.ax_props.set_xscale('log')
429
496
  self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
430
497
  else:
431
498
  self.ax_props.set_xscale('linear')
499
+ self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
432
500
  self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
433
501
  except Exception as e:
434
502
  print(e)
435
503
  elif i==0:
436
504
  try:
505
+ feat_y = self.features_cb[0].currentText()
506
+ min_y = self.df.dropna(subset=feat_y)[feat_y].min()
507
+ max_y = self.df.dropna(subset=feat_y)[feat_y].max()
508
+ y_padding = (max_y - min_y) * 0.05
509
+ if y_padding==0:
510
+ y_padding = 0.05
511
+
437
512
  if self.ax_props.get_yscale()=='linear':
438
- ymin,ymax = self.ax_props.get_ylim()
513
+ self.ax_props.set_ylim(min_y, max_y)
439
514
  self.ax_props.set_yscale('log')
440
515
  self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
441
516
  else:
442
517
  self.ax_props.set_yscale('linear')
518
+ self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
443
519
  self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
444
520
  except Exception as e:
445
521
  print(e)
@@ -181,7 +181,13 @@ class ControlPanel(QMainWindow, Styles):
181
181
  exp_hbox = QHBoxLayout()
182
182
  exp_hbox.addWidget(experiment_label, 25, alignment=Qt.AlignRight)
183
183
  exp_subhbox = QHBoxLayout()
184
- exp_subhbox.addWidget(QLabel(name), 90, alignment=Qt.AlignLeft)
184
+ if len(name)>thresh:
185
+ name_cut = name[:thresh - 3]+'...'
186
+ else:
187
+ name_cut = name
188
+ exp_name_lbl = QLabel(name_cut)
189
+ exp_name_lbl.setToolTip(name)
190
+ exp_subhbox.addWidget(exp_name_lbl, 90, alignment=Qt.AlignLeft)
185
191
  exp_subhbox.addWidget(self.folder_exp_btn, 5, alignment=Qt.AlignRight)
186
192
  exp_subhbox.addWidget(self.edit_config_button, 5, alignment=Qt.AlignRight)
187
193
  exp_hbox.addLayout(exp_subhbox, 75)