celldetective 1.4.0__py3-none-any.whl → 1.4.1__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 (78) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/exceptions.py +11 -0
  3. celldetective/filters.py +7 -1
  4. celldetective/gui/InitWindow.py +4 -1
  5. celldetective/gui/__init__.py +2 -9
  6. celldetective/gui/about.py +2 -2
  7. celldetective/gui/base_annotator.py +786 -0
  8. celldetective/gui/classifier_widget.py +18 -13
  9. celldetective/gui/configure_new_exp.py +51 -30
  10. celldetective/gui/control_panel.py +10 -7
  11. celldetective/gui/{signal_annotator.py → event_annotator.py} +473 -1437
  12. celldetective/gui/generic_signal_plot.py +2 -1
  13. celldetective/gui/gui_utils.py +5 -2
  14. celldetective/gui/help/neighborhood.json +2 -2
  15. celldetective/gui/layouts.py +21 -11
  16. celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +3 -1
  17. celldetective/gui/process_block.py +129 -91
  18. celldetective/gui/processes/downloader.py +37 -34
  19. celldetective/gui/processes/measure_cells.py +14 -8
  20. celldetective/gui/processes/segment_cells.py +21 -6
  21. celldetective/gui/processes/track_cells.py +12 -13
  22. celldetective/gui/settings/__init__.py +7 -0
  23. celldetective/gui/settings/_settings_base.py +70 -0
  24. celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +35 -91
  25. celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +28 -81
  26. celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +1 -1
  27. celldetective/gui/settings/_settings_segmentation.py +49 -0
  28. celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +33 -79
  29. celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +73 -95
  30. celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +64 -87
  31. celldetective/gui/styles.py +2 -1
  32. celldetective/gui/survival_ui.py +1 -1
  33. celldetective/gui/tableUI.py +25 -0
  34. celldetective/gui/table_ops/__init__.py +0 -0
  35. celldetective/gui/table_ops/merge_groups.py +118 -0
  36. celldetective/gui/viewers.py +3 -5
  37. celldetective/gui/workers.py +0 -2
  38. celldetective/io.py +98 -55
  39. celldetective/links/zenodo.json +145 -144
  40. celldetective/measure.py +31 -26
  41. celldetective/preprocessing.py +34 -21
  42. celldetective/regionprops/_regionprops.py +16 -5
  43. celldetective/scripts/measure_cells.py +5 -5
  44. celldetective/scripts/measure_relative.py +16 -11
  45. celldetective/scripts/segment_cells.py +4 -4
  46. celldetective/scripts/segment_cells_thresholds.py +3 -3
  47. celldetective/scripts/track_cells.py +7 -7
  48. celldetective/scripts/train_segmentation_model.py +10 -1
  49. celldetective/tracking.py +10 -4
  50. celldetective/utils.py +59 -58
  51. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/METADATA +1 -1
  52. celldetective-1.4.1.dist-info/RECORD +123 -0
  53. tests/gui/__init__.py +0 -0
  54. tests/gui/test_new_project.py +228 -0
  55. tests/{test_qt.py → gui/test_project.py} +22 -26
  56. tests/test_preprocessing.py +2 -2
  57. celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
  58. celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
  59. celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
  60. celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
  61. celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
  62. celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
  63. celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
  64. celldetective/models/signal_detection/NucCond/config_input.json +0 -1
  65. celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
  66. celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
  67. celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
  68. celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
  69. celldetective/models/signal_detection/NucCond/scores.npy +0 -0
  70. celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
  71. celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
  72. celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
  73. celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
  74. celldetective-1.4.0.dist-info/RECORD +0 -131
  75. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/WHEEL +0 -0
  76. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
  77. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/licenses/LICENSE +0 -0
  78. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
1
+ from typing import List
2
+
3
+ import numpy as np
4
+ from PyQt5.QtCore import QSize, Qt
5
+ from PyQt5.QtWidgets import QComboBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout
6
+ from fonticon_mdi6 import MDI6
7
+ from superqt.fonticon import icon
8
+
9
+ from celldetective.gui import CelldetectiveWidget
10
+ from celldetective.gui.gui_utils import PandasModel, center_window
11
+
12
+
13
+ class MergeGroupWidget(CelldetectiveWidget):
14
+ def __init__(self, parent_window, columns: List[str] = [], n_cols_init: int = 3):
15
+
16
+ super().__init__()
17
+ self.parent_window = parent_window
18
+
19
+ self.setWindowTitle("Merge classifications")
20
+ self.group_cols = [c for c in list(self.parent_window.data.columns) if c.startswith("group_") or c.startswith("status_")]
21
+ self.group_cols.insert(0, "--")
22
+ if len(columns) > n_cols_init:
23
+ n_cols_init = len(columns)
24
+
25
+ center_window(self)
26
+
27
+ layout = QVBoxLayout(self)
28
+ layout.setContentsMargins(30, 10, 30, 30)
29
+
30
+ label = QLabel(
31
+ "Merge several binary or multi-label classification features into a multi-label classification feature where each state is one of the possible combinations.\n")
32
+
33
+ label.setWordWrap(True) # enables automatic line breaking
34
+ label.setTextInteractionFlags(Qt.TextSelectableByMouse) # optional, to allow copy
35
+ label.setStyleSheet("color: gray;") # optional style
36
+
37
+ layout.addWidget(label)
38
+
39
+
40
+ self.name_le = QLineEdit("group_multilabel")
41
+ name_layout = QHBoxLayout()
42
+ name_layout.addWidget(QLabel("name: "), 25)
43
+ name_layout.addWidget(self.name_le, 75)
44
+ layout.addLayout(name_layout)
45
+
46
+ self.cbs_layout = QVBoxLayout()
47
+ self.cbs_layout.setContentsMargins(0,10,0,0)
48
+
49
+ self.cbs = []
50
+ for i in range(n_cols_init):
51
+ cb_i = QComboBox()
52
+ cb_i.addItems(self.group_cols)
53
+ if i < len(columns):
54
+ selection = columns[i]
55
+ idx = cb_i.findText(selection)
56
+ if idx>=0:
57
+ cb_i.setCurrentIndex(idx)
58
+ else:
59
+ cb_i.setCurrentIndex(0)
60
+ self.cbs.append(cb_i)
61
+
62
+ col_layout = QHBoxLayout()
63
+ col_layout.addWidget(QLabel(f'state {i}: '), 25)
64
+ col_layout.addWidget(cb_i, 75)
65
+ self.cbs_layout.addLayout(col_layout)
66
+
67
+ layout.addLayout(self.cbs_layout)
68
+
69
+ self.add_feature_btn = QPushButton()
70
+ self.add_feature_btn.setIcon(icon(MDI6.plus, color=self.help_color))
71
+ self.add_feature_btn.setIconSize(QSize(20, 20))
72
+ self.add_feature_btn.clicked.connect(self.add_col)
73
+ self.add_feature_btn.setStyleSheet(self.button_select_all)
74
+ layout.addWidget(self.add_feature_btn, alignment=Qt.AlignRight)
75
+
76
+ self.submit_btn = QPushButton('Compute')
77
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
78
+ self.submit_btn.clicked.connect(self.compute)
79
+ layout.addWidget(self.submit_btn, 30)
80
+
81
+ self.setAttribute(Qt.WA_DeleteOnClose)
82
+
83
+ def add_col(self):
84
+ cb_i = QComboBox()
85
+ cb_i.addItems(self.group_cols)
86
+ self.cbs.append(cb_i)
87
+
88
+ col_layout = QHBoxLayout()
89
+ col_layout.addWidget(QLabel(f'state {len(self.cbs)}: '), 25)
90
+ col_layout.addWidget(cb_i, 75)
91
+
92
+ self.cbs_layout.addLayout(col_layout)
93
+
94
+ def compute(self):
95
+
96
+ cols_to_merge = [cb_i.currentText() for cb_i in self.cbs if cb_i.currentText() != "--"]
97
+ name = self.name_le.text()
98
+ if " " in name:
99
+ name.replace(" ","_")
100
+ if name == '':
101
+ name = "multilabel"
102
+ if not name.startswith("group_"):
103
+ name = "group_" + name
104
+
105
+ if len(cols_to_merge) > 1:
106
+ print("Computing a multi-label classification from the classification feature sources...")
107
+ bases = [int(self.parent_window.data[c].max()) + 1 for c in cols_to_merge]
108
+ multipliers = np.concatenate(([1], np.cumprod(bases[:-1])))
109
+ self.parent_window.data[name] = (self.parent_window.data[cols_to_merge] * multipliers).sum(axis=1)
110
+ self.parent_window.data.loc[self.parent_window.data[cols_to_merge].isna().any(axis=1), name] = np.nan
111
+
112
+ self.parent_window.model = PandasModel(self.parent_window.data)
113
+ self.parent_window.table_view.setModel(self.parent_window.model)
114
+ self.close()
115
+ elif len(cols_to_merge) == 1:
116
+ print("Only one classification feature was selected, nothing to merge...")
117
+ else:
118
+ print("No classification feature was selected...")
@@ -1,4 +1,3 @@
1
- import numpy as np
2
1
  from celldetective.io import auto_load_number_of_frames, load_frames
3
2
  from celldetective.filters import *
4
3
  from celldetective.segmentation import filter_image, threshold_image
@@ -11,17 +10,16 @@ from natsort import natsorted
11
10
  from glob import glob
12
11
  import os
13
12
 
14
- from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
13
+ from PyQt5.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
15
14
  from PyQt5.QtCore import Qt, QSize
16
15
  from PyQt5.QtGui import QKeySequence, QDoubleValidator
17
- from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit, PreprocessingLayout2
18
- from celldetective.gui import Styles, CelldetectiveWidget
16
+ from celldetective.gui.gui_utils import FigureCanvas, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit, PreprocessingLayout2
17
+ from celldetective.gui import CelldetectiveWidget
19
18
  from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
20
19
  from superqt.fonticon import icon
21
20
  from fonticon_mdi6 import MDI6
22
21
  from matplotlib_scalebar.scalebar import ScaleBar
23
22
  import gc
24
- from celldetective.utils import mask_edges
25
23
  from scipy.ndimage import shift
26
24
 
27
25
  class StackVisualizer(CelldetectiveWidget):
@@ -4,8 +4,6 @@ from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, QSize, Qt
4
4
 
5
5
  from celldetective.gui.base_components import CelldetectiveDialog
6
6
  from celldetective.gui.gui_utils import center_window
7
- from celldetective.gui import Styles
8
- import time
9
7
  import math
10
8
 
11
9
  class ProgressWindow(CelldetectiveDialog):
celldetective/io.py CHANGED
@@ -24,13 +24,18 @@ from magicgui import magicgui
24
24
  from pathlib import Path, PurePath
25
25
  from shutil import copyfile, rmtree
26
26
 
27
- from celldetective.utils import _rearrange_multichannel_frame, _fix_no_contrast, zoom_multiframes,ConfigSectionMap, extract_experiment_channels, _extract_labels_from_config, get_zenodo_files, download_zenodo_file
28
- from celldetective.utils import interpolate_nan_multichannel, _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel, get_config
27
+ from celldetective.utils import _rearrange_multichannel_frame, _fix_no_contrast, zoom_multiframes, \
28
+ config_section_to_dict, extract_experiment_channels, _extract_labels_from_config, get_zenodo_files, \
29
+ download_zenodo_file
30
+ from celldetective.utils import interpolate_nan_multichannel, get_config
29
31
 
30
32
  from stardist import fill_label_holes
31
33
  from skimage.transform import resize
32
34
  import re
33
35
 
36
+ from typing import List, Tuple, Union
37
+ import numbers
38
+
34
39
  def extract_experiment_from_well(well_path):
35
40
 
36
41
  """
@@ -202,6 +207,9 @@ def collect_experiment_metadata(pos_path=None, well_path=None):
202
207
  if not well_path.endswith(os.sep):
203
208
  well_path += os.sep
204
209
  experiment = extract_experiment_from_well(well_path)
210
+ else:
211
+ print("Please provide a position or well path...")
212
+ return None
205
213
 
206
214
  wells = list(get_experiment_wells(experiment))
207
215
  idx = wells.index(well_path)
@@ -211,7 +219,14 @@ def collect_experiment_metadata(pos_path=None, well_path=None):
211
219
  else:
212
220
  pos_name = 0
213
221
 
214
- dico = {"pos_path": pos_path, "position": pos_path, "pos_name": pos_name, "well_path": well_path, "well_name": well_name, "well_nbr": well_nbr, "experiment": experiment}
222
+ dico = {"pos_path": pos_path,
223
+ "position": pos_path,
224
+ "pos_name": pos_name,
225
+ "well_path": well_path,
226
+ "well_name": well_name,
227
+ "well_nbr": well_nbr,
228
+ "experiment": experiment,
229
+ }
215
230
 
216
231
  meta = get_experiment_metadata(experiment) # None or dict of metadata
217
232
  if meta is not None:
@@ -306,9 +321,9 @@ def get_spatial_calibration(experiment):
306
321
  """
307
322
 
308
323
  config = get_config(experiment)
309
- PxToUm = float(ConfigSectionMap(config, "MovieSettings")["pxtoum"])
324
+ px_to_um = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
310
325
 
311
- return PxToUm
326
+ return px_to_um
312
327
 
313
328
 
314
329
  def get_temporal_calibration(experiment):
@@ -351,14 +366,14 @@ def get_temporal_calibration(experiment):
351
366
  """
352
367
 
353
368
  config = get_config(experiment)
354
- FrameToMin = float(ConfigSectionMap(config, "MovieSettings")["frametomin"])
369
+ frame_to_min = float(config_section_to_dict(config, "MovieSettings")["frametomin"])
355
370
 
356
- return FrameToMin
371
+ return frame_to_min
357
372
 
358
373
  def get_experiment_metadata(experiment):
359
374
 
360
375
  config = get_config(experiment)
361
- metadata = ConfigSectionMap(config, "Metadata")
376
+ metadata = config_section_to_dict(config, "Metadata")
362
377
  return metadata
363
378
 
364
379
  def get_experiment_labels(experiment):
@@ -367,12 +382,12 @@ def get_experiment_labels(experiment):
367
382
  wells = get_experiment_wells(experiment)
368
383
  nbr_of_wells = len(wells)
369
384
 
370
- labels = ConfigSectionMap(config, "Labels")
385
+ labels = config_section_to_dict(config, "Labels")
371
386
  for k in list(labels.keys()):
372
387
  values = labels[k].split(',')
373
388
  if nbr_of_wells != len(values):
374
389
  values = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
375
- if np.all([s.isnumeric() for s in values]):
390
+ if np.all(np.array([s.isnumeric() for s in values])):
376
391
  values = [float(s) for s in values]
377
392
  labels.update({k: values})
378
393
 
@@ -427,7 +442,7 @@ def get_experiment_concentrations(experiment, dtype=str):
427
442
  wells = get_experiment_wells(experiment)
428
443
  nbr_of_wells = len(wells)
429
444
 
430
- concentrations = ConfigSectionMap(config, "Labels")["concentrations"].split(",")
445
+ concentrations = config_section_to_dict(config, "Labels")["concentrations"].split(",")
431
446
  if nbr_of_wells != len(concentrations):
432
447
  concentrations = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
433
448
 
@@ -482,7 +497,7 @@ def get_experiment_cell_types(experiment, dtype=str):
482
497
  wells = get_experiment_wells(experiment)
483
498
  nbr_of_wells = len(wells)
484
499
 
485
- cell_types = ConfigSectionMap(config, "Labels")["cell_types"].split(",")
500
+ cell_types = config_section_to_dict(config, "Labels")["cell_types"].split(",")
486
501
  if nbr_of_wells != len(cell_types):
487
502
  cell_types = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
488
503
 
@@ -534,7 +549,7 @@ def get_experiment_antibodies(experiment, dtype=str):
534
549
  wells = get_experiment_wells(experiment)
535
550
  nbr_of_wells = len(wells)
536
551
 
537
- antibodies = ConfigSectionMap(config, "Labels")["antibodies"].split(",")
552
+ antibodies = config_section_to_dict(config, "Labels")["antibodies"].split(",")
538
553
  if nbr_of_wells != len(antibodies):
539
554
  antibodies = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
540
555
 
@@ -589,7 +604,7 @@ def get_experiment_pharmaceutical_agents(experiment, dtype=str):
589
604
  wells = get_experiment_wells(experiment)
590
605
  nbr_of_wells = len(wells)
591
606
 
592
- pharmaceutical_agents = ConfigSectionMap(config, "Labels")["pharmaceutical_agents"].split(",")
607
+ pharmaceutical_agents = config_section_to_dict(config, "Labels")["pharmaceutical_agents"].split(",")
593
608
  if nbr_of_wells != len(pharmaceutical_agents):
594
609
  pharmaceutical_agents = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
595
610
 
@@ -599,7 +614,7 @@ def get_experiment_pharmaceutical_agents(experiment, dtype=str):
599
614
  def get_experiment_populations(experiment, dtype=str):
600
615
 
601
616
  config = get_config(experiment)
602
- populations_str = ConfigSectionMap(config, "Populations")
617
+ populations_str = config_section_to_dict(config, "Populations")
603
618
  if populations_str is not None:
604
619
  populations = populations_str['populations'].split(',')
605
620
  else:
@@ -607,7 +622,7 @@ def get_experiment_populations(experiment, dtype=str):
607
622
  return list([dtype(c) for c in populations])
608
623
 
609
624
 
610
- def interpret_wells_and_positions(experiment, well_option, position_option):
625
+ def interpret_wells_and_positions(experiment: str, well_option: Union[str,int,List[int]], position_option: Union[str,int,List[int]]) -> Union[Tuple[List[int], List[int]], None]:
611
626
  """
612
627
  Interpret well and position options for a given experiment.
613
628
 
@@ -617,8 +632,8 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
617
632
 
618
633
  Parameters
619
634
  ----------
620
- experiment : object
621
- The experiment object containing well information.
635
+ experiment : str
636
+ The experiment path containing well information.
622
637
  well_option : str, int, or list of int
623
638
  The well selection option:
624
639
  - '*' : Select all wells.
@@ -660,6 +675,9 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
660
675
  well_indices = [int(well_option)]
661
676
  elif isinstance(well_option, list):
662
677
  well_indices = well_option
678
+ else:
679
+ print("Well indices could not be interpreted...")
680
+ return None
663
681
 
664
682
  if position_option == '*':
665
683
  position_indices = None
@@ -667,6 +685,9 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
667
685
  position_indices = np.array([position_option], dtype=int)
668
686
  elif isinstance(position_option, list):
669
687
  position_indices = position_option
688
+ else:
689
+ print("Position indices could not be interpreted...")
690
+ return None
670
691
 
671
692
  return well_indices, position_indices
672
693
 
@@ -795,7 +816,11 @@ def get_position_table(pos, population, return_path=False):
795
816
  table = pos + os.sep.join(['output', 'tables', f'trajectories_{population}.csv'])
796
817
 
797
818
  if os.path.exists(table):
798
- df_pos = pd.read_csv(table, low_memory=False)
819
+ try:
820
+ df_pos = pd.read_csv(table, low_memory=False)
821
+ except Exception as e:
822
+ print(e)
823
+ df_pos = None
799
824
  else:
800
825
  df_pos = None
801
826
 
@@ -970,7 +995,7 @@ def load_experiment_tables(experiment, population='targets', well_option='*', po
970
995
  config = get_config(experiment)
971
996
  wells = get_experiment_wells(experiment)
972
997
 
973
- movie_prefix = ConfigSectionMap(config, "MovieSettings")["movie_prefix"]
998
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
974
999
 
975
1000
  labels = get_experiment_labels(experiment)
976
1001
  metadata = get_experiment_metadata(experiment) # None or dict of metadata
@@ -1027,15 +1052,23 @@ def load_experiment_tables(experiment, population='targets', well_option='*', po
1027
1052
 
1028
1053
  if metadata is not None:
1029
1054
  keys = list(metadata.keys())
1030
- for k in keys:
1031
- df_pos[k] = metadata[k]
1055
+ for key in keys:
1056
+ df_pos[key] = metadata[key]
1032
1057
 
1033
1058
  df.append(df_pos)
1034
1059
  any_table = True
1035
1060
 
1036
- pos_dict = {'pos_path': pos_path, 'pos_index': real_pos_index, 'pos_name': pos_name, 'table_path': table,
1037
- 'stack_path': stack_path,'well_path': well_path, 'well_index': real_well_index, 'well_name': well_name,
1038
- 'well_number': well_number, 'well_alias': well_alias}
1061
+ pos_dict = {'pos_path': pos_path,
1062
+ 'pos_index': real_pos_index,
1063
+ 'pos_name': pos_name,
1064
+ 'table_path': table,
1065
+ 'stack_path': stack_path,
1066
+ 'well_path': well_path,
1067
+ 'well_index': real_well_index,
1068
+ 'well_name': well_name,
1069
+ 'well_number': well_number,
1070
+ 'well_alias': well_alias,
1071
+ }
1039
1072
 
1040
1073
  df_pos_info.append(pos_dict)
1041
1074
 
@@ -1098,7 +1131,9 @@ def locate_stack(position, prefix='Aligned'):
1098
1131
  position += os.sep
1099
1132
 
1100
1133
  stack_path = glob(position + os.sep.join(['movie', f'{prefix}*.tif']))
1101
- assert len(stack_path) > 0, f"No movie with prefix {prefix} found..."
1134
+ if not stack_path:
1135
+ raise FileNotFoundError(f"No movie with prefix {prefix} found...")
1136
+
1102
1137
  stack = imread(stack_path[0].replace('\\', '/'))
1103
1138
  stack_length = auto_load_number_of_frames(stack_path[0])
1104
1139
 
@@ -1417,7 +1452,7 @@ def auto_load_number_of_frames(stack_path):
1417
1452
 
1418
1453
  Handle invalid or missing paths gracefully:
1419
1454
 
1420
- >>> frames = auto_load_number_of_frames(None)
1455
+ >>> frames = auto_load_number_of_frames("stack.tif")
1421
1456
  >>> print(frames)
1422
1457
  None
1423
1458
 
@@ -1520,7 +1555,7 @@ def parse_isotropic_radii(string):
1520
1555
 
1521
1556
  """
1522
1557
 
1523
- sections = re.split(',| ', string)
1558
+ sections = re.split(r"[ ,]", string)
1524
1559
  radii = []
1525
1560
  for k, s in enumerate(sections):
1526
1561
  if s.isdigit():
@@ -2344,7 +2379,7 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
2344
2379
  position : str
2345
2380
  The path to the position or experiment directory.
2346
2381
  prefix : str, optional
2347
- The prefix used to identify the the movie file. The default is "Aligned".
2382
+ The prefix used to identify the movie file. The default is "Aligned".
2348
2383
  population : str, optional
2349
2384
  The population type to load, either "target" or "effector". The default is "target".
2350
2385
 
@@ -2396,7 +2431,7 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
2396
2431
  return data, properties, graph, labels, stack
2397
2432
 
2398
2433
 
2399
- def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False):
2434
+ def auto_correct_masks(masks, bbox_factor: float = 1.75, min_area: int = 9, fill_labels: bool = False):
2400
2435
 
2401
2436
  """
2402
2437
  Correct segmentation masks to ensure consistency and remove anomalies.
@@ -2416,6 +2451,13 @@ def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False)
2416
2451
  masks : np.ndarray
2417
2452
  A 2D array representing the segmented mask image with labeled regions. Each unique value
2418
2453
  in the array represents a different object or cell.
2454
+ bbox_factor : float, optional
2455
+ A factor on cell area that is compared directly to the bounding box area of the cell, to detect remote cells
2456
+ sharing a same label value. The default is `1.75`.
2457
+ min_area : int, optional
2458
+ Discard cells that have an area smaller than this minimum area (px²). The default is `9` (3x3 pixels).
2459
+ fill_labels : bool, optional
2460
+ Fill holes within cell masks automatically. The default is `False`.
2419
2461
 
2420
2462
  Returns
2421
2463
  -------
@@ -2439,6 +2481,8 @@ def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False)
2439
2481
  [0, 2, 2, 1],
2440
2482
  [0, 2, 0, 0]])
2441
2483
  """
2484
+
2485
+ assert masks.ndim==2,"`masks` should be a 2D numpy array..."
2442
2486
 
2443
2487
  # Avoid negative mask values
2444
2488
  masks[masks<0] = np.abs(masks[masks<0])
@@ -2503,6 +2547,8 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
2503
2547
  The prefix used to identify the stack. The default is 'Aligned'.
2504
2548
  population : str, optional
2505
2549
  The population type for which the segmentation is performed. The default is 'target'.
2550
+ flush_memory : bool, optional
2551
+ Pop napari layers upon closing the viewer to empty the memory footprint. The default is `False`.
2506
2552
 
2507
2553
  Notes
2508
2554
  -----
@@ -2559,7 +2605,7 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
2559
2605
  for k in keys:
2560
2606
  info.update({k: metadata_info[k]})
2561
2607
 
2562
- spatial_calibration = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
2608
+ spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
2563
2609
  channel_names, channel_indices = extract_experiment_channels(expfolder)
2564
2610
 
2565
2611
  annotation_folder = expfolder + os.sep + f'annotations_{population}' + os.sep
@@ -2727,6 +2773,12 @@ def correct_annotation(filename):
2727
2773
  def save_widget():
2728
2774
  return export_labels()
2729
2775
 
2776
+ if filename.endswith("_labelled.tif"):
2777
+ filename = filename.replace("_labelled.tif",".tif")
2778
+ if filename.endswith(".json"):
2779
+ filename = filename.replace('.json',".tif")
2780
+ assert os.path.exists(filename),f"Image {filename} does not seem to exist..."
2781
+
2730
2782
  img = imread(filename.replace('\\','/'))
2731
2783
  if img.ndim==3:
2732
2784
  img = np.moveaxis(img, 0, -1)
@@ -2791,7 +2843,7 @@ def _view_on_napari(tracks=None, stack=None, labels=None):
2791
2843
  ... 'x': [10, 20, 30], 'y': [15, 25, 35]})
2792
2844
  >>> stack = np.random.rand(100, 100, 3)
2793
2845
  >>> labels = np.random.randint(0, 2, (100, 100))
2794
- >>> view_on_napari(tracks, stack=stack, labels=labels)
2846
+ >>> _view_on_napari(tracks, stack=stack, labels=labels)
2795
2847
  # Visualize tracks, stack, and labels using Napari.
2796
2848
 
2797
2849
  """
@@ -2864,22 +2916,6 @@ def get_segmentation_models_list(mode='targets', return_path=False):
2864
2916
  else:
2865
2917
  repository_models = get_zenodo_files(cat=os.sep.join(["models", f"segmentation_{mode}"]))
2866
2918
 
2867
- # if mode == 'targets':
2868
- # modelpath = os.sep.join(
2869
- # [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
2870
- # "segmentation_targets", os.sep])
2871
- # repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_targets"]))
2872
- # elif mode == 'effectors':
2873
- # modelpath = os.sep.join(
2874
- # [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
2875
- # "segmentation_effectors", os.sep])
2876
- # repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_effectors"]))
2877
- # elif mode == 'generic':
2878
- # modelpath = os.sep.join(
2879
- # [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
2880
- # "segmentation_generic", os.sep])
2881
- # repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_generic"]))
2882
-
2883
2919
  available_models = natsorted(glob(modelpath + '*/'))
2884
2920
  available_models = [m.replace('\\', '/').split('/')[-2] for m in available_models]
2885
2921
 
@@ -3199,7 +3235,7 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
3199
3235
  subframe = frame.copy()
3200
3236
 
3201
3237
  if values is not None:
3202
- mi = values[0];
3238
+ mi = values[0]
3203
3239
  ma = values[1]
3204
3240
  else:
3205
3241
  mi = np.nanpercentile(subframe.flatten(), percentiles[0], keepdims=True)
@@ -3220,9 +3256,14 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
3220
3256
  return frame.copy().astype(dtype)
3221
3257
 
3222
3258
 
3223
- def normalize_multichannel(multichannel_frame, percentiles=None,
3224
- values=None, ignore_gray_value=0., clip=False,
3225
- amplification=None, dtype=float):
3259
+ def normalize_multichannel(multichannel_frame: np.ndarray,
3260
+ percentiles=None,
3261
+ values=None,
3262
+ ignore_gray_value=0.,
3263
+ clip=False,
3264
+ amplification=None,
3265
+ dtype=float,
3266
+ ):
3226
3267
 
3227
3268
  """
3228
3269
  Normalizes a multichannel frame by adjusting the intensity values of each channel based on specified percentiles,
@@ -3271,13 +3312,11 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
3271
3312
  Examples
3272
3313
  --------
3273
3314
  >>> multichannel_frame = np.random.rand(100, 100, 3) # Example multichannel frame
3274
- >>> normalized_frame = normalize_multichannel(multichannel_frame, percentiles=((1, 99), (2, 98), (0, 100)))
3315
+ >>> normalized_frame = normalize_multichannel(multichannel_frame, percentiles=[(1, 99), (2, 98), (0, 100)])
3275
3316
  # Normalizes each channel of the frame using specified percentile ranges.
3276
3317
 
3277
3318
  """
3278
3319
 
3279
-
3280
-
3281
3320
  mf = multichannel_frame.copy().astype(float)
3282
3321
  assert mf.ndim == 3, f'Wrong shape for the multichannel frame: {mf.shape}.'
3283
3322
  if percentiles is None:
@@ -3371,6 +3410,10 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=np
3371
3410
  print(
3372
3411
  f'Error in loading the frame {img_nums} {e}. Please check that the experiment channel information is consistent with the movie being read.')
3373
3412
  return None
3413
+ try:
3414
+ frames[np.isinf(frames)] = np.nan
3415
+ except Exception as e:
3416
+ print(e)
3374
3417
 
3375
3418
  frames = _rearrange_multichannel_frame(frames)
3376
3419