celldetective 1.5.0b1__py3-none-any.whl → 1.5.0b3__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/InitWindow.py +51 -12
- celldetective/gui/base/components.py +22 -1
- celldetective/gui/base_annotator.py +20 -9
- celldetective/gui/control_panel.py +21 -16
- celldetective/gui/event_annotator.py +51 -1060
- celldetective/gui/gui_utils.py +14 -5
- celldetective/gui/interactions_block.py +55 -25
- celldetective/gui/interactive_timeseries_viewer.py +11 -1
- celldetective/gui/measure_annotator.py +1064 -0
- celldetective/gui/plot_measurements.py +2 -4
- celldetective/gui/plot_signals_ui.py +3 -4
- celldetective/gui/process_block.py +298 -72
- celldetective/gui/viewers/base_viewer.py +134 -3
- celldetective/gui/viewers/contour_viewer.py +4 -4
- celldetective/gui/workers.py +25 -10
- celldetective/measure.py +3 -0
- celldetective/napari/utils.py +29 -19
- celldetective/processes/load_table.py +55 -0
- celldetective/processes/measure_cells.py +107 -81
- celldetective/processes/track_cells.py +39 -39
- celldetective/segmentation.py +1 -1
- celldetective/tracking.py +9 -0
- celldetective/utils/data_loaders.py +21 -1
- celldetective/utils/image_loaders.py +3 -0
- celldetective/utils/masks.py +1 -1
- celldetective/utils/maths.py +14 -1
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/METADATA +1 -1
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/RECORD +35 -32
- tests/gui/test_enhancements.py +351 -0
- tests/test_notebooks.py +2 -1
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/WHEEL +0 -0
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from PyQt5.QtCore import Qt
|
|
4
|
+
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QMutex, QWaitCondition
|
|
5
5
|
from PyQt5.QtWidgets import QHBoxLayout, QAction, QLabel, QComboBox
|
|
6
6
|
from fonticon_mdi6 import MDI6
|
|
7
7
|
from superqt import QLabeledDoubleRangeSlider, QLabeledSlider
|
|
@@ -9,11 +9,111 @@ from superqt.fonticon import icon
|
|
|
9
9
|
|
|
10
10
|
from celldetective.gui.base.components import CelldetectiveWidget
|
|
11
11
|
from celldetective.gui.base.utils import center_window
|
|
12
|
-
from celldetective.utils.image_loaders import
|
|
12
|
+
from celldetective.utils.image_loaders import (
|
|
13
|
+
auto_load_number_of_frames,
|
|
14
|
+
_get_img_num_per_channel,
|
|
15
|
+
load_frames,
|
|
16
|
+
)
|
|
13
17
|
from celldetective import get_logger
|
|
14
18
|
|
|
15
19
|
logger = get_logger(__name__)
|
|
16
20
|
|
|
21
|
+
|
|
22
|
+
class StackLoader(QThread):
|
|
23
|
+
frame_loaded = pyqtSignal(int, int, np.ndarray) # channel, frame_idx, image
|
|
24
|
+
|
|
25
|
+
def __init__(self, stack_path, img_num_per_channel, n_channels):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.stack_path = stack_path
|
|
28
|
+
self.img_num_per_channel = img_num_per_channel
|
|
29
|
+
self.n_channels = n_channels
|
|
30
|
+
self.target_channel = 0
|
|
31
|
+
self.priority_frame = 0
|
|
32
|
+
self.cache_keys = set()
|
|
33
|
+
self.running = True
|
|
34
|
+
self.mutex = QMutex()
|
|
35
|
+
self.condition = QWaitCondition()
|
|
36
|
+
|
|
37
|
+
def update_priority(self, channel, frame, current_cache_keys):
|
|
38
|
+
self.mutex.lock()
|
|
39
|
+
self.target_channel = channel
|
|
40
|
+
self.priority_frame = frame
|
|
41
|
+
self.cache_keys = set(current_cache_keys)
|
|
42
|
+
self.condition.wakeAll()
|
|
43
|
+
self.mutex.unlock()
|
|
44
|
+
|
|
45
|
+
def stop(self):
|
|
46
|
+
self.running = False
|
|
47
|
+
self.condition.wakeAll()
|
|
48
|
+
self.wait()
|
|
49
|
+
|
|
50
|
+
def run(self):
|
|
51
|
+
while self.running:
|
|
52
|
+
self.mutex.lock()
|
|
53
|
+
if not self.running:
|
|
54
|
+
self.mutex.unlock()
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
t_ch = self.target_channel
|
|
58
|
+
p_frame = self.priority_frame
|
|
59
|
+
keys_snapshot = list(self.cache_keys)
|
|
60
|
+
self.mutex.unlock()
|
|
61
|
+
|
|
62
|
+
# Determine next frame to load
|
|
63
|
+
# Strategy: look around priority frame
|
|
64
|
+
frame_to_load = -1
|
|
65
|
+
|
|
66
|
+
# Search radius
|
|
67
|
+
radius = 10
|
|
68
|
+
found = False
|
|
69
|
+
|
|
70
|
+
# Check immediate neighbors first
|
|
71
|
+
check_order = [p_frame]
|
|
72
|
+
for r in range(1, radius + 1):
|
|
73
|
+
check_order.append(p_frame + r)
|
|
74
|
+
check_order.append(p_frame - r)
|
|
75
|
+
|
|
76
|
+
# Determine max frames
|
|
77
|
+
max_frames = self.img_num_per_channel.shape[1]
|
|
78
|
+
|
|
79
|
+
for f in check_order:
|
|
80
|
+
if 0 <= f < max_frames:
|
|
81
|
+
if (t_ch, f) not in keys_snapshot:
|
|
82
|
+
frame_to_load = f
|
|
83
|
+
found = True
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
if found:
|
|
87
|
+
try:
|
|
88
|
+
# Load the frame
|
|
89
|
+
from celldetective.utils.image_loaders import load_frames
|
|
90
|
+
|
|
91
|
+
img = load_frames(
|
|
92
|
+
self.img_num_per_channel[t_ch, frame_to_load],
|
|
93
|
+
self.stack_path,
|
|
94
|
+
normalize_input=False,
|
|
95
|
+
)[:, :, 0]
|
|
96
|
+
|
|
97
|
+
self.frame_loaded.emit(t_ch, frame_to_load, img)
|
|
98
|
+
|
|
99
|
+
# Update snapshot locally to avoid reloading immediately in next loop
|
|
100
|
+
self.mutex.lock()
|
|
101
|
+
self.cache_keys.add((t_ch, frame_to_load))
|
|
102
|
+
self.mutex.unlock()
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
pass
|
|
106
|
+
# logger.error(f"Error loading frame {frame_to_load}: {e}")
|
|
107
|
+
# Prepare to wait to avoid spin loop on error
|
|
108
|
+
self.msleep(100)
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
# If nothing to load, wait
|
|
112
|
+
self.mutex.lock()
|
|
113
|
+
self.condition.wait(self.mutex, 500) # Wait 500ms or until new priority
|
|
114
|
+
self.mutex.unlock()
|
|
115
|
+
|
|
116
|
+
|
|
17
117
|
class StackVisualizer(CelldetectiveWidget):
|
|
18
118
|
"""
|
|
19
119
|
A widget for visualizing image stacks with interactive sliders and channel selection.
|
|
@@ -96,6 +196,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
96
196
|
self._min = 0
|
|
97
197
|
self._max = 0
|
|
98
198
|
|
|
199
|
+
self.loader_thread = None
|
|
99
200
|
self.load_stack()
|
|
100
201
|
self.generate_figure_canvas()
|
|
101
202
|
if self.create_channel_cb:
|
|
@@ -113,7 +214,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
113
214
|
self.is_drawing_line = False
|
|
114
215
|
self.generate_custom_tools()
|
|
115
216
|
|
|
116
|
-
self.canvas.layout.setContentsMargins(15, 15, 15,
|
|
217
|
+
self.canvas.layout.setContentsMargins(15, 15, 15, 15)
|
|
117
218
|
|
|
118
219
|
center_window(self)
|
|
119
220
|
|
|
@@ -464,6 +565,13 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
464
565
|
np.arange(self.n_channels), self.stack_length, self.n_channels
|
|
465
566
|
)
|
|
466
567
|
|
|
568
|
+
# Initialize background loader
|
|
569
|
+
self.loader_thread = StackLoader(
|
|
570
|
+
self.stack_path, self.img_num_per_channel, self.n_channels
|
|
571
|
+
)
|
|
572
|
+
self.loader_thread.frame_loaded.connect(self.on_frame_loaded)
|
|
573
|
+
self.loader_thread.start()
|
|
574
|
+
|
|
467
575
|
self.init_frame = load_frames(
|
|
468
576
|
self.img_num_per_channel[self.target_channel, self.mid_time],
|
|
469
577
|
self.stack_path,
|
|
@@ -647,6 +755,12 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
647
755
|
|
|
648
756
|
self.current_time_index = value
|
|
649
757
|
|
|
758
|
+
# Update loader priority
|
|
759
|
+
if self.mode == "virtual" and self.loader_thread:
|
|
760
|
+
self.loader_thread.update_priority(
|
|
761
|
+
self.target_channel, value, self.frame_cache.keys()
|
|
762
|
+
)
|
|
763
|
+
|
|
650
764
|
if self.mode == "direct":
|
|
651
765
|
self.init_frame = self.stack[value, :, :, self.target_channel]
|
|
652
766
|
|
|
@@ -693,8 +807,25 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
693
807
|
self.canvas.canvas.draw_idle()
|
|
694
808
|
self.update_profile()
|
|
695
809
|
|
|
810
|
+
def on_frame_loaded(self, channel, frame, image):
|
|
811
|
+
"""Callback from loader thread"""
|
|
812
|
+
# Store in cache
|
|
813
|
+
cache_key = (channel, frame)
|
|
814
|
+
if cache_key not in self.frame_cache:
|
|
815
|
+
self.frame_cache[cache_key] = image
|
|
816
|
+
if len(self.frame_cache) > self.max_cache_size:
|
|
817
|
+
self.frame_cache.popitem(last=False)
|
|
818
|
+
|
|
819
|
+
# If this is the current frame (user might have scrolled while loading), update display?
|
|
820
|
+
# Usually change_frame handles display. If we are waiting for this frame, we might want to refresh.
|
|
821
|
+
if channel == self.target_channel and frame == self.current_time_index:
|
|
822
|
+
# Refresh
|
|
823
|
+
self.change_frame(self.current_time_index)
|
|
824
|
+
|
|
696
825
|
def closeEvent(self, event):
|
|
697
826
|
# Event handler for closing the widget
|
|
827
|
+
if self.loader_thread:
|
|
828
|
+
self.loader_thread.stop()
|
|
698
829
|
if hasattr(self, "frame_cache") and isinstance(self.frame_cache, OrderedDict):
|
|
699
830
|
self.frame_cache.clear()
|
|
700
831
|
self.canvas.close()
|
|
@@ -118,10 +118,10 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
118
118
|
), "Wrong dimensions for the provided labels, expect TXY"
|
|
119
119
|
assert len(self.labels) == self.stack_length
|
|
120
120
|
|
|
121
|
-
self.
|
|
121
|
+
self.label_mode = "direct"
|
|
122
122
|
self.init_label = self.labels[self.mid_time, :, :]
|
|
123
123
|
else:
|
|
124
|
-
self.
|
|
124
|
+
self.label_mode = "virtual"
|
|
125
125
|
assert isinstance(self.stack_path, str)
|
|
126
126
|
assert self.stack_path.endswith(".tif")
|
|
127
127
|
self.locate_labels_virtual()
|
|
@@ -292,7 +292,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
292
292
|
self.sdf_cache.move_to_end(value)
|
|
293
293
|
else:
|
|
294
294
|
# Cache Miss: Load Label
|
|
295
|
-
if self.
|
|
295
|
+
if self.label_mode == "virtual":
|
|
296
296
|
if hasattr(self, "label_map") and value in self.label_map:
|
|
297
297
|
try:
|
|
298
298
|
self.init_label = imread(self.label_map[value])
|
|
@@ -300,7 +300,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
300
300
|
self.init_label = np.zeros_like(self.init_frame)
|
|
301
301
|
else:
|
|
302
302
|
self.init_label = np.zeros_like(self.init_frame)
|
|
303
|
-
elif self.
|
|
303
|
+
elif self.label_mode == "direct":
|
|
304
304
|
if value < len(self.labels):
|
|
305
305
|
self.init_label = self.labels[value, :, :]
|
|
306
306
|
else:
|
celldetective/gui/workers.py
CHANGED
|
@@ -21,6 +21,8 @@ class ProgressWindow(CelldetectiveDialog):
|
|
|
21
21
|
title="",
|
|
22
22
|
position_info=True,
|
|
23
23
|
process_args=None,
|
|
24
|
+
well_label="Well progress:",
|
|
25
|
+
pos_label="Position progress:",
|
|
24
26
|
):
|
|
25
27
|
|
|
26
28
|
super().__init__()
|
|
@@ -41,20 +43,26 @@ class ProgressWindow(CelldetectiveDialog):
|
|
|
41
43
|
self.__label = QLabel("Idle")
|
|
42
44
|
self.time_left_lbl = QLabel("")
|
|
43
45
|
|
|
44
|
-
self.well_time_lbl = QLabel(
|
|
46
|
+
self.well_time_lbl = QLabel(well_label)
|
|
45
47
|
self.well_progress_bar = QProgressBar()
|
|
46
48
|
self.well_progress_bar.setValue(0)
|
|
47
49
|
self.well_progress_bar.setFormat("Total (Wells): %p%")
|
|
48
50
|
|
|
49
|
-
self.pos_time_lbl = QLabel(
|
|
51
|
+
self.pos_time_lbl = QLabel(pos_label)
|
|
50
52
|
self.pos_progress_bar = QProgressBar()
|
|
51
53
|
self.pos_progress_bar.setValue(0)
|
|
52
54
|
self.pos_progress_bar.setFormat("Current Well (Positions): %p%")
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
if "show_frame_progress" in process_args:
|
|
57
|
+
self.show_frame_progress = process_args["show_frame_progress"]
|
|
58
|
+
else:
|
|
59
|
+
self.show_frame_progress = True
|
|
60
|
+
|
|
61
|
+
if self.show_frame_progress:
|
|
62
|
+
self.frame_time_lbl = QLabel("Frame progress:")
|
|
63
|
+
self.frame_progress_bar = QProgressBar()
|
|
64
|
+
self.frame_progress_bar.setValue(0)
|
|
65
|
+
self.frame_progress_bar.setFormat("Current Position (Frames): %p%")
|
|
58
66
|
|
|
59
67
|
self.__runner = Runner(
|
|
60
68
|
process=self.__process,
|
|
@@ -73,8 +81,10 @@ class ProgressWindow(CelldetectiveDialog):
|
|
|
73
81
|
self.__runner.signals.update_pos.connect(self.pos_progress_bar.setValue)
|
|
74
82
|
self.__runner.signals.update_pos_time.connect(self.pos_time_lbl.setText)
|
|
75
83
|
|
|
76
|
-
self.
|
|
77
|
-
|
|
84
|
+
if self.show_frame_progress:
|
|
85
|
+
self.__runner.signals.update_frame.connect(self.frame_progress_bar.setValue)
|
|
86
|
+
self.__runner.signals.update_frame_time.connect(self.frame_time_lbl.setText)
|
|
87
|
+
|
|
78
88
|
self.__runner.signals.update_status.connect(self.__label.setText)
|
|
79
89
|
self.__runner.signals.update_image.connect(self.update_image)
|
|
80
90
|
|
|
@@ -96,8 +106,9 @@ class ProgressWindow(CelldetectiveDialog):
|
|
|
96
106
|
self.progress_layout.addWidget(self.pos_time_lbl)
|
|
97
107
|
self.progress_layout.addWidget(self.pos_progress_bar)
|
|
98
108
|
|
|
99
|
-
self.
|
|
100
|
-
|
|
109
|
+
if self.show_frame_progress:
|
|
110
|
+
self.progress_layout.addWidget(self.frame_time_lbl)
|
|
111
|
+
self.progress_layout.addWidget(self.frame_progress_bar)
|
|
101
112
|
|
|
102
113
|
self.btn_layout = QHBoxLayout()
|
|
103
114
|
self.btn_layout.addWidget(self.__btn_stp)
|
|
@@ -266,6 +277,9 @@ class Runner(QRunnable):
|
|
|
266
277
|
if "training_result" in data:
|
|
267
278
|
self.signals.training_result.emit(data["training_result"])
|
|
268
279
|
|
|
280
|
+
if "result" in data:
|
|
281
|
+
self.signals.result.emit(data["result"])
|
|
282
|
+
|
|
269
283
|
if "status" in data: # Moved this block out of frame_time check
|
|
270
284
|
logger.info(
|
|
271
285
|
f"Runner received status: {data['status']}"
|
|
@@ -312,6 +326,7 @@ class RunnerSignal(QObject):
|
|
|
312
326
|
update_image = pyqtSignal(object)
|
|
313
327
|
update_plot = pyqtSignal(dict)
|
|
314
328
|
training_result = pyqtSignal(dict)
|
|
329
|
+
result = pyqtSignal(object)
|
|
315
330
|
update_status = pyqtSignal(str)
|
|
316
331
|
|
|
317
332
|
finished = pyqtSignal()
|
celldetective/measure.py
CHANGED
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
from math import ceil
|
|
6
6
|
from functools import reduce
|
|
7
7
|
from inspect import getmembers, isfunction
|
|
8
|
+
from celldetective.gui.base.utils import pretty_table
|
|
8
9
|
|
|
9
10
|
from celldetective.exceptions import EmptyQueryError, MissingColumnsError, QueryError
|
|
10
11
|
from celldetective.utils.masks import (
|
|
@@ -1894,6 +1895,8 @@ def measure_radial_distance_to_center(
|
|
|
1894
1895
|
):
|
|
1895
1896
|
|
|
1896
1897
|
try:
|
|
1898
|
+
df[column_labels["x"]] = df[column_labels["x"]].astype(float)
|
|
1899
|
+
df[column_labels["y"]] = df[column_labels["y"]].astype(float)
|
|
1897
1900
|
df["radial_distance"] = np.sqrt(
|
|
1898
1901
|
(df[column_labels["x"]] - volume[0] / 2) ** 2
|
|
1899
1902
|
+ (df[column_labels["y"]] - volume[1] / 2) ** 2
|
celldetective/napari/utils.py
CHANGED
|
@@ -27,6 +27,7 @@ from celldetective.utils.experiment import (
|
|
|
27
27
|
)
|
|
28
28
|
from celldetective.utils.parsing import config_section_to_dict
|
|
29
29
|
from celldetective import get_logger
|
|
30
|
+
from celldetective.gui.base.styles import Styles
|
|
30
31
|
|
|
31
32
|
logger = get_logger()
|
|
32
33
|
|
|
@@ -87,10 +88,16 @@ def control_tracks(
|
|
|
87
88
|
position += os.sep
|
|
88
89
|
|
|
89
90
|
position = position.replace("\\", "/")
|
|
91
|
+
if progress_callback:
|
|
92
|
+
progress_callback(0)
|
|
93
|
+
|
|
90
94
|
stack, labels = locate_stack_and_labels(
|
|
91
95
|
position, prefix=prefix, population=population
|
|
92
96
|
)
|
|
93
97
|
|
|
98
|
+
if progress_callback:
|
|
99
|
+
progress_callback(25)
|
|
100
|
+
|
|
94
101
|
return view_tracks_in_napari(
|
|
95
102
|
position,
|
|
96
103
|
population,
|
|
@@ -130,7 +137,13 @@ def view_tracks_in_napari(
|
|
|
130
137
|
Updated
|
|
131
138
|
"""
|
|
132
139
|
|
|
140
|
+
print(f"DEBUG: view_tracks_in_napari called with pos={position}, pop={population}")
|
|
133
141
|
df, df_path = get_position_table(position, population=population, return_path=True)
|
|
142
|
+
print(f"DEBUG: get_position_table returned df={df is not None}")
|
|
143
|
+
|
|
144
|
+
if progress_callback:
|
|
145
|
+
progress_callback(50)
|
|
146
|
+
|
|
134
147
|
if df is None:
|
|
135
148
|
print("Please compute trajectories first... Abort...")
|
|
136
149
|
return None
|
|
@@ -144,12 +157,18 @@ def view_tracks_in_napari(
|
|
|
144
157
|
|
|
145
158
|
if (labels is not None) * relabel:
|
|
146
159
|
print("Replacing the cell mask labels with the track ID...")
|
|
160
|
+
|
|
161
|
+
def wrapped_callback(p):
|
|
162
|
+
if progress_callback:
|
|
163
|
+
return progress_callback(50 + int(p * 0.5))
|
|
164
|
+
return True
|
|
165
|
+
|
|
147
166
|
labels = relabel_segmentation(
|
|
148
167
|
labels,
|
|
149
168
|
df,
|
|
150
169
|
exclude_nans=True,
|
|
151
170
|
threads=threads,
|
|
152
|
-
progress_callback=
|
|
171
|
+
progress_callback=wrapped_callback,
|
|
153
172
|
)
|
|
154
173
|
if labels is None:
|
|
155
174
|
return None
|
|
@@ -186,9 +205,12 @@ def launch_napari_viewer(
|
|
|
186
205
|
shared_data,
|
|
187
206
|
contrast_limits,
|
|
188
207
|
flush_memory=True,
|
|
208
|
+
block=True,
|
|
209
|
+
progress_callback=None,
|
|
189
210
|
):
|
|
190
211
|
|
|
191
212
|
viewer = napari.Viewer()
|
|
213
|
+
|
|
192
214
|
if stack is not None:
|
|
193
215
|
viewer.add_image(
|
|
194
216
|
stack,
|
|
@@ -196,6 +218,7 @@ def launch_napari_viewer(
|
|
|
196
218
|
colormap=["gray"] * stack.shape[-1],
|
|
197
219
|
contrast_limits=contrast_limits,
|
|
198
220
|
)
|
|
221
|
+
|
|
199
222
|
if labels is not None:
|
|
200
223
|
labels_layer = viewer.add_labels(
|
|
201
224
|
labels.astype(int), name="segmentation", opacity=0.4
|
|
@@ -274,8 +297,6 @@ def launch_napari_viewer(
|
|
|
274
297
|
def export_table_widget():
|
|
275
298
|
return export_modifications()
|
|
276
299
|
|
|
277
|
-
from celldetective.gui.base.styles import Styles
|
|
278
|
-
|
|
279
300
|
export_table_widget.native.setStyleSheet(Styles().button_style_sheet)
|
|
280
301
|
|
|
281
302
|
def label_changed(event):
|
|
@@ -426,9 +447,9 @@ def launch_napari_viewer(
|
|
|
426
447
|
|
|
427
448
|
shared_data["df"] = df
|
|
428
449
|
|
|
429
|
-
viewer.show(block=
|
|
450
|
+
viewer.show(block=block)
|
|
430
451
|
|
|
431
|
-
if flush_memory:
|
|
452
|
+
if flush_memory and block:
|
|
432
453
|
|
|
433
454
|
# temporary fix for slight napari memory leak
|
|
434
455
|
for i in range(10000):
|
|
@@ -781,8 +802,6 @@ def control_segmentation_napari(
|
|
|
781
802
|
def export_widget():
|
|
782
803
|
return export_annotation()
|
|
783
804
|
|
|
784
|
-
from celldetective.gui.base.styles import Styles
|
|
785
|
-
|
|
786
805
|
stack, labels = locate_stack_and_labels(
|
|
787
806
|
position, prefix=prefix, population=population
|
|
788
807
|
)
|
|
@@ -889,22 +908,13 @@ def correct_annotation(filename):
|
|
|
889
908
|
stack,
|
|
890
909
|
channel_axis=-1,
|
|
891
910
|
colormap=["gray"] * stack.shape[-1],
|
|
892
|
-
|
|
911
|
+
contrast_limits=contrast_limits,
|
|
893
912
|
)
|
|
894
913
|
viewer.add_labels(labels, name="segmentation", opacity=0.4)
|
|
895
914
|
viewer.window.add_dock_widget(save_widget, area="right")
|
|
896
|
-
|
|
915
|
+
save_widget.native.setStyleSheet(Styles().button_style_sheet)
|
|
897
916
|
|
|
898
|
-
|
|
899
|
-
for i in range(100):
|
|
900
|
-
try:
|
|
901
|
-
viewer.layers.pop()
|
|
902
|
-
except:
|
|
903
|
-
pass
|
|
904
|
-
del viewer
|
|
905
|
-
del stack
|
|
906
|
-
del labels
|
|
907
|
-
gc.collect()
|
|
917
|
+
viewer.show(block=False)
|
|
908
918
|
|
|
909
919
|
|
|
910
920
|
def _view_on_napari(tracks=None, stack=None, labels=None):
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from multiprocessing import Process
|
|
3
|
+
from celldetective.utils.data_loaders import load_experiment_tables
|
|
4
|
+
from celldetective import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TableLoaderProcess(Process):
|
|
10
|
+
|
|
11
|
+
def __init__(self, queue=None, process_args=None, *args, **kwargs):
|
|
12
|
+
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
if process_args is not None:
|
|
16
|
+
for key, value in process_args.items():
|
|
17
|
+
setattr(self, key, value)
|
|
18
|
+
|
|
19
|
+
self.queue = queue
|
|
20
|
+
|
|
21
|
+
def run(self):
|
|
22
|
+
|
|
23
|
+
def progress(well_progress, pos_progress):
|
|
24
|
+
# Check for cancellation if needed?
|
|
25
|
+
# The runner checks queue for instructions? No, runner closes queue.
|
|
26
|
+
# But here we can just push updates.
|
|
27
|
+
self.queue.put(
|
|
28
|
+
{
|
|
29
|
+
"well_progress": well_progress,
|
|
30
|
+
"pos_progress": pos_progress,
|
|
31
|
+
"status": f"Loading tables... Well {well_progress}%, Position {pos_progress}%",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
return True # continue
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
self.queue.put({"status": "Started loading..."})
|
|
38
|
+
|
|
39
|
+
df = load_experiment_tables(
|
|
40
|
+
experiment=self.experiment,
|
|
41
|
+
population=self.population,
|
|
42
|
+
well_option=self.well_option,
|
|
43
|
+
position_option=self.position_option,
|
|
44
|
+
return_pos_info=False,
|
|
45
|
+
progress_callback=progress,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
self.queue.put({"status": "finished", "result": df})
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Table loading failed: {e}")
|
|
52
|
+
self.queue.put({"status": "error", "message": str(e)})
|
|
53
|
+
|
|
54
|
+
def end_process(self):
|
|
55
|
+
self.terminate()
|