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
celldetective/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.5.
|
|
1
|
+
__version__ = "1.5.0b3"
|
celldetective/gui/InitWindow.py
CHANGED
|
@@ -21,6 +21,7 @@ from PyQt5.QtWidgets import (
|
|
|
21
21
|
QPushButton,
|
|
22
22
|
QMessageBox,
|
|
23
23
|
QVBoxLayout,
|
|
24
|
+
QProgressDialog,
|
|
24
25
|
)
|
|
25
26
|
from fonticon_mdi6 import MDI6
|
|
26
27
|
from psutil import cpu_count
|
|
@@ -57,6 +58,8 @@ class BackgroundLoader(QThread):
|
|
|
57
58
|
import scipy.ndimage
|
|
58
59
|
import tifffile
|
|
59
60
|
import numpy
|
|
61
|
+
import napari
|
|
62
|
+
from celldetective.napari.utils import launch_napari_viewer
|
|
60
63
|
except Exception:
|
|
61
64
|
logger.error("Background packages not loaded...")
|
|
62
65
|
logger.info("Background packages loaded...")
|
|
@@ -322,14 +325,18 @@ class AppInitWindow(CelldetectiveMainWindow):
|
|
|
322
325
|
def reload_previous_experiments(self):
|
|
323
326
|
|
|
324
327
|
self.recent_file_acts = []
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
recent_exps = recent_exps.readlines()
|
|
330
|
-
recent_exps = [r.strip() for r in recent_exps]
|
|
328
|
+
recent_path = os.sep.join([self.soft_path, "celldetective", "recent.txt"])
|
|
329
|
+
if os.path.exists(recent_path):
|
|
330
|
+
with open(recent_path, "r") as f:
|
|
331
|
+
recent_exps = [r.strip() for r in f.readlines()]
|
|
331
332
|
recent_exps.reverse()
|
|
332
|
-
recent_exps = list(dict.fromkeys(recent_exps))
|
|
333
|
+
recent_exps = list(dict.fromkeys(recent_exps))[:10]
|
|
334
|
+
|
|
335
|
+
# Auto-clean the file as well
|
|
336
|
+
with open(recent_path, "w") as f:
|
|
337
|
+
for r in reversed(recent_exps):
|
|
338
|
+
f.write(r + "\n")
|
|
339
|
+
|
|
333
340
|
self.recent_file_acts = [QAction(r, self) for r in recent_exps]
|
|
334
341
|
for r in self.recent_file_acts:
|
|
335
342
|
r.triggered.connect(
|
|
@@ -337,14 +344,33 @@ class AppInitWindow(CelldetectiveMainWindow):
|
|
|
337
344
|
)
|
|
338
345
|
|
|
339
346
|
def correct_seg_annotation(self):
|
|
347
|
+
|
|
340
348
|
from celldetective.napari.utils import correct_annotation
|
|
341
349
|
|
|
342
350
|
self.filename, _ = QFileDialog.getOpenFileName(
|
|
343
351
|
self, "Open Image", "/home/", "TIF Files (*.tif)"
|
|
344
352
|
)
|
|
353
|
+
|
|
345
354
|
if self.filename != "":
|
|
355
|
+
# 2. Show progress bar for opening the image in Napari
|
|
356
|
+
progress_open = QProgressDialog(
|
|
357
|
+
f"Opening {os.path.basename(self.filename)} in napari...",
|
|
358
|
+
None,
|
|
359
|
+
0,
|
|
360
|
+
0,
|
|
361
|
+
self,
|
|
362
|
+
)
|
|
363
|
+
progress_open.setWindowTitle("Please wait")
|
|
364
|
+
progress_open.setWindowModality(Qt.WindowModal)
|
|
365
|
+
progress_open.setMinimumDuration(0)
|
|
366
|
+
progress_open.show()
|
|
367
|
+
QApplication.processEvents()
|
|
368
|
+
|
|
346
369
|
logger.info(f"Opening {self.filename} in napari...")
|
|
347
|
-
|
|
370
|
+
try:
|
|
371
|
+
correct_annotation(self.filename)
|
|
372
|
+
finally:
|
|
373
|
+
progress_open.close()
|
|
348
374
|
else:
|
|
349
375
|
return None
|
|
350
376
|
|
|
@@ -503,10 +529,23 @@ class AppInitWindow(CelldetectiveMainWindow):
|
|
|
503
529
|
logger.info(f"Number of positions per well:")
|
|
504
530
|
pretty_table(number_pos)
|
|
505
531
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
)
|
|
509
|
-
|
|
532
|
+
recent_path = os.sep.join(
|
|
533
|
+
[self.soft_path, "celldetective", "recent.txt"]
|
|
534
|
+
)
|
|
535
|
+
recent_exps = []
|
|
536
|
+
if os.path.exists(recent_path):
|
|
537
|
+
with open(recent_path, "r") as f:
|
|
538
|
+
recent_exps = [r.strip() for r in f.readlines()]
|
|
539
|
+
|
|
540
|
+
recent_exps.append(self.exp_dir)
|
|
541
|
+
# Deduplicate (keep latest)
|
|
542
|
+
recent_exps = list(dict.fromkeys(reversed(recent_exps)))
|
|
543
|
+
recent_exps.reverse() # Back to original order (latest at end)
|
|
544
|
+
recent_exps = recent_exps[-10:] # Keep only last 10
|
|
545
|
+
|
|
546
|
+
with open(recent_path, "w") as f:
|
|
547
|
+
for r in recent_exps:
|
|
548
|
+
f.write(r + "\n")
|
|
510
549
|
|
|
511
550
|
threading.Thread(
|
|
512
551
|
target=log_position_stats, args=(wells,), daemon=True
|
|
@@ -11,11 +11,13 @@ from PyQt5.QtWidgets import (
|
|
|
11
11
|
QStylePainter,
|
|
12
12
|
QStyleOptionComboBox,
|
|
13
13
|
QStyle,
|
|
14
|
-
QFrame,
|
|
15
14
|
QSizePolicy,
|
|
16
15
|
QProgressDialog,
|
|
16
|
+
QPushButton,
|
|
17
|
+
QFrame,
|
|
17
18
|
)
|
|
18
19
|
from PyQt5.QtCore import Qt, pyqtSignal, QEvent
|
|
20
|
+
from superqt.fonticon import icon
|
|
19
21
|
from celldetective.gui.base.styles import Styles
|
|
20
22
|
|
|
21
23
|
|
|
@@ -118,6 +120,8 @@ class QCheckableComboBox(QComboBox):
|
|
|
118
120
|
actions = self.toolMenu.actions()
|
|
119
121
|
|
|
120
122
|
item = self.model().itemFromIndex(index)
|
|
123
|
+
if item is None:
|
|
124
|
+
return
|
|
121
125
|
if item.checkState() == Qt.Checked:
|
|
122
126
|
item.setCheckState(Qt.Unchecked)
|
|
123
127
|
actions[idx].setChecked(False)
|
|
@@ -247,3 +251,20 @@ class QHSeperationLine(QFrame):
|
|
|
247
251
|
self.setFrameShape(QFrame.HLine)
|
|
248
252
|
self.setFrameShadow(QFrame.Sunken)
|
|
249
253
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class HoverButton(QPushButton):
|
|
257
|
+
def __init__(self, text, icon_enum, default_color="black", hover_color="white"):
|
|
258
|
+
super().__init__(text)
|
|
259
|
+
self.icon_enum = icon_enum
|
|
260
|
+
self.default_color = default_color
|
|
261
|
+
self.hover_color = hover_color
|
|
262
|
+
self.setIcon(icon(self.icon_enum, color=self.default_color))
|
|
263
|
+
|
|
264
|
+
def enterEvent(self, event):
|
|
265
|
+
self.setIcon(icon(self.icon_enum, color=self.hover_color))
|
|
266
|
+
super().enterEvent(event)
|
|
267
|
+
|
|
268
|
+
def leaveEvent(self, event):
|
|
269
|
+
self.setIcon(icon(self.icon_enum, color=self.default_color))
|
|
270
|
+
super().leaveEvent(event)
|
|
@@ -47,6 +47,9 @@ from celldetective.gui.gui_utils import (
|
|
|
47
47
|
from celldetective.gui.base.figure_canvas import FigureCanvas
|
|
48
48
|
from celldetective.gui.base.utils import center_window
|
|
49
49
|
import gc
|
|
50
|
+
from celldetective import get_logger
|
|
51
|
+
|
|
52
|
+
logger = get_logger(__name__)
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
@@ -302,7 +305,11 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
302
305
|
]
|
|
303
306
|
# self.log_btns = [QPushButton() for i in range(self.n_signals)]
|
|
304
307
|
|
|
305
|
-
signals =
|
|
308
|
+
signals = [
|
|
309
|
+
c
|
|
310
|
+
for c in self.df_tracks.columns
|
|
311
|
+
if pd.api.types.is_numeric_dtype(self.df_tracks[c])
|
|
312
|
+
]
|
|
306
313
|
|
|
307
314
|
to_remove = [
|
|
308
315
|
"FRAME",
|
|
@@ -354,7 +361,10 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
354
361
|
|
|
355
362
|
for i in range(len(self.signal_choice_cb)):
|
|
356
363
|
self.signal_choice_cb[i].addItems(["--"] + signals)
|
|
357
|
-
self.signal_choice_cb[i].
|
|
364
|
+
if i + 1 < self.signal_choice_cb[i].count():
|
|
365
|
+
self.signal_choice_cb[i].setCurrentIndex(i + 1)
|
|
366
|
+
else:
|
|
367
|
+
self.signal_choice_cb[i].setCurrentIndex(0)
|
|
358
368
|
self.signal_choice_cb[i].currentIndexChanged.connect(self.plot_signals)
|
|
359
369
|
|
|
360
370
|
def on_scatter_pick(self, event):
|
|
@@ -364,7 +374,7 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
364
374
|
self.correct_btn.disconnect()
|
|
365
375
|
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
366
376
|
|
|
367
|
-
|
|
377
|
+
logger.info(f"{self.selection=}")
|
|
368
378
|
|
|
369
379
|
ind = event.ind
|
|
370
380
|
|
|
@@ -711,7 +721,7 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
711
721
|
try:
|
|
712
722
|
self.df_tracks = self.df_tracks.drop([c], axis=1)
|
|
713
723
|
except Exception as e:
|
|
714
|
-
|
|
724
|
+
logger.error(e)
|
|
715
725
|
item_idx = self.class_choice_cb.findText(class_to_delete)
|
|
716
726
|
self.class_choice_cb.removeItem(item_idx)
|
|
717
727
|
|
|
@@ -755,7 +765,7 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
755
765
|
self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
|
|
756
766
|
self.log_scale = False
|
|
757
767
|
except Exception as e:
|
|
758
|
-
|
|
768
|
+
logger.error(e)
|
|
759
769
|
|
|
760
770
|
# self.cell_ax.autoscale()
|
|
761
771
|
self.cell_fcanvas.canvas.draw_idle()
|
|
@@ -839,9 +849,9 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
839
849
|
pathsave += ".npy"
|
|
840
850
|
try:
|
|
841
851
|
np.save(pathsave, training_set)
|
|
842
|
-
|
|
852
|
+
logger.info(f"File successfully written in {pathsave}.")
|
|
843
853
|
except Exception as e:
|
|
844
|
-
|
|
854
|
+
logger.error(f"Error {e}...")
|
|
845
855
|
|
|
846
856
|
def create_new_event_class(self):
|
|
847
857
|
|
|
@@ -897,10 +907,11 @@ class BaseAnnotator(CelldetectiveMainWindow, Styles):
|
|
|
897
907
|
|
|
898
908
|
def save_trajectories(self):
|
|
899
909
|
# specific to signal/static annotator
|
|
900
|
-
|
|
910
|
+
logger.info(
|
|
911
|
+
"Save trajectory function not implemented for BaseAnnotator class..."
|
|
912
|
+
)
|
|
901
913
|
|
|
902
914
|
def cancel_selection(self):
|
|
903
|
-
print("we are in cancel selection...")
|
|
904
915
|
self.hide_annotation_buttons()
|
|
905
916
|
self.correct_btn.setEnabled(False)
|
|
906
917
|
self.correct_btn.setText("correct")
|
|
@@ -58,14 +58,15 @@ logger = logging.getLogger(__name__)
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class BackgroundLoader(QThread):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
def run(self):
|
|
62
|
+
logger.info("Loading background packages...")
|
|
63
|
+
try:
|
|
64
|
+
from celldetective.gui.viewers.base_viewer import StackVisualizer
|
|
65
|
+
|
|
66
|
+
self.StackVisualizer = StackVisualizer
|
|
67
|
+
except Exception:
|
|
68
|
+
logger.error("Background packages not loaded...")
|
|
69
|
+
logger.info("Background packages loaded...")
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
class ControlPanel(CelldetectiveMainWindow):
|
|
@@ -179,9 +180,11 @@ class ControlPanel(CelldetectiveMainWindow):
|
|
|
179
180
|
|
|
180
181
|
name = self.exp_dir.split(os.sep)[-2]
|
|
181
182
|
experiment_label = QLabel(f"Experiment:")
|
|
182
|
-
experiment_label.setStyleSheet(
|
|
183
|
+
experiment_label.setStyleSheet(
|
|
184
|
+
"""
|
|
183
185
|
font-weight: bold;
|
|
184
|
-
"""
|
|
186
|
+
"""
|
|
187
|
+
)
|
|
185
188
|
|
|
186
189
|
self.folder_exp_btn = QPushButton()
|
|
187
190
|
self.folder_exp_btn.setIcon(icon(MDI6.folder, color="black"))
|
|
@@ -553,16 +556,18 @@ class ControlPanel(CelldetectiveMainWindow):
|
|
|
553
556
|
for p in self.ProcessPopulations:
|
|
554
557
|
p.check_seg_btn.setEnabled(False)
|
|
555
558
|
p.check_tracking_result_btn.setEnabled(False)
|
|
556
|
-
p.view_tab_btn.setEnabled(
|
|
557
|
-
p.signal_analysis_action.setEnabled(
|
|
559
|
+
p.view_tab_btn.setEnabled(self.position_list.isAnySelected())
|
|
560
|
+
p.signal_analysis_action.setEnabled(self.position_list.isAnySelected())
|
|
558
561
|
p.check_seg_btn.setEnabled(False)
|
|
559
562
|
p.check_tracking_result_btn.setEnabled(False)
|
|
560
|
-
p.check_measurements_btn.setEnabled(
|
|
561
|
-
p.check_signals_btn.setEnabled(
|
|
563
|
+
p.check_measurements_btn.setEnabled(self.position_list.isAnySelected())
|
|
564
|
+
p.check_signals_btn.setEnabled(self.position_list.isAnySelected())
|
|
562
565
|
p.delete_tracks_btn.hide()
|
|
563
566
|
|
|
564
|
-
self.NeighPanel.view_tab_btn.setEnabled(
|
|
565
|
-
self.NeighPanel.check_signals_btn.setEnabled(
|
|
567
|
+
self.NeighPanel.view_tab_btn.setEnabled(self.position_list.isAnySelected())
|
|
568
|
+
self.NeighPanel.check_signals_btn.setEnabled(
|
|
569
|
+
self.position_list.isAnySelected()
|
|
570
|
+
)
|
|
566
571
|
self.view_stack_btn.setEnabled(False)
|
|
567
572
|
|
|
568
573
|
elif self.well_list.isMultipleSelection():
|