celldetective 1.3.7.post1__py3-none-any.whl → 1.3.8__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 (31) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/gui/btrack_options.py +8 -8
  3. celldetective/gui/classifier_widget.py +8 -0
  4. celldetective/gui/configure_new_exp.py +1 -1
  5. celldetective/gui/json_readers.py +2 -4
  6. celldetective/gui/plot_signals_ui.py +38 -29
  7. celldetective/gui/process_block.py +1 -0
  8. celldetective/gui/processes/downloader.py +108 -0
  9. celldetective/gui/processes/measure_cells.py +346 -0
  10. celldetective/gui/processes/segment_cells.py +354 -0
  11. celldetective/gui/processes/track_cells.py +298 -0
  12. celldetective/gui/processes/train_segmentation_model.py +270 -0
  13. celldetective/gui/processes/train_signal_model.py +108 -0
  14. celldetective/gui/seg_model_loader.py +71 -25
  15. celldetective/gui/signal_annotator2.py +10 -7
  16. celldetective/gui/signal_annotator_options.py +1 -1
  17. celldetective/gui/tableUI.py +252 -20
  18. celldetective/gui/viewers.py +1 -1
  19. celldetective/io.py +53 -20
  20. celldetective/measure.py +12 -144
  21. celldetective/relative_measurements.py +40 -43
  22. celldetective/segmentation.py +48 -1
  23. celldetective/signals.py +84 -305
  24. celldetective/tracking.py +23 -24
  25. celldetective/utils.py +1 -1
  26. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/METADATA +11 -2
  27. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/RECORD +31 -25
  28. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/WHEEL +1 -1
  29. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/LICENSE +0 -0
  30. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/entry_points.txt +0 -0
  31. {celldetective-1.3.7.post1.dist-info → celldetective-1.3.8.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from PyQt5.QtWidgets import QWidget, QGridLayout, QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, QRadioButton, QFileDialog, QPushButton, QMessageBox
1
+ from PyQt5.QtWidgets import QWidget, QGridLayout, QComboBox, QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, QRadioButton, QFileDialog, QPushButton, QMessageBox
2
2
  from PyQt5.QtCore import Qt, QSize
3
3
  from celldetective.gui.gui_utils import center_window
4
4
  from celldetective.gui.layouts import ChannelNormGenerator
@@ -79,13 +79,24 @@ class SegmentationModelLoader(QWidget, Styles):
79
79
  self.layout.addWidget(self.open_dialog_button, 9, 0, 1, 1)
80
80
  self.layout.addWidget(self.file_label, 9, 1, 1, 1)
81
81
 
82
+
83
+ self.merge_lbl = QLabel('Merging option: ')
84
+ self.merge_cb = QComboBox()
85
+ self.merge_cb.addItems(['OR'])
86
+ merge_hbox = QHBoxLayout()
87
+ merge_hbox.addWidget(self.merge_lbl, 33)
88
+ merge_hbox.addWidget(self.merge_cb, 66)
89
+ self.layout.addLayout(merge_hbox, 10, 0, 1, 2)
90
+ self.merge_lbl.hide()
91
+ self.merge_cb.hide()
92
+
82
93
  self.upload_button = QPushButton("Upload")
83
94
  self.upload_button.clicked.connect(self.upload_model)
84
95
  self.upload_button.setIcon(icon(MDI6.upload,color="white"))
85
96
  self.upload_button.setIconSize(QSize(25, 25))
86
97
  self.upload_button.setStyleSheet(self.button_style_sheet)
87
98
  self.upload_button.setEnabled(False)
88
- self.layout.addWidget(self.upload_button, 10, 0, 1, 1)
99
+ self.layout.addWidget(self.upload_button, 11, 0, 1, 1)
89
100
 
90
101
  self.base_block_options = [self.calibration_label, self.spatial_calib_le,*self.channel_layout.channel_cbs,*self.channel_layout.channel_labels,*self.channel_layout.normalization_mode_btns, *self.channel_layout.normalization_clip_btns, *self.channel_layout.normalization_min_value_lbl,
91
102
  *self.channel_layout.normalization_min_value_le, *self.channel_layout.normalization_max_value_lbl,*self.channel_layout.normalization_max_value_le,
@@ -207,41 +218,73 @@ class SegmentationModelLoader(QWidget, Styles):
207
218
  self.file_dialog.setFileMode(QFileDialog.ExistingFile)
208
219
 
209
220
  # If accepted check validity of data
210
- if self.file_dialog.exec_() == QFileDialog.Accepted:
211
- self.filename = self.file_dialog.selectedFiles()[0]
212
- if self.seg_mode=="stardist":
213
- subfiles = glob(self.filename+"/*")
214
- subfiles = [s.replace('\\','/') for s in subfiles]
215
- if self.filename+"/thresholds.json" in subfiles:
221
+ if self.seg_mode!='threshold':
222
+ if self.file_dialog.exec_() == QFileDialog.Accepted:
223
+ self.filename = self.file_dialog.selectedFiles()[0]
224
+ if self.seg_mode=="stardist":
225
+ subfiles = glob(self.filename+"/*")
226
+ subfiles = [s.replace('\\','/') for s in subfiles]
227
+ if self.filename+"/thresholds.json" in subfiles:
228
+ self.file_label.setText(self.filename.split("/")[-1])
229
+ self.modelname = self.filename.split("/")[-1]
230
+ self.destination = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
231
+ self.folder_dest = self.destination
232
+ else:
233
+ msgBox = QMessageBox()
234
+ msgBox.setIcon(QMessageBox.Warning)
235
+ msgBox.setText("StarDist model not recognized... Please ensure that it contains a thresholds.json file or that it is a valid StarDist model...")
236
+ msgBox.setWindowTitle("Warning")
237
+ msgBox.setStandardButtons(QMessageBox.Ok)
238
+ returnValue = msgBox.exec()
239
+ if returnValue == QMessageBox.Ok:
240
+ return None
241
+
242
+ if self.seg_mode=="cellpose":
216
243
  self.file_label.setText(self.filename.split("/")[-1])
217
244
  self.modelname = self.filename.split("/")[-1]
218
- self.destination = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
219
- self.folder_dest = self.destination
245
+ print(f"Transferring Cellpose model {self.filename}...")
246
+ self.folder_dest = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
247
+ self.destination = self.folder_dest+f"/{self.modelname}"
248
+
249
+ else:
250
+ self.filename, _ = QFileDialog.getOpenFileNames(
251
+ None,
252
+ "Load threshold configuration(s)...",
253
+ "",
254
+ "Json Configs (*.json)",
255
+ )
256
+ if self.filename:
257
+ n_files = len(self.filename)
258
+ print(f'You loaded {n_files} threshold configuration files...')
259
+ for i,filename in enumerate(self.filename):
260
+ print(f"Config {i}: ",filename,"...")
261
+
262
+ if n_files==1:
263
+ self.merge_cb.hide()
264
+ self.merge_lbl.hide()
265
+ self.file_label.setText(self.filename[0].split("/")[-1])
220
266
  else:
221
267
  msgBox = QMessageBox()
222
268
  msgBox.setIcon(QMessageBox.Warning)
223
- msgBox.setText("StarDist model not recognized... Please ensure that it contains a thresholds.json file or that it is a valid StarDist model...")
269
+ msgBox.setText("You selected more than one pipeline. Please set a merging procedure for the resulting masks...")
224
270
  msgBox.setWindowTitle("Warning")
225
271
  msgBox.setStandardButtons(QMessageBox.Ok)
226
272
  returnValue = msgBox.exec()
227
273
  if returnValue == QMessageBox.Ok:
228
- return None
229
-
230
- if self.seg_mode=="cellpose":
231
- self.file_label.setText(self.filename.split("/")[-1])
232
- self.modelname = self.filename.split("/")[-1]
233
- print(f"Transferring Cellpose model {self.filename}...")
234
- self.folder_dest = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
235
- self.destination = self.folder_dest+f"/{self.modelname}"
274
+ pass
236
275
 
237
- if self.seg_mode=="threshold":
238
- self.file_label.setText(self.filename.split("/")[-1])
276
+ self.merge_cb.show()
277
+ self.merge_lbl.show()
278
+ self.file_label.setText(f"{n_files} configs loaded...")
239
279
 
240
280
  def show_seg_options(self):
241
281
 
242
282
  """
243
283
  Show the relevant widgets, mask the others.
244
284
  """
285
+
286
+ self.filename = None
287
+ self.file_label.setText('No file chosen')
245
288
  self.base_block_options = [self.calibration_label, self.spatial_calib_le,*self.channel_layout.channel_cbs,*self.channel_layout.channel_labels,*self.channel_layout.normalization_mode_btns, *self.channel_layout.normalization_clip_btns, *self.channel_layout.normalization_min_value_lbl,
246
289
  *self.channel_layout.normalization_min_value_le, *self.channel_layout.normalization_max_value_lbl,*self.channel_layout.normalization_max_value_le,
247
290
  self.channel_layout.add_col_btn]
@@ -250,7 +293,7 @@ class SegmentationModelLoader(QWidget, Styles):
250
293
  self.spatial_calib_le.setToolTip('Cellpose rescales the images such that the cells are 30.0 pixels. You can compute the scale from the training data as\n(pixel calibration [µm] in training images)*(cell diameter [px] in training images)/(30 [px]).\nIf you pass images with a different calibration to the model, they will be rescaled automatically.\nThe rescaling is ignored if you pass a diameter different from 30 px below.')
251
294
  self.channel_layout.channel_labels[0].setText('cyto: ')
252
295
  self.channel_layout.channel_labels[1].setText('nuclei: ')
253
- for c in [self.threshold_config_button]:
296
+ for c in [self.threshold_config_button, self.merge_lbl, self.merge_cb]:
254
297
  c.hide()
255
298
  for c in self.cellpose_options+self.base_block_options:
256
299
  c.show()
@@ -261,7 +304,7 @@ class SegmentationModelLoader(QWidget, Styles):
261
304
  self.channel_layout.channel_labels[1].setText('channel 2: ')
262
305
  for c in self.base_block_options:
263
306
  c.show()
264
- for c in self.cellpose_options+[self.threshold_config_button]:
307
+ for c in self.cellpose_options+[self.threshold_config_button, self.merge_lbl, self.merge_cb]:
265
308
  c.hide()
266
309
  self.unlock_upload()
267
310
  else:
@@ -346,7 +389,10 @@ class SegmentationModelLoader(QWidget, Styles):
346
389
  self.parent_window.init_seg_model_list()
347
390
  self.close()
348
391
  else:
349
- if self.mode=="targets":
392
+ if not isinstance(self.filename, list):
393
+ if not self.filename is None:
394
+ self.filename = [self.filename]
395
+ if self.mode=="targets":
350
396
  self.parent_window.threshold_config_targets = self.filename
351
397
  self.parent_window.seg_model_list.setCurrentText('Threshold')
352
398
  print('Path to the traditional segmentation pipeline successfully set in celldetective...')
@@ -355,7 +401,7 @@ class SegmentationModelLoader(QWidget, Styles):
355
401
  self.parent_window.threshold_config_effectors = self.filename
356
402
  self.parent_window.seg_model_list.setCurrentText('Threshold')
357
403
  print('Path to the traditional segmentation pipeline successfully set in celldetective...')
358
- self.close()
404
+ self.close()
359
405
 
360
406
  def generate_input_config(self):
361
407
 
@@ -1577,13 +1577,16 @@ class SignalAnnotator2(QMainWindow,Styles):
1577
1577
  self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
1578
1578
 
1579
1579
  if self.reference_track_of_interest is not None and self.neighbor_track_of_interest is not None:
1580
- t0 = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID'] == self.neighbor_track_of_interest)&(self.df_relative['reference_population'] == self.reference_population)&(self.df_relative['neighbor_population'] == self.neighbor_population), self.pair_time_name].dropna().to_numpy()
1581
- if t0!=[]:
1582
- t0=t0[0]
1583
- ymin,ymax = self.cell_ax.get_ylim()
1584
- self.line_dt.set_xdata([t0, t0])
1585
- self.line_dt.set_ydata([ymin,ymax])
1586
-
1580
+ t0 = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID'] == self.neighbor_track_of_interest)&(self.df_relative['reference_population'] == self.reference_population)&(self.df_relative['neighbor_population'] == self.neighbor_population), self.pair_time_name].dropna().values
1581
+ try:
1582
+ if t0!=[]:
1583
+ t0=t0[0]
1584
+ ymin,ymax = self.cell_ax.get_ylim()
1585
+ self.line_dt.set_xdata([t0, t0])
1586
+ self.line_dt.set_ydata([ymin,ymax])
1587
+ except Exception as e:
1588
+ print(e)
1589
+
1587
1590
  self.cell_ax.legend()
1588
1591
  self.cell_fcanvas.canvas.draw()
1589
1592
 
@@ -34,7 +34,7 @@ class ConfigSignalAnnotator(QMainWindow, Styles):
34
34
  self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_targets.json"
35
35
  elif self.mode=="effectors":
36
36
  self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_effectors.json"
37
- elif self.mode == "neighborhood":
37
+ elif self.mode == "pairs":
38
38
  self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_neighborhood.json"
39
39
 
40
40
  exp_config = self.exp_dir +"config.ini"
@@ -166,7 +166,7 @@ class DifferentiateColWidget(QWidget, Styles):
166
166
  layout.addLayout(measurement_layout)
167
167
 
168
168
  self.window_size_slider = QLabeledSlider()
169
- self.window_size_slider.setRange(1,np.nanmax(self.parent_window.data.FRAME.to_numpy()))
169
+ self.window_size_slider.setRange(1,int(np.nanmax(self.parent_window.data.FRAME.to_numpy())))
170
170
  self.window_size_slider.setValue(3)
171
171
  window_layout = QHBoxLayout()
172
172
  window_layout.addWidget(QLabel('window size: '), 25)
@@ -215,6 +215,108 @@ class DifferentiateColWidget(QWidget, Styles):
215
215
  self.parent_window.table_view.setModel(self.parent_window.model)
216
216
  self.close()
217
217
 
218
+
219
+
220
+ class OperationOnColsWidget(QWidget, Styles):
221
+
222
+ def __init__(self, parent_window, column1=None, column2=None, operation='divide'):
223
+
224
+ super().__init__()
225
+ self.parent_window = parent_window
226
+ self.column1 = column1
227
+ self.column2 = column2
228
+ self.operation = operation
229
+
230
+ self.setWindowTitle(self.operation)
231
+ # Create the QComboBox and add some items
232
+ center_window(self)
233
+
234
+ layout = QVBoxLayout(self)
235
+ layout.setContentsMargins(30,30,30,30)
236
+
237
+ self.col1_cb = QComboBox()
238
+ self.col1_cb.addItems(list(self.parent_window.data.columns))
239
+ if self.column1 is not None:
240
+ idx = self.col1_cb.findText(self.column1)
241
+ self.col1_cb.setCurrentIndex(idx)
242
+
243
+ numerator_layout = QHBoxLayout()
244
+ numerator_layout.addWidget(QLabel('column 1: '), 25)
245
+ numerator_layout.addWidget(self.col1_cb, 75)
246
+ layout.addLayout(numerator_layout)
247
+
248
+ self.col2_cb = QComboBox()
249
+ self.col2_cb.addItems(list(self.parent_window.data.columns))
250
+ if self.column2 is not None:
251
+ idx = self.col2_cb.findText(self.column2)
252
+ self.col2_cb.setCurrentIndex(idx)
253
+
254
+ denominator_layout = QHBoxLayout()
255
+ denominator_layout.addWidget(QLabel('column 2: '), 25)
256
+ denominator_layout.addWidget(self.col2_cb, 75)
257
+ layout.addLayout(denominator_layout)
258
+
259
+ self.submit_btn = QPushButton('Compute')
260
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
261
+ self.submit_btn.clicked.connect(self.compute)
262
+ layout.addWidget(self.submit_btn, 30)
263
+
264
+ self.setAttribute(Qt.WA_DeleteOnClose)
265
+
266
+ def compute(self):
267
+
268
+ test = self._check_cols_before_operation()
269
+ if not test:
270
+ msgBox = QMessageBox()
271
+ msgBox.setIcon(QMessageBox.Warning)
272
+ msgBox.setText(f"Operation could not be performed, one of the column types is object...")
273
+ msgBox.setWindowTitle("Warning")
274
+ msgBox.setStandardButtons(QMessageBox.Ok)
275
+ returnValue = msgBox.exec()
276
+ if returnValue == QMessageBox.Ok:
277
+ return None
278
+ else:
279
+ return None
280
+ else:
281
+ if self.operation=='divide':
282
+ name = f"{self.col1_txt}/{self.col2_txt}"
283
+ with np.errstate(divide='ignore', invalid='ignore'):
284
+ res = np.true_divide(self.col1, self.col2)
285
+ res[res == np.inf] = np.nan
286
+ res[self.col1!=self.col1] = np.nan
287
+ res[self.col2!=self.col2] = np.nan
288
+ self.parent_window.data[name] = res
289
+
290
+ elif self.operation=='multiply':
291
+ name = f"{self.col1_txt}*{self.col2_txt}"
292
+ res = np.multiply(self.col1, self.col2)
293
+
294
+ elif self.operation=='add':
295
+ name = f"{self.col1_txt}+{self.col2_txt}"
296
+ res = np.add(self.col1, self.col2)
297
+
298
+ elif self.operation=='subtract':
299
+ name = f"{self.col1_txt}-{self.col2_txt}"
300
+ res = np.subtract(self.col1, self.col2)
301
+
302
+ self.parent_window.data[name] = res
303
+ self.parent_window.model = PandasModel(self.parent_window.data)
304
+ self.parent_window.table_view.setModel(self.parent_window.model)
305
+ self.close()
306
+
307
+ def _check_cols_before_operation(self):
308
+
309
+ self.col1_txt = self.col1_cb.currentText()
310
+ self.col2_txt = self.col2_cb.currentText()
311
+
312
+ self.col1 = self.parent_window.data[self.col1_txt].to_numpy()
313
+ self.col2 = self.parent_window.data[self.col2_txt].to_numpy()
314
+
315
+ test = np.all([self.col1.dtype!='O', self.col2.dtype!='O'])
316
+
317
+ return test
318
+
319
+
218
320
  class CalibrateColWidget(GenericOpColWidget):
219
321
 
220
322
  def __init__(self, *args, **kwargs):
@@ -563,12 +665,34 @@ class TableUI(QMainWindow, Styles):
563
665
  self.log_action = QAction('&Log (decimal)...', self)
564
666
  self.log_action.triggered.connect(self.take_log_of_selected_feature)
565
667
  #self.derivative_action.setShortcut("Ctrl+D")
566
- self.mathMenu.addAction(self.log_action)
668
+ self.mathMenu.addAction(self.log_action)
669
+
670
+
671
+ self.divide_action = QAction('&Divide...', self)
672
+ self.divide_action.triggered.connect(self.divide_signals)
673
+ #self.derivative_action.setShortcut("Ctrl+D")
674
+ self.mathMenu.addAction(self.divide_action)
675
+
676
+ self.multiply_action = QAction('&Multiply...', self)
677
+ self.multiply_action.triggered.connect(self.multiply_signals)
678
+ #self.derivative_action.setShortcut("Ctrl+D")
679
+ self.mathMenu.addAction(self.multiply_action)
680
+
681
+ self.add_action = QAction('&Add...', self)
682
+ self.add_action.triggered.connect(self.add_signals)
683
+ #self.derivative_action.setShortcut("Ctrl+D")
684
+ self.mathMenu.addAction(self.add_action)
685
+
686
+ self.subtract_action = QAction('&Subtract...', self)
687
+ self.subtract_action.triggered.connect(self.subtract_signals)
688
+ #self.derivative_action.setShortcut("Ctrl+D")
689
+ self.mathMenu.addAction(self.subtract_action)
567
690
 
568
- self.onehot_action = QAction('&One hot to categorical...', self)
569
- self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
570
- #self.onehot_action.setShortcut("Ctrl+D")
571
- self.mathMenu.addAction(self.onehot_action)
691
+
692
+ # self.onehot_action = QAction('&One hot to categorical...', self)
693
+ # self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
694
+ # #self.onehot_action.setShortcut("Ctrl+D")
695
+ # self.mathMenu.addAction(self.onehot_action)
572
696
 
573
697
  def collapse_pairs_in_neigh(self):
574
698
 
@@ -734,6 +858,96 @@ class TableUI(QMainWindow, Styles):
734
858
  pos_group.to_csv(pos[0]+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
735
859
  print("Done...")
736
860
 
861
+ def divide_signals(self):
862
+
863
+ x = self.table_view.selectedIndexes()
864
+ col_idx = np.unique(np.array([l.column() for l in x]))
865
+ if isinstance(col_idx, (list, np.ndarray)):
866
+ cols = np.array(list(self.data.columns))
867
+ if len(col_idx)>0:
868
+ selected_col1 = str(cols[col_idx[0]])
869
+ if len(col_idx)>1:
870
+ selected_col2 = str(cols[col_idx[1]])
871
+ else:
872
+ selected_col2 = None
873
+ else:
874
+ selected_col1 = None
875
+ selected_col2 = None
876
+ else:
877
+ selected_col1 = None
878
+ selected_col2 = None
879
+
880
+ self.divWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='divide')
881
+ self.divWidget.show()
882
+
883
+
884
+ def multiply_signals(self):
885
+
886
+ x = self.table_view.selectedIndexes()
887
+ col_idx = np.unique(np.array([l.column() for l in x]))
888
+ if isinstance(col_idx, (list, np.ndarray)):
889
+ cols = np.array(list(self.data.columns))
890
+ if len(col_idx)>0:
891
+ selected_col1 = str(cols[col_idx[0]])
892
+ if len(col_idx)>1:
893
+ selected_col2 = str(cols[col_idx[1]])
894
+ else:
895
+ selected_col2 = None
896
+ else:
897
+ selected_col1 = None
898
+ selected_col2 = None
899
+ else:
900
+ selected_col1 = None
901
+ selected_col2 = None
902
+
903
+ self.mulWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='multiply')
904
+ self.mulWidget.show()
905
+
906
+ def add_signals(self):
907
+
908
+ x = self.table_view.selectedIndexes()
909
+ col_idx = np.unique(np.array([l.column() for l in x]))
910
+ if isinstance(col_idx, (list, np.ndarray)):
911
+ cols = np.array(list(self.data.columns))
912
+ if len(col_idx)>0:
913
+ selected_col1 = str(cols[col_idx[0]])
914
+ if len(col_idx)>1:
915
+ selected_col2 = str(cols[col_idx[1]])
916
+ else:
917
+ selected_col2 = None
918
+ else:
919
+ selected_col1 = None
920
+ selected_col2 = None
921
+ else:
922
+ selected_col1 = None
923
+ selected_col2 = None
924
+
925
+ self.addiWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='add')
926
+ self.addiWidget.show()
927
+
928
+ def subtract_signals(self):
929
+
930
+ x = self.table_view.selectedIndexes()
931
+ col_idx = np.unique(np.array([l.column() for l in x]))
932
+ if isinstance(col_idx, (list, np.ndarray)):
933
+ cols = np.array(list(self.data.columns))
934
+ if len(col_idx)>0:
935
+ selected_col1 = str(cols[col_idx[0]])
936
+ if len(col_idx)>1:
937
+ selected_col2 = str(cols[col_idx[1]])
938
+ else:
939
+ selected_col2 = None
940
+ else:
941
+ selected_col1 = None
942
+ selected_col2 = None
943
+ else:
944
+ selected_col1 = None
945
+ selected_col2 = None
946
+
947
+ self.subWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='subtract')
948
+ self.subWidget.show()
949
+
950
+
737
951
  def differenciate_selected_feature(self):
738
952
 
739
953
  # check only one col selected and assert is numerical
@@ -742,9 +956,12 @@ class TableUI(QMainWindow, Styles):
742
956
 
743
957
  x = self.table_view.selectedIndexes()
744
958
  col_idx = np.unique(np.array([l.column() for l in x]))
745
- if col_idx!=0:
959
+ if isinstance(col_idx, (list, np.ndarray)):
746
960
  cols = np.array(list(self.data.columns))
747
- selected_col = str(cols[col_idx][0])
961
+ if len(col_idx)>0:
962
+ selected_col = str(cols[col_idx[0]])
963
+ else:
964
+ selected_col = None
748
965
  else:
749
966
  selected_col = None
750
967
 
@@ -759,9 +976,12 @@ class TableUI(QMainWindow, Styles):
759
976
 
760
977
  x = self.table_view.selectedIndexes()
761
978
  col_idx = np.unique(np.array([l.column() for l in x]))
762
- if col_idx!=0:
979
+ if isinstance(col_idx, (list, np.ndarray)):
763
980
  cols = np.array(list(self.data.columns))
764
- selected_col = str(cols[col_idx][0])
981
+ if len(col_idx)>0:
982
+ selected_col = str(cols[col_idx[0]])
983
+ else:
984
+ selected_col = None
765
985
  else:
766
986
  selected_col = None
767
987
 
@@ -772,9 +992,12 @@ class TableUI(QMainWindow, Styles):
772
992
 
773
993
  x = self.table_view.selectedIndexes()
774
994
  col_idx = np.unique(np.array([l.column() for l in x]))
775
- if col_idx!=0:
995
+ if isinstance(col_idx, (list, np.ndarray)):
776
996
  cols = np.array(list(self.data.columns))
777
- selected_col = str(cols[col_idx][0])
997
+ if len(col_idx)>0:
998
+ selected_col = str(cols[col_idx[0]])
999
+ else:
1000
+ selected_col = None
778
1001
  else:
779
1002
  selected_col = None
780
1003
 
@@ -790,9 +1013,12 @@ class TableUI(QMainWindow, Styles):
790
1013
 
791
1014
  x = self.table_view.selectedIndexes()
792
1015
  col_idx = np.unique(np.array([l.column() for l in x]))
793
- if col_idx!=0:
1016
+ if isinstance(col_idx, (list, np.ndarray)):
794
1017
  cols = np.array(list(self.data.columns))
795
- selected_col = str(cols[col_idx][0])
1018
+ if len(col_idx)>0:
1019
+ selected_col = str(cols[col_idx[0]])
1020
+ else:
1021
+ selected_col = None
796
1022
  else:
797
1023
  selected_col = None
798
1024
 
@@ -804,11 +1030,14 @@ class TableUI(QMainWindow, Styles):
804
1030
 
805
1031
  x = self.table_view.selectedIndexes()
806
1032
  col_idx = np.unique(np.array([l.column() for l in x]))
807
- if list(col_idx):
1033
+ if isinstance(col_idx, (list, np.ndarray)):
808
1034
  cols = np.array(list(self.data.columns))
809
- selected_cols = cols[col_idx]
1035
+ if len(col_idx)>0:
1036
+ selected_col = str(cols[col_idx[0]])
1037
+ else:
1038
+ selected_col = None
810
1039
  else:
811
- selected_cols = None
1040
+ selected_col = None
812
1041
 
813
1042
  self.mergewidget = MergeOneHotWidget(self, selected_columns=selected_cols)
814
1043
  self.mergewidget.show()
@@ -874,7 +1103,7 @@ class TableUI(QMainWindow, Styles):
874
1103
  self.projection_option.setChecked(True)
875
1104
  self.projection_option.toggled.connect(self.enable_projection_options)
876
1105
  self.projection_op_cb = QComboBox()
877
- self.projection_op_cb.addItems(['mean','median','min','max', 'prod', 'sum'])
1106
+ self.projection_op_cb.addItems(['mean','median','min','max','first','last','prod','sum'])
878
1107
 
879
1108
  projection_layout = QHBoxLayout()
880
1109
  projection_layout.addWidget(self.projection_option, 33)
@@ -1044,8 +1273,11 @@ class TableUI(QMainWindow, Styles):
1044
1273
  all_cms = list(colormaps)
1045
1274
  for cm in all_cms:
1046
1275
  if hasattr(matplotlib.cm, str(cm).lower()):
1047
- self.cmap_cb.addColormap(cm.lower())
1048
-
1276
+ try:
1277
+ self.cmap_cb.addColormap(cm.lower())
1278
+ except:
1279
+ pass
1280
+
1049
1281
  hbox = QHBoxLayout()
1050
1282
  hbox.addWidget(QLabel('colormap: '), 33)
1051
1283
  hbox.addWidget(self.cmap_cb, 66)
@@ -932,7 +932,7 @@ class CellSizeViewer(StackVisualizer):
932
932
  with interactive sliders for diameter adjustment and circle display.
933
933
  """
934
934
 
935
- def __init__(self, initial_diameter=40, set_radius_in_list=False, diameter_slider_range=(0,200), parent_le=None, parent_list_widget=None, *args, **kwargs):
935
+ def __init__(self, initial_diameter=40, set_radius_in_list=False, diameter_slider_range=(0,500), parent_le=None, parent_list_widget=None, *args, **kwargs):
936
936
  # Initialize the widget and its attributes
937
937
 
938
938
  super().__init__(*args, **kwargs)