celldetective 1.3.0.post1__py3-none-any.whl → 1.3.2__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 (36) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/events.py +88 -11
  3. celldetective/extra_properties.py +5 -1
  4. celldetective/gui/InitWindow.py +35 -9
  5. celldetective/gui/classifier_widget.py +99 -23
  6. celldetective/gui/control_panel.py +7 -1
  7. celldetective/gui/generic_signal_plot.py +161 -2
  8. celldetective/gui/gui_utils.py +90 -1
  9. celldetective/gui/layouts.py +128 -7
  10. celldetective/gui/measurement_options.py +3 -3
  11. celldetective/gui/plot_signals_ui.py +8 -3
  12. celldetective/gui/process_block.py +77 -32
  13. celldetective/gui/retrain_segmentation_model_options.py +24 -10
  14. celldetective/gui/signal_annotator.py +53 -26
  15. celldetective/gui/signal_annotator2.py +17 -30
  16. celldetective/gui/survival_ui.py +24 -3
  17. celldetective/gui/tableUI.py +300 -183
  18. celldetective/gui/viewers.py +263 -3
  19. celldetective/io.py +56 -3
  20. celldetective/links/zenodo.json +136 -123
  21. celldetective/measure.py +3 -0
  22. celldetective/models/tracking_configs/biased_motion.json +68 -0
  23. celldetective/models/tracking_configs/no_z_motion.json +202 -0
  24. celldetective/neighborhood.py +154 -69
  25. celldetective/preprocessing.py +172 -3
  26. celldetective/relative_measurements.py +128 -4
  27. celldetective/scripts/measure_cells.py +3 -3
  28. celldetective/signals.py +212 -215
  29. celldetective/tracking.py +7 -3
  30. celldetective/utils.py +22 -6
  31. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/METADATA +3 -3
  32. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/RECORD +36 -34
  33. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/WHEEL +1 -1
  34. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/LICENSE +0 -0
  35. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/entry_points.txt +0 -0
  36. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/top_level.txt +0 -0
@@ -11,9 +11,10 @@ from natsort import natsorted
11
11
  from glob import glob
12
12
  import os
13
13
 
14
- from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget
14
+ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
15
15
  from PyQt5.QtCore import Qt, QSize
16
- from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout
16
+ from PyQt5.QtGui import QKeySequence
17
+ from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit
17
18
  from celldetective.gui import Styles
18
19
  from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
19
20
  from superqt.fonticon import icon
@@ -21,6 +22,7 @@ from fonticon_mdi6 import MDI6
21
22
  from matplotlib_scalebar.scalebar import ScaleBar
22
23
  import gc
23
24
  from celldetective.utils import mask_edges
25
+ from scipy.ndimage import shift
24
26
 
25
27
  class StackVisualizer(QWidget, Styles):
26
28
 
@@ -739,4 +741,262 @@ class CellSizeViewer(StackVisualizer):
739
741
 
740
742
  self.diameter = value
741
743
  self.circ.set_radius(self.diameter//2)
742
- self.canvas.canvas.draw_idle()
744
+ self.canvas.canvas.draw_idle()
745
+
746
+
747
+ class ChannelOffsetViewer(StackVisualizer):
748
+
749
+ def __init__(self, parent_window=None, *args, **kwargs):
750
+
751
+ self.parent_window = parent_window
752
+ self.overlay_target_channel = -1
753
+ self.shift_vertical = 0
754
+ self.shift_horizontal = 0
755
+ super().__init__(*args, **kwargs)
756
+
757
+ self.load_stack()
758
+ self.canvas.layout.addWidget(QHSeperationLine())
759
+
760
+ self.generate_overlay_channel_cb()
761
+ self.generate_overlay_imshow()
762
+
763
+ self.generate_overlay_alpha_slider()
764
+ self.generate_overlay_contrast_slider()
765
+
766
+ self.generate_overlay_shift()
767
+ self.generate_add_to_parent_btn()
768
+
769
+ if self.overlay_target_channel==-1:
770
+ index = len(self.channel_names) -1
771
+ else:
772
+ index = self.overlay_target_channel
773
+ self.channels_overlay_cb.setCurrentIndex(index)
774
+ self.frame_slider.valueChanged.connect(self.change_overlay_frame)
775
+
776
+ self.define_keyboard_shortcuts()
777
+
778
+ self.channels_overlay_cb.setCurrentIndex(self.parent_window.channels_cb.currentIndex())
779
+ self.set_channel_index(0)
780
+
781
+ self.setAttribute(Qt.WA_DeleteOnClose)
782
+
783
+ def generate_overlay_imshow(self):
784
+ self.im_overlay = self.ax.imshow(self.overlay_init_frame, cmap='Blues', interpolation='none',alpha=0.5, **self.imshow_kwargs)
785
+
786
+ def generate_overlay_alpha_slider(self):
787
+ # Generate the contrast slider if enabled
788
+
789
+ self.overlay_alpha_slider = QLabeledDoubleSlider()
790
+ alpha_layout = QuickSliderLayout(
791
+ label='Overlay\ntransparency: ',
792
+ slider=self.overlay_alpha_slider,
793
+ slider_initial_value=0.5,
794
+ slider_range=(0,1.0),
795
+ decimal_option=True,
796
+ precision=1.0E-05,
797
+ )
798
+ alpha_layout.setContentsMargins(15,0,15,0)
799
+ self.overlay_alpha_slider.valueChanged.connect(self.change_alpha_overlay)
800
+ self.canvas.layout.addLayout(alpha_layout)
801
+
802
+
803
+ def generate_overlay_contrast_slider(self):
804
+ # Generate the contrast slider if enabled
805
+
806
+ self.overlay_contrast_slider = QLabeledDoubleRangeSlider()
807
+ contrast_layout = QuickSliderLayout(
808
+ label='Overlay contrast: ',
809
+ slider=self.overlay_contrast_slider,
810
+ slider_initial_value=[np.nanpercentile(self.overlay_init_frame, 0.1),np.nanpercentile(self.overlay_init_frame, 99.99)],
811
+ slider_range=(np.nanmin(self.overlay_init_frame),np.nanmax(self.overlay_init_frame)),
812
+ decimal_option=True,
813
+ precision=1.0E-05,
814
+ )
815
+ contrast_layout.setContentsMargins(15,0,15,0)
816
+ self.im_overlay.set_clim(vmin=np.nanpercentile(self.overlay_init_frame, 0.1),vmax=np.nanpercentile(self.overlay_init_frame, 99.99))
817
+ self.overlay_contrast_slider.valueChanged.connect(self.change_contrast_overlay)
818
+ self.canvas.layout.addLayout(contrast_layout)
819
+
820
+ def set_overlay_channel_index(self, value):
821
+ # Set the channel index based on dropdown value
822
+
823
+ self.overlay_target_channel = value
824
+ self.overlay_init_contrast = True
825
+ if self.mode == 'direct':
826
+ self.overlay_last_frame = self.stack[-1,:,:,self.overlay_target_channel]
827
+ elif self.mode == 'virtual':
828
+ self.overlay_last_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.stack_length-1],
829
+ self.stack_path,
830
+ normalize_input=False).astype(float)[:,:,0]
831
+ self.change_overlay_frame(self.frame_slider.value())
832
+ self.overlay_init_contrast = False
833
+
834
+ def generate_overlay_channel_cb(self):
835
+
836
+ assert self.channel_names is not None
837
+ assert len(self.channel_names)==self.n_channels
838
+
839
+ channel_layout = QHBoxLayout()
840
+ channel_layout.setContentsMargins(15,0,15,0)
841
+ channel_layout.addWidget(QLabel('Overlay channel: '), 25)
842
+
843
+ self.channels_overlay_cb = QComboBox()
844
+ self.channels_overlay_cb.addItems(self.channel_names)
845
+ self.channels_overlay_cb.currentIndexChanged.connect(self.set_overlay_channel_index)
846
+ channel_layout.addWidget(self.channels_overlay_cb, 75)
847
+ self.canvas.layout.addLayout(channel_layout)
848
+
849
+ def generate_overlay_shift(self):
850
+
851
+ shift_layout = QHBoxLayout()
852
+ shift_layout.setContentsMargins(15,0,15,0)
853
+ shift_layout.addWidget(QLabel('shift (h): '), 20, alignment=Qt.AlignRight)
854
+
855
+ self.apply_shift_btn = QPushButton('Apply')
856
+ self.apply_shift_btn.setStyleSheet(self.button_style_sheet_2)
857
+ self.apply_shift_btn.setToolTip('Apply the shift to the overlay channel.')
858
+ self.apply_shift_btn.clicked.connect(self.shift_generic)
859
+
860
+ self.set_shift_btn = QPushButton('Set')
861
+
862
+ self.horizontal_shift_le = ThresholdLineEdit(init_value=self.shift_horizontal, connected_buttons=[self.apply_shift_btn, self.set_shift_btn],placeholder='horizontal shift [pixels]', value_type='float')
863
+ shift_layout.addWidget(self.horizontal_shift_le, 20)
864
+
865
+ shift_layout.addWidget(QLabel('shift (v): '), 20, alignment=Qt.AlignRight)
866
+
867
+ self.vertical_shift_le = ThresholdLineEdit(init_value=self.shift_vertical, connected_buttons=[self.apply_shift_btn, self.set_shift_btn],placeholder='vertical shift [pixels]', value_type='float')
868
+ shift_layout.addWidget(self.vertical_shift_le, 20)
869
+
870
+ shift_layout.addWidget(self.apply_shift_btn, 20)
871
+
872
+ self.canvas.layout.addLayout(shift_layout)
873
+
874
+
875
+ def change_overlay_frame(self, value):
876
+ # Change the displayed frame based on slider value
877
+
878
+ if self.mode=='virtual':
879
+
880
+ self.overlay_init_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, value],
881
+ self.stack_path,
882
+ normalize_input=False
883
+ ).astype(float)[:,:,0]
884
+ elif self.mode=='direct':
885
+ self.overlay_init_frame = self.stack[value,:,:,self.overlay_target_channel].copy()
886
+
887
+ self.im_overlay.set_data(self.overlay_init_frame)
888
+
889
+ if self.overlay_init_contrast:
890
+ self.im_overlay.autoscale()
891
+ I_min, I_max = self.im_overlay.get_clim()
892
+ self.overlay_contrast_slider.setRange(np.nanmin([self.overlay_init_frame,self.overlay_last_frame]),np.nanmax([self.overlay_init_frame,self.overlay_last_frame]))
893
+ self.overlay_contrast_slider.setValue((I_min,I_max))
894
+
895
+ if self.create_contrast_slider:
896
+ self.change_contrast_overlay(self.overlay_contrast_slider.value())
897
+
898
+ def locate_image_virtual(self):
899
+ # Locate the stack of images if provided as a file
900
+ self.stack_length = auto_load_number_of_frames(self.stack_path)
901
+ if self.stack_length is None:
902
+ stack = imread(self.stack_path)
903
+ self.stack_length = len(stack)
904
+ del stack
905
+ gc.collect()
906
+
907
+ self.mid_time = self.stack_length // 2
908
+ self.img_num_per_channel = _get_img_num_per_channel(np.arange(self.n_channels), self.stack_length, self.n_channels)
909
+
910
+ self.init_frame = load_frames(self.img_num_per_channel[self.target_channel, self.mid_time],
911
+ self.stack_path,
912
+ normalize_input=False).astype(float)[:,:,0]
913
+ self.last_frame = load_frames(self.img_num_per_channel[self.target_channel, self.stack_length-1],
914
+ self.stack_path,
915
+ normalize_input=False).astype(float)[:,:,0]
916
+ self.overlay_init_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.mid_time],
917
+ self.stack_path,
918
+ normalize_input=False).astype(float)[:,:,0]
919
+ self.overlay_last_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.stack_length-1],
920
+ self.stack_path,
921
+ normalize_input=False).astype(float)[:,:,0]
922
+
923
+ def change_contrast_overlay(self, value):
924
+ # Change contrast based on slider value
925
+
926
+ vmin = value[0]
927
+ vmax = value[1]
928
+ self.im_overlay.set_clim(vmin=vmin, vmax=vmax)
929
+ self.fig.canvas.draw_idle()
930
+
931
+ def change_alpha_overlay(self, value):
932
+ # Change contrast based on slider value
933
+
934
+ alpha = value
935
+ self.im_overlay.set_alpha(alpha)
936
+ self.fig.canvas.draw_idle()
937
+
938
+
939
+ def define_keyboard_shortcuts(self):
940
+
941
+ self.shift_up_shortcut = QShortcut(QKeySequence(Qt.Key_Up), self.canvas)
942
+ self.shift_up_shortcut.activated.connect(self.shift_overlay_up)
943
+
944
+ self.shift_down_shortcut = QShortcut(QKeySequence(Qt.Key_Down), self.canvas)
945
+ self.shift_down_shortcut.activated.connect(self.shift_overlay_down)
946
+
947
+ self.shift_left_shortcut = QShortcut(QKeySequence(Qt.Key_Left), self.canvas)
948
+ self.shift_left_shortcut.activated.connect(self.shift_overlay_left)
949
+
950
+ self.shift_right_shortcut = QShortcut(QKeySequence(Qt.Key_Right), self.canvas)
951
+ self.shift_right_shortcut.activated.connect(self.shift_overlay_right)
952
+
953
+
954
+ def shift_overlay_up(self):
955
+ self.shift_vertical -= 2
956
+ self.vertical_shift_le.set_threshold(self.shift_vertical)
957
+ #self.shift_generic()
958
+ self.apply_shift_btn.click()
959
+
960
+ def shift_overlay_down(self):
961
+ self.shift_vertical += 2
962
+ self.vertical_shift_le.set_threshold(self.shift_vertical)
963
+ #self.shift_generic()
964
+ self.apply_shift_btn.click()
965
+
966
+ def shift_overlay_left(self):
967
+ self.shift_horizontal -= 2
968
+ self.horizontal_shift_le.set_threshold(self.shift_horizontal)
969
+ #self.shift_generic()
970
+ self.apply_shift_btn.click()
971
+
972
+ def shift_overlay_right(self):
973
+ self.shift_horizontal += 2
974
+ self.horizontal_shift_le.set_threshold(self.shift_horizontal)
975
+ #self.shift_generic()
976
+ self.apply_shift_btn.click()
977
+
978
+ def shift_generic(self):
979
+ self.shift_vertical = self.vertical_shift_le.get_threshold()
980
+ self.shift_horizontal = self.horizontal_shift_le.get_threshold()
981
+ self.shifted_frame = shift(self.overlay_init_frame, [self.shift_vertical, self.shift_horizontal],prefilter=False)
982
+ self.im_overlay.set_data(self.shifted_frame)
983
+ self.fig.canvas.draw_idle()
984
+
985
+ def generate_add_to_parent_btn(self):
986
+
987
+ add_hbox = QHBoxLayout()
988
+ add_hbox.setContentsMargins(0,5,0,5)
989
+ self.set_shift_btn.clicked.connect(self.set_parent_attributes)
990
+ self.set_shift_btn.setStyleSheet(self.button_style_sheet)
991
+ add_hbox.addWidget(QLabel(''),33)
992
+ add_hbox.addWidget(self.set_shift_btn, 33)
993
+ add_hbox.addWidget(QLabel(''),33)
994
+ self.canvas.layout.addLayout(add_hbox)
995
+
996
+ def set_parent_attributes(self):
997
+
998
+ idx = self.channels_overlay_cb.currentIndex()
999
+ self.parent_window.channels_cb.setCurrentIndex(idx)
1000
+ self.parent_window.vertical_shift_le.set_threshold(self.vertical_shift_le.get_threshold())
1001
+ self.parent_window.horizontal_shift_le.set_threshold(self.horizontal_shift_le.get_threshold())
1002
+ self.close()
celldetective/io.py CHANGED
@@ -24,6 +24,54 @@ import concurrent.futures
24
24
  from tifffile import imwrite
25
25
  from stardist import fill_label_holes
26
26
 
27
+ def extract_experiment_from_well(well_path):
28
+ if not well_path.endswith(os.sep):
29
+ well_path += os.sep
30
+ exp_path_blocks = well_path.split(os.sep)[:-2]
31
+ experiment = os.sep.join(exp_path_blocks)
32
+ return experiment
33
+
34
+ def extract_well_from_position(pos_path):
35
+ if not pos_path.endswith(os.sep):
36
+ pos_path += os.sep
37
+ well_path_blocks = pos_path.split(os.sep)[:-2]
38
+ well_path = os.sep.join(well_path_blocks)+os.sep
39
+ return well_path
40
+
41
+ def extract_experiment_from_position(pos_path):
42
+ if not pos_path.endswith(os.sep):
43
+ pos_path += os.sep
44
+ exp_path_blocks = pos_path.split(os.sep)[:-3]
45
+ experiment = os.sep.join(exp_path_blocks)
46
+ return experiment
47
+
48
+ def collect_experiment_metadata(pos_path=None, well_path=None):
49
+
50
+ if pos_path is not None:
51
+ if not pos_path.endswith(os.sep):
52
+ pos_path += os.sep
53
+ experiment = extract_experiment_from_position(pos_path)
54
+ well_path = extract_well_from_position(pos_path)
55
+ elif well_path is not None:
56
+ if not well_path.endswith(os.sep):
57
+ well_path += os.sep
58
+ experiment = extract_experiment_from_well(well_path)
59
+
60
+ wells = list(get_experiment_wells(experiment))
61
+ idx = wells.index(well_path)
62
+ well_name, well_nbr = extract_well_name_and_number(well_path)
63
+ if pos_path is not None:
64
+ pos_name = extract_position_name(pos_path)
65
+ else:
66
+ pos_name = 0
67
+ concentrations = get_experiment_concentrations(experiment, dtype=float)
68
+ cell_types = get_experiment_cell_types(experiment)
69
+ antibodies = get_experiment_antibodies(experiment)
70
+ pharmaceutical_agents = get_experiment_pharmaceutical_agents(experiment)
71
+
72
+ return {"pos_path": pos_path, "pos_name": pos_name, "well_path": well_path, "well_name": well_name, "well_nbr": well_nbr, "experiment": experiment, "antibody": antibodies[idx], "concentration": concentrations[idx], "cell_type": cell_types[idx], "pharmaceutical_agent": pharmaceutical_agents[idx]}
73
+
74
+
27
75
  def get_experiment_wells(experiment):
28
76
 
29
77
  """
@@ -712,12 +760,17 @@ def fix_missing_labels(position, population='target', prefix='Aligned'):
712
760
 
713
761
  if population.lower() == "target" or population.lower() == "targets":
714
762
  label_path = natsorted(glob(position + os.sep.join(["labels_targets", "*.tif"])))
763
+ path = position + os.sep + "labels_targets"
715
764
  elif population.lower() == "effector" or population.lower() == "effectors":
716
765
  label_path = natsorted(glob(position + os.sep.join(["labels_effectors", "*.tif"])))
766
+ path = position + os.sep + "labels_effectors"
717
767
 
718
- path = os.path.split(label_path[0])[0]
719
- int_valid = [int(lbl.split(os.sep)[-1].split('.')[0]) for lbl in label_path]
720
- to_create = [x for x in all_frames if x not in int_valid]
768
+ if label_path!=[]:
769
+ #path = os.path.split(label_path[0])[0]
770
+ int_valid = [int(lbl.split(os.sep)[-1].split('.')[0]) for lbl in label_path]
771
+ to_create = [x for x in all_frames if x not in int_valid]
772
+ else:
773
+ to_create = all_frames
721
774
  to_create = [str(x).zfill(4)+'.tif' for x in to_create]
722
775
  for file in to_create:
723
776
  imwrite(os.sep.join([path, file]), template)