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.
Files changed (35) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/gui/InitWindow.py +51 -12
  3. celldetective/gui/base/components.py +22 -1
  4. celldetective/gui/base_annotator.py +20 -9
  5. celldetective/gui/control_panel.py +21 -16
  6. celldetective/gui/event_annotator.py +51 -1060
  7. celldetective/gui/gui_utils.py +14 -5
  8. celldetective/gui/interactions_block.py +55 -25
  9. celldetective/gui/interactive_timeseries_viewer.py +11 -1
  10. celldetective/gui/measure_annotator.py +1064 -0
  11. celldetective/gui/plot_measurements.py +2 -4
  12. celldetective/gui/plot_signals_ui.py +3 -4
  13. celldetective/gui/process_block.py +298 -72
  14. celldetective/gui/viewers/base_viewer.py +134 -3
  15. celldetective/gui/viewers/contour_viewer.py +4 -4
  16. celldetective/gui/workers.py +25 -10
  17. celldetective/measure.py +3 -0
  18. celldetective/napari/utils.py +29 -19
  19. celldetective/processes/load_table.py +55 -0
  20. celldetective/processes/measure_cells.py +107 -81
  21. celldetective/processes/track_cells.py +39 -39
  22. celldetective/segmentation.py +1 -1
  23. celldetective/tracking.py +9 -0
  24. celldetective/utils/data_loaders.py +21 -1
  25. celldetective/utils/image_loaders.py +3 -0
  26. celldetective/utils/masks.py +1 -1
  27. celldetective/utils/maths.py +14 -1
  28. {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/METADATA +1 -1
  29. {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/RECORD +35 -32
  30. tests/gui/test_enhancements.py +351 -0
  31. tests/test_notebooks.py +2 -1
  32. {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/WHEEL +0 -0
  33. {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/entry_points.txt +0 -0
  34. {celldetective-1.5.0b1.dist-info → celldetective-1.5.0b3.dist-info}/licenses/LICENSE +0 -0
  35. {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.0b1"
1
+ __version__ = "1.5.0b3"
@@ -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
- if os.path.exists(os.sep.join([self.soft_path, "celldetective", "recent.txt"])):
326
- recent_exps = open(
327
- os.sep.join([self.soft_path, "celldetective", "recent.txt"]), "r"
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
- correct_annotation(self.filename)
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
- with open(
507
- os.sep.join([self.soft_path, "celldetective", "recent.txt"]), "a+"
508
- ) as f:
509
- f.write(self.exp_dir + "\n")
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 = list(self.df_tracks.columns)
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].setCurrentIndex(i + 1)
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
- print(f"{self.selection=}")
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
- print(e)
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
- print(e)
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
- print(f"File successfully written in {pathsave}.")
852
+ logger.info(f"File successfully written in {pathsave}.")
843
853
  except Exception as e:
844
- print(f"Error {e}...")
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
- print("Save trajectory function not implemented for BaseAnnotator class...")
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
- def run(self):
62
- logger.info("Loading background packages...")
63
- try:
64
- from celldetective.gui.viewers.base_viewer import StackVisualizer
65
- self.StackVisualizer = StackVisualizer
66
- except Exception:
67
- logger.error("Background packages not loaded...")
68
- logger.info("Background packages loaded...")
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(True)
557
- p.signal_analysis_action.setEnabled(True)
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(False)
561
- p.check_signals_btn.setEnabled(False)
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(True)
565
- self.NeighPanel.check_signals_btn.setEnabled(False)
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():