celldetective 1.5.0b0__py3-none-any.whl → 1.5.0b2__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 +26 -4
- celldetective/gui/base/components.py +20 -1
- celldetective/gui/base_annotator.py +11 -7
- celldetective/gui/event_annotator.py +51 -1060
- celldetective/gui/interactions_block.py +55 -25
- celldetective/gui/interactive_timeseries_viewer.py +11 -1
- celldetective/gui/measure_annotator.py +968 -0
- celldetective/gui/process_block.py +88 -34
- celldetective/gui/viewers/base_viewer.py +134 -3
- celldetective/gui/viewers/contour_viewer.py +4 -4
- celldetective/gui/workers.py +124 -10
- celldetective/measure.py +3 -0
- celldetective/napari/utils.py +29 -19
- celldetective/processes/downloader.py +122 -96
- 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/downloaders.py +38 -0
- celldetective/utils/maths.py +14 -1
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/METADATA +1 -1
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/RECORD +29 -27
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/WHEEL +0 -0
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.5.0b0.dist-info → celldetective-1.5.0b2.dist-info}/top_level.txt +0 -0
|
@@ -9,6 +9,7 @@ from PyQt5.QtWidgets import (
|
|
|
9
9
|
QHBoxLayout,
|
|
10
10
|
QCheckBox,
|
|
11
11
|
QMessageBox,
|
|
12
|
+
QApplication,
|
|
12
13
|
)
|
|
13
14
|
from PyQt5.QtCore import Qt, QSize, QTimer, QThread, pyqtSignal
|
|
14
15
|
from superqt.fonticon import icon
|
|
@@ -30,13 +31,14 @@ from celldetective.gui.base.components import (
|
|
|
30
31
|
CelldetectiveWidget,
|
|
31
32
|
CelldetectiveProgressDialog,
|
|
32
33
|
QHSeperationLine,
|
|
34
|
+
HoverButton,
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
import numpy as np
|
|
36
38
|
from glob import glob
|
|
37
39
|
from celldetective import get_logger
|
|
38
40
|
|
|
39
|
-
logger = get_logger()
|
|
41
|
+
logger = get_logger("celldetective")
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class NapariLoaderThread(QThread):
|
|
@@ -345,11 +347,12 @@ class ProcessPanel(QFrame, Styles):
|
|
|
345
347
|
self.refresh_signal_models()
|
|
346
348
|
# self.to_disable.append(self.cell_models_list)
|
|
347
349
|
|
|
348
|
-
self.train_signal_model_btn =
|
|
350
|
+
self.train_signal_model_btn = HoverButton(
|
|
351
|
+
"TRAIN", MDI6.redo_variant, "black", "white"
|
|
352
|
+
)
|
|
349
353
|
self.train_signal_model_btn.setToolTip(
|
|
350
354
|
"Train or retrain an event detection model\non newly annotated data."
|
|
351
355
|
)
|
|
352
|
-
self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
353
356
|
self.train_signal_model_btn.setIconSize(QSize(20, 20))
|
|
354
357
|
self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
355
358
|
model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
|
|
@@ -557,8 +560,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
557
560
|
self.seg_model_list.setGeometry(50, 50, 200, 30)
|
|
558
561
|
self.init_seg_model_list()
|
|
559
562
|
|
|
560
|
-
self.upload_model_btn =
|
|
561
|
-
self.upload_model_btn.setIcon(icon(MDI6.upload, color="black"))
|
|
563
|
+
self.upload_model_btn = HoverButton("UPLOAD", MDI6.upload, "black", "white")
|
|
562
564
|
self.upload_model_btn.setIconSize(QSize(20, 20))
|
|
563
565
|
self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
564
566
|
self.upload_model_btn.setToolTip(
|
|
@@ -568,11 +570,10 @@ class ProcessPanel(QFrame, Styles):
|
|
|
568
570
|
self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
|
|
569
571
|
# self.to_disable.append(self.upload_tc_model)
|
|
570
572
|
|
|
571
|
-
self.train_btn =
|
|
573
|
+
self.train_btn = HoverButton("TRAIN", MDI6.redo_variant, "black", "white")
|
|
572
574
|
self.train_btn.setToolTip(
|
|
573
575
|
"Train or retrain a segmentation model\non newly annotated data."
|
|
574
576
|
)
|
|
575
|
-
self.train_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
576
577
|
self.train_btn.setIconSize(QSize(20, 20))
|
|
577
578
|
self.train_btn.setStyleSheet(self.button_style_sheet_3)
|
|
578
579
|
self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
|
|
@@ -865,7 +866,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
865
866
|
self.signal_loader.start()
|
|
866
867
|
|
|
867
868
|
def check_measurements(self):
|
|
868
|
-
from celldetective.gui.
|
|
869
|
+
from celldetective.gui.measure_annotator import MeasureAnnotator
|
|
869
870
|
|
|
870
871
|
test = self.parent_window.locate_selected_position()
|
|
871
872
|
if test:
|
|
@@ -1493,6 +1494,9 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1493
1494
|
window_title="Preparing the napari viewer...",
|
|
1494
1495
|
)
|
|
1495
1496
|
|
|
1497
|
+
self.napari_progress.setAutoClose(False)
|
|
1498
|
+
self.napari_progress.setAutoReset(False)
|
|
1499
|
+
|
|
1496
1500
|
self.napari_progress.setValue(0)
|
|
1497
1501
|
self.napari_loader.progress.connect(self.napari_progress.setValue)
|
|
1498
1502
|
self.napari_loader.status.connect(self.napari_progress.setLabelText)
|
|
@@ -1502,13 +1506,15 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1502
1506
|
from celldetective.napari.utils import launch_napari_viewer
|
|
1503
1507
|
|
|
1504
1508
|
self.napari_progress.blockSignals(True)
|
|
1505
|
-
self.napari_progress.close()
|
|
1509
|
+
# self.napari_progress.close()
|
|
1506
1510
|
if self.napari_loader._is_cancelled:
|
|
1507
1511
|
logger.info("Task was cancelled...")
|
|
1512
|
+
self.napari_progress.close()
|
|
1508
1513
|
return
|
|
1509
1514
|
|
|
1510
1515
|
if isinstance(result, Exception):
|
|
1511
1516
|
logger.error(f"napari loading error: {result}")
|
|
1517
|
+
self.napari_progress.close()
|
|
1512
1518
|
msgBox = QMessageBox()
|
|
1513
1519
|
msgBox.setIcon(QMessageBox.Warning)
|
|
1514
1520
|
msgBox.setText(str(result))
|
|
@@ -1519,13 +1525,33 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1519
1525
|
|
|
1520
1526
|
if result:
|
|
1521
1527
|
logger.info("Launching the napari viewer with tracks...")
|
|
1528
|
+
self.napari_progress.setLabelText("Initializing Napari viewer...")
|
|
1529
|
+
self.napari_progress.setRange(0, 0)
|
|
1530
|
+
QApplication.processEvents()
|
|
1531
|
+
|
|
1532
|
+
def progress_cb(msg):
|
|
1533
|
+
if isinstance(msg, str):
|
|
1534
|
+
self.napari_progress.setLabelText(msg)
|
|
1535
|
+
QApplication.processEvents()
|
|
1536
|
+
|
|
1537
|
+
if "flush_memory" in result:
|
|
1538
|
+
result.pop("flush_memory")
|
|
1539
|
+
|
|
1522
1540
|
try:
|
|
1523
|
-
launch_napari_viewer(
|
|
1541
|
+
launch_napari_viewer(
|
|
1542
|
+
**result,
|
|
1543
|
+
block=False,
|
|
1544
|
+
flush_memory=False,
|
|
1545
|
+
progress_callback=progress_cb,
|
|
1546
|
+
)
|
|
1524
1547
|
logger.info("napari viewer was closed...")
|
|
1525
1548
|
except Exception as e:
|
|
1526
1549
|
logger.error(f"Failed to launch Napari: {e}")
|
|
1527
1550
|
QMessageBox.warning(self, "Error", f"Failed to launch Napari: {e}")
|
|
1551
|
+
finally:
|
|
1552
|
+
self.napari_progress.close()
|
|
1528
1553
|
else:
|
|
1554
|
+
self.napari_progress.close()
|
|
1529
1555
|
logger.warning(
|
|
1530
1556
|
"napari loading returned None (likely no trajectories found)."
|
|
1531
1557
|
)
|
|
@@ -1540,33 +1566,61 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1540
1566
|
|
|
1541
1567
|
def view_table_ui(self):
|
|
1542
1568
|
from celldetective.gui.tableUI import TableUI
|
|
1569
|
+
from celldetective.gui.workers import ProgressWindow
|
|
1570
|
+
from celldetective.processes.load_table import TableLoaderProcess
|
|
1543
1571
|
|
|
1544
1572
|
logger.info("Load table...")
|
|
1545
|
-
self.load_available_tables()
|
|
1546
1573
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1574
|
+
# Prepare args for the process
|
|
1575
|
+
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
1576
|
+
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
1577
|
+
|
|
1578
|
+
process_args = {
|
|
1579
|
+
"experiment": self.exp_dir,
|
|
1580
|
+
"population": self.mode,
|
|
1581
|
+
"well_option": self.well_option,
|
|
1582
|
+
"position_option": self.position_option,
|
|
1583
|
+
"show_frame_progress": False,
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
self.df = None
|
|
1587
|
+
|
|
1588
|
+
def on_table_loaded(df):
|
|
1589
|
+
self.df = df
|
|
1590
|
+
if self.df is not None:
|
|
1591
|
+
plot_mode = "plot_track_signals"
|
|
1592
|
+
if "TRACK_ID" not in list(self.df.columns):
|
|
1593
|
+
plot_mode = "static"
|
|
1594
|
+
self.tab_ui = TableUI(
|
|
1595
|
+
self.df,
|
|
1596
|
+
f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}",
|
|
1597
|
+
population=self.mode,
|
|
1598
|
+
plot_mode=plot_mode,
|
|
1599
|
+
save_inplace_option=True,
|
|
1600
|
+
)
|
|
1601
|
+
self.tab_ui.show()
|
|
1602
|
+
center_window(self.tab_ui)
|
|
1603
|
+
else:
|
|
1604
|
+
logger.info("Table could not be loaded...")
|
|
1605
|
+
msgBox = QMessageBox()
|
|
1606
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1607
|
+
msgBox.setText("No table could be loaded...")
|
|
1608
|
+
msgBox.setWindowTitle("Info")
|
|
1609
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1610
|
+
returnValue = msgBox.exec()
|
|
1611
|
+
|
|
1612
|
+
self.job = ProgressWindow(
|
|
1613
|
+
TableLoaderProcess,
|
|
1614
|
+
parent_window=self,
|
|
1615
|
+
title="Loading tables...",
|
|
1616
|
+
process_args=process_args,
|
|
1617
|
+
position_info=False,
|
|
1618
|
+
well_label="Wells loaded:",
|
|
1619
|
+
pos_label="Positions loaded:",
|
|
1620
|
+
)
|
|
1621
|
+
self.job._ProgressWindow__runner.signals.result.connect(on_table_loaded)
|
|
1622
|
+
|
|
1623
|
+
result = self.job.exec_()
|
|
1570
1624
|
|
|
1571
1625
|
def load_available_tables(self):
|
|
1572
1626
|
"""
|
|
@@ -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,7 +326,107 @@ 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()
|
|
318
333
|
error = pyqtSignal(str)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class GenericProgressWindow(CelldetectiveDialog):
|
|
337
|
+
|
|
338
|
+
def __init__(
|
|
339
|
+
self,
|
|
340
|
+
process=None,
|
|
341
|
+
parent_window=None,
|
|
342
|
+
title="",
|
|
343
|
+
process_args=None,
|
|
344
|
+
label_text="Progress:",
|
|
345
|
+
):
|
|
346
|
+
|
|
347
|
+
super().__init__()
|
|
348
|
+
|
|
349
|
+
self.setWindowTitle(f"{title}")
|
|
350
|
+
self.__process = process
|
|
351
|
+
self.parent_window = parent_window
|
|
352
|
+
|
|
353
|
+
self.__btn_stp = QPushButton("Cancel")
|
|
354
|
+
self.__label = QLabel("Idle")
|
|
355
|
+
self.progress_label = QLabel(label_text)
|
|
356
|
+
|
|
357
|
+
self.progress_bar = QProgressBar()
|
|
358
|
+
self.progress_bar.setValue(0)
|
|
359
|
+
self.progress_bar.setFormat("%p%")
|
|
360
|
+
|
|
361
|
+
self.__runner = Runner(
|
|
362
|
+
process=self.__process,
|
|
363
|
+
process_args=process_args,
|
|
364
|
+
)
|
|
365
|
+
logger.info("Runner initialized...")
|
|
366
|
+
self.pool = QThreadPool.globalInstance()
|
|
367
|
+
|
|
368
|
+
self.__btn_stp.clicked.connect(self.__stp_net)
|
|
369
|
+
self.__runner.signals.finished.connect(self.__on_finished)
|
|
370
|
+
self.__runner.signals.error.connect(self.__on_error)
|
|
371
|
+
self.__runner.signals.update_status.connect(self.__label.setText)
|
|
372
|
+
|
|
373
|
+
# Connect update_pos for generic progress (Runner maps generic list progress to update_pos)
|
|
374
|
+
self.__runner.signals.update_pos.connect(self.progress_bar.setValue)
|
|
375
|
+
|
|
376
|
+
self.__btn_stp.setDisabled(True)
|
|
377
|
+
|
|
378
|
+
self.layout = QVBoxLayout()
|
|
379
|
+
self.layout.addWidget(self.progress_label)
|
|
380
|
+
self.layout.addWidget(self.progress_bar)
|
|
381
|
+
|
|
382
|
+
self.btn_layout = QHBoxLayout()
|
|
383
|
+
self.btn_layout.addWidget(self.__btn_stp)
|
|
384
|
+
self.btn_layout.addWidget(self.__label)
|
|
385
|
+
|
|
386
|
+
self.layout.addLayout(self.btn_layout)
|
|
387
|
+
|
|
388
|
+
self.setLayout(self.layout)
|
|
389
|
+
self.setFixedSize(QSize(400, 150))
|
|
390
|
+
self.show()
|
|
391
|
+
self.raise_()
|
|
392
|
+
self.activateWindow()
|
|
393
|
+
logger.info("GenericProgressWindow initialized and shown.")
|
|
394
|
+
self.__run_net()
|
|
395
|
+
self.setModal(True)
|
|
396
|
+
|
|
397
|
+
def closeEvent(self, evnt):
|
|
398
|
+
evnt.ignore()
|
|
399
|
+
self.setWindowState(Qt.WindowMinimized)
|
|
400
|
+
|
|
401
|
+
def __run_net(self):
|
|
402
|
+
self.__btn_stp.setEnabled(True)
|
|
403
|
+
self.__label.setText("Running...")
|
|
404
|
+
self.pool.start(self.__runner)
|
|
405
|
+
|
|
406
|
+
def __stp_net(self):
|
|
407
|
+
self.__runner.close()
|
|
408
|
+
logger.info("\n Job cancelled... Abort.")
|
|
409
|
+
self.reject()
|
|
410
|
+
|
|
411
|
+
def __on_finished(self):
|
|
412
|
+
self.__btn_stp.setDisabled(True)
|
|
413
|
+
self.__label.setText("\nFinished!")
|
|
414
|
+
self.__runner.close()
|
|
415
|
+
self.accept()
|
|
416
|
+
|
|
417
|
+
def __on_error(self, message="Error"):
|
|
418
|
+
self.__btn_stp.setDisabled(True)
|
|
419
|
+
self.__label.setText("\nError")
|
|
420
|
+
self.__runner.close()
|
|
421
|
+
|
|
422
|
+
# Show error in a message box to ensure it's seen
|
|
423
|
+
from PyQt5.QtWidgets import QMessageBox
|
|
424
|
+
|
|
425
|
+
msg = QMessageBox()
|
|
426
|
+
msg.setIcon(QMessageBox.Critical)
|
|
427
|
+
msg.setText("Process failed")
|
|
428
|
+
msg.setInformativeText(str(message))
|
|
429
|
+
msg.setWindowTitle("Error")
|
|
430
|
+
msg.exec_()
|
|
431
|
+
|
|
432
|
+
self.reject()
|
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
|