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
|
@@ -263,8 +263,7 @@ class ConfigMeasurementsPlot(CelldetectiveWidget):
|
|
|
263
263
|
def ask_for_feature(self):
|
|
264
264
|
|
|
265
265
|
cols = np.array(list(self.df.columns))
|
|
266
|
-
|
|
267
|
-
feats = cols[is_number(self.df.dtypes)]
|
|
266
|
+
feats = [c for c in cols if pd.api.types.is_numeric_dtype(self.df[c])]
|
|
268
267
|
|
|
269
268
|
self.feature_choice_widget = CelldetectiveWidget()
|
|
270
269
|
self.feature_choice_widget.setWindowTitle("Select numeric feature")
|
|
@@ -286,8 +285,7 @@ class ConfigMeasurementsPlot(CelldetectiveWidget):
|
|
|
286
285
|
def ask_for_features(self):
|
|
287
286
|
|
|
288
287
|
cols = np.array(list(self.df.columns))
|
|
289
|
-
|
|
290
|
-
feats = cols[is_number(self.df.dtypes)]
|
|
288
|
+
feats = [c for c in cols if pd.api.types.is_numeric_dtype(self.df[c])]
|
|
291
289
|
|
|
292
290
|
self.feature_choice_widget = CelldetectiveWidget()
|
|
293
291
|
self.feature_choice_widget.setWindowTitle("Select numeric feature")
|
|
@@ -21,6 +21,7 @@ from celldetective.utils.data_cleaning import extract_cols_from_table_list
|
|
|
21
21
|
from celldetective.utils.parsing import _extract_labels_from_config
|
|
22
22
|
from celldetective.utils.data_loaders import load_experiment_tables
|
|
23
23
|
from celldetective.signals import mean_signal
|
|
24
|
+
import pandas as pd
|
|
24
25
|
import numpy as np
|
|
25
26
|
import os
|
|
26
27
|
import matplotlib.pyplot as plt
|
|
@@ -377,8 +378,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
|
|
|
377
378
|
def ask_for_feature(self):
|
|
378
379
|
|
|
379
380
|
cols = np.array(list(self.df.columns))
|
|
380
|
-
|
|
381
|
-
feats = cols[is_number(self.df.dtypes)]
|
|
381
|
+
feats = [c for c in cols if pd.api.types.is_numeric_dtype(self.df[c])]
|
|
382
382
|
|
|
383
383
|
self.feature_choice_widget = CelldetectiveWidget()
|
|
384
384
|
self.feature_choice_widget.setWindowTitle("Select numeric feature")
|
|
@@ -400,8 +400,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
|
|
|
400
400
|
def ask_for_features(self):
|
|
401
401
|
|
|
402
402
|
cols = np.array(list(self.df.columns))
|
|
403
|
-
|
|
404
|
-
feats = cols[is_number(self.df.dtypes)]
|
|
403
|
+
feats = [c for c in cols if pd.api.types.is_numeric_dtype(self.df[c])]
|
|
405
404
|
|
|
406
405
|
self.feature_choice_widget = CelldetectiveWidget()
|
|
407
406
|
self.feature_choice_widget.setWindowTitle("Select numeric feature")
|
|
@@ -9,6 +9,7 @@ from PyQt5.QtWidgets import (
|
|
|
9
9
|
QHBoxLayout,
|
|
10
10
|
QCheckBox,
|
|
11
11
|
QMessageBox,
|
|
12
|
+
QApplication,
|
|
12
13
|
)
|
|
13
14
|
from PyQt5.QtCore import Qt, QSize, QTimer, QThread, pyqtSignal
|
|
14
15
|
from superqt.fonticon import icon
|
|
@@ -30,13 +31,14 @@ from celldetective.gui.base.components import (
|
|
|
30
31
|
CelldetectiveWidget,
|
|
31
32
|
CelldetectiveProgressDialog,
|
|
32
33
|
QHSeperationLine,
|
|
34
|
+
HoverButton,
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
import numpy as np
|
|
36
38
|
from glob import glob
|
|
37
39
|
from celldetective import get_logger
|
|
38
40
|
|
|
39
|
-
logger = get_logger()
|
|
41
|
+
logger = get_logger("celldetective")
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class NapariLoaderThread(QThread):
|
|
@@ -345,11 +347,12 @@ class ProcessPanel(QFrame, Styles):
|
|
|
345
347
|
self.refresh_signal_models()
|
|
346
348
|
# self.to_disable.append(self.cell_models_list)
|
|
347
349
|
|
|
348
|
-
self.train_signal_model_btn =
|
|
350
|
+
self.train_signal_model_btn = HoverButton(
|
|
351
|
+
"TRAIN", MDI6.redo_variant, "black", "white"
|
|
352
|
+
)
|
|
349
353
|
self.train_signal_model_btn.setToolTip(
|
|
350
354
|
"Train or retrain an event detection model\non newly annotated data."
|
|
351
355
|
)
|
|
352
|
-
self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
353
356
|
self.train_signal_model_btn.setIconSize(QSize(20, 20))
|
|
354
357
|
self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
355
358
|
model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
|
|
@@ -557,8 +560,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
557
560
|
self.seg_model_list.setGeometry(50, 50, 200, 30)
|
|
558
561
|
self.init_seg_model_list()
|
|
559
562
|
|
|
560
|
-
self.upload_model_btn =
|
|
561
|
-
self.upload_model_btn.setIcon(icon(MDI6.upload, color="black"))
|
|
563
|
+
self.upload_model_btn = HoverButton("UPLOAD", MDI6.upload, "black", "white")
|
|
562
564
|
self.upload_model_btn.setIconSize(QSize(20, 20))
|
|
563
565
|
self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
564
566
|
self.upload_model_btn.setToolTip(
|
|
@@ -568,11 +570,10 @@ class ProcessPanel(QFrame, Styles):
|
|
|
568
570
|
self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
|
|
569
571
|
# self.to_disable.append(self.upload_tc_model)
|
|
570
572
|
|
|
571
|
-
self.train_btn =
|
|
573
|
+
self.train_btn = HoverButton("TRAIN", MDI6.redo_variant, "black", "white")
|
|
572
574
|
self.train_btn.setToolTip(
|
|
573
575
|
"Train or retrain a segmentation model\non newly annotated data."
|
|
574
576
|
)
|
|
575
|
-
self.train_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
576
577
|
self.train_btn.setIconSize(QSize(20, 20))
|
|
577
578
|
self.train_btn.setStyleSheet(self.button_style_sheet_3)
|
|
578
579
|
self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
|
|
@@ -819,58 +820,117 @@ class ProcessPanel(QFrame, Styles):
|
|
|
819
820
|
|
|
820
821
|
def check_signals(self):
|
|
821
822
|
from celldetective.gui.event_annotator import EventAnnotator, StackLoaderThread
|
|
823
|
+
from celldetective.utils.experiment import interpret_wells_and_positions
|
|
822
824
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
self.event_annotator = EventAnnotator(self, lazy_load=True)
|
|
825
|
+
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
826
|
+
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
826
827
|
|
|
827
|
-
|
|
828
|
-
|
|
828
|
+
# Count selected positions
|
|
829
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
830
|
+
self.exp_dir, self.well_option, self.position_option
|
|
831
|
+
)
|
|
832
|
+
total_positions = 0
|
|
833
|
+
from celldetective.utils.experiment import (
|
|
834
|
+
get_positions_in_well,
|
|
835
|
+
get_experiment_wells,
|
|
836
|
+
)
|
|
829
837
|
|
|
830
|
-
|
|
838
|
+
wells = get_experiment_wells(self.exp_dir)
|
|
839
|
+
for widx in well_indices:
|
|
840
|
+
positions = get_positions_in_well(wells[widx])
|
|
841
|
+
if position_indices is not None:
|
|
842
|
+
total_positions += len(position_indices)
|
|
843
|
+
else:
|
|
844
|
+
total_positions += len(positions)
|
|
831
845
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
846
|
+
if total_positions == 1:
|
|
847
|
+
test = self.parent_window.locate_selected_position()
|
|
848
|
+
if test:
|
|
849
|
+
self.event_annotator = EventAnnotator(self, lazy_load=True)
|
|
835
850
|
|
|
836
|
-
|
|
851
|
+
if not getattr(self.event_annotator, "proceed", True):
|
|
852
|
+
return
|
|
837
853
|
|
|
838
|
-
|
|
839
|
-
self.signal_loader.status_update.connect(self.signal_progress.setLabelText)
|
|
840
|
-
self.signal_progress.canceled.connect(self.signal_loader.stop)
|
|
854
|
+
self.signal_loader = StackLoaderThread(self.event_annotator)
|
|
841
855
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
856
|
+
self.signal_progress = CelldetectiveProgressDialog(
|
|
857
|
+
"Loading data...",
|
|
858
|
+
"Cancel",
|
|
859
|
+
0,
|
|
860
|
+
100,
|
|
861
|
+
self,
|
|
862
|
+
window_title="Please wait",
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
self.signal_progress.setValue(0)
|
|
866
|
+
|
|
867
|
+
self.signal_loader.progress.connect(self.signal_progress.setValue)
|
|
868
|
+
self.signal_loader.status_update.connect(
|
|
869
|
+
self.signal_progress.setLabelText
|
|
870
|
+
)
|
|
871
|
+
self.signal_progress.canceled.connect(self.signal_loader.stop)
|
|
872
|
+
|
|
873
|
+
def on_finished():
|
|
874
|
+
self.signal_progress.blockSignals(True)
|
|
875
|
+
self.signal_progress.close()
|
|
876
|
+
if not self.signal_loader._is_cancelled:
|
|
849
877
|
try:
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
878
|
+
self.event_annotator.finalize_init()
|
|
879
|
+
self.event_annotator.show()
|
|
880
|
+
try:
|
|
881
|
+
QTimer.singleShot(
|
|
882
|
+
100,
|
|
883
|
+
lambda: self.event_annotator.resize(
|
|
884
|
+
self.event_annotator.width() + 1,
|
|
885
|
+
self.event_annotator.height() + 1,
|
|
886
|
+
),
|
|
887
|
+
)
|
|
888
|
+
except:
|
|
889
|
+
pass
|
|
890
|
+
except Exception as e:
|
|
891
|
+
print(f"Error finalizing annotator: {e}")
|
|
892
|
+
else:
|
|
893
|
+
self.event_annotator.close()
|
|
863
894
|
|
|
864
|
-
|
|
865
|
-
|
|
895
|
+
self.signal_loader.finished.connect(on_finished)
|
|
896
|
+
self.signal_loader.start()
|
|
897
|
+
else:
|
|
898
|
+
# Multi position explorer: redirect to TableUI with progress bar
|
|
899
|
+
self.view_table_ui()
|
|
866
900
|
|
|
867
901
|
def check_measurements(self):
|
|
868
|
-
from celldetective.gui.
|
|
902
|
+
from celldetective.gui.measure_annotator import MeasureAnnotator
|
|
903
|
+
from celldetective.utils.experiment import interpret_wells_and_positions
|
|
869
904
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
905
|
+
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
906
|
+
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
907
|
+
|
|
908
|
+
# Count selected positions
|
|
909
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
910
|
+
self.exp_dir, self.well_option, self.position_option
|
|
911
|
+
)
|
|
912
|
+
total_positions = 0
|
|
913
|
+
from celldetective.utils.experiment import (
|
|
914
|
+
get_positions_in_well,
|
|
915
|
+
get_experiment_wells,
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
wells = get_experiment_wells(self.exp_dir)
|
|
919
|
+
for widx in well_indices:
|
|
920
|
+
positions = get_positions_in_well(wells[widx])
|
|
921
|
+
if position_indices is not None:
|
|
922
|
+
total_positions += len(position_indices)
|
|
923
|
+
else:
|
|
924
|
+
total_positions += len(positions)
|
|
925
|
+
|
|
926
|
+
if total_positions == 1:
|
|
927
|
+
test = self.parent_window.locate_selected_position()
|
|
928
|
+
if test:
|
|
929
|
+
self.measure_annotator = MeasureAnnotator(self)
|
|
930
|
+
self.measure_annotator.show()
|
|
931
|
+
else:
|
|
932
|
+
# Multi position explorer: redirect to TableUI with progress bar
|
|
933
|
+
self.view_table_ui()
|
|
874
934
|
|
|
875
935
|
def enable_segmentation_model_list(self):
|
|
876
936
|
if self.segment_action.isChecked():
|
|
@@ -1493,6 +1553,9 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1493
1553
|
window_title="Preparing the napari viewer...",
|
|
1494
1554
|
)
|
|
1495
1555
|
|
|
1556
|
+
self.napari_progress.setAutoClose(False)
|
|
1557
|
+
self.napari_progress.setAutoReset(False)
|
|
1558
|
+
|
|
1496
1559
|
self.napari_progress.setValue(0)
|
|
1497
1560
|
self.napari_loader.progress.connect(self.napari_progress.setValue)
|
|
1498
1561
|
self.napari_loader.status.connect(self.napari_progress.setLabelText)
|
|
@@ -1502,13 +1565,15 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1502
1565
|
from celldetective.napari.utils import launch_napari_viewer
|
|
1503
1566
|
|
|
1504
1567
|
self.napari_progress.blockSignals(True)
|
|
1505
|
-
self.napari_progress.close()
|
|
1568
|
+
# self.napari_progress.close()
|
|
1506
1569
|
if self.napari_loader._is_cancelled:
|
|
1507
1570
|
logger.info("Task was cancelled...")
|
|
1571
|
+
self.napari_progress.close()
|
|
1508
1572
|
return
|
|
1509
1573
|
|
|
1510
1574
|
if isinstance(result, Exception):
|
|
1511
1575
|
logger.error(f"napari loading error: {result}")
|
|
1576
|
+
self.napari_progress.close()
|
|
1512
1577
|
msgBox = QMessageBox()
|
|
1513
1578
|
msgBox.setIcon(QMessageBox.Warning)
|
|
1514
1579
|
msgBox.setText(str(result))
|
|
@@ -1519,13 +1584,33 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1519
1584
|
|
|
1520
1585
|
if result:
|
|
1521
1586
|
logger.info("Launching the napari viewer with tracks...")
|
|
1587
|
+
self.napari_progress.setLabelText("Initializing Napari viewer...")
|
|
1588
|
+
self.napari_progress.setRange(0, 0)
|
|
1589
|
+
QApplication.processEvents()
|
|
1590
|
+
|
|
1591
|
+
def progress_cb(msg):
|
|
1592
|
+
if isinstance(msg, str):
|
|
1593
|
+
self.napari_progress.setLabelText(msg)
|
|
1594
|
+
QApplication.processEvents()
|
|
1595
|
+
|
|
1596
|
+
if "flush_memory" in result:
|
|
1597
|
+
result.pop("flush_memory")
|
|
1598
|
+
|
|
1522
1599
|
try:
|
|
1523
|
-
launch_napari_viewer(
|
|
1600
|
+
launch_napari_viewer(
|
|
1601
|
+
**result,
|
|
1602
|
+
block=False,
|
|
1603
|
+
flush_memory=False,
|
|
1604
|
+
progress_callback=progress_cb,
|
|
1605
|
+
)
|
|
1524
1606
|
logger.info("napari viewer was closed...")
|
|
1525
1607
|
except Exception as e:
|
|
1526
1608
|
logger.error(f"Failed to launch Napari: {e}")
|
|
1527
1609
|
QMessageBox.warning(self, "Error", f"Failed to launch Napari: {e}")
|
|
1610
|
+
finally:
|
|
1611
|
+
self.napari_progress.close()
|
|
1528
1612
|
else:
|
|
1613
|
+
self.napari_progress.close()
|
|
1529
1614
|
logger.warning(
|
|
1530
1615
|
"napari loading returned None (likely no trajectories found)."
|
|
1531
1616
|
)
|
|
@@ -1540,33 +1625,95 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1540
1625
|
|
|
1541
1626
|
def view_table_ui(self):
|
|
1542
1627
|
from celldetective.gui.tableUI import TableUI
|
|
1628
|
+
from celldetective.gui.workers import ProgressWindow
|
|
1629
|
+
from celldetective.processes.load_table import TableLoaderProcess
|
|
1630
|
+
from celldetective.utils.experiment import interpret_wells_and_positions
|
|
1543
1631
|
|
|
1544
1632
|
logger.info("Load table...")
|
|
1545
|
-
self.load_available_tables()
|
|
1546
1633
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1634
|
+
# Prepare args for the process
|
|
1635
|
+
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
1636
|
+
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
1637
|
+
|
|
1638
|
+
# Count selected positions
|
|
1639
|
+
well_indices, position_indices = interpret_wells_and_positions(
|
|
1640
|
+
self.exp_dir, self.well_option, self.position_option
|
|
1641
|
+
)
|
|
1642
|
+
total_positions = 0
|
|
1643
|
+
from celldetective.utils.experiment import (
|
|
1644
|
+
get_positions_in_well,
|
|
1645
|
+
get_experiment_wells,
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1648
|
+
wells = get_experiment_wells(self.exp_dir)
|
|
1649
|
+
for widx in well_indices:
|
|
1650
|
+
positions = get_positions_in_well(wells[widx])
|
|
1651
|
+
if position_indices is not None:
|
|
1652
|
+
total_positions += len(position_indices)
|
|
1653
|
+
else:
|
|
1654
|
+
total_positions += len(positions)
|
|
1655
|
+
|
|
1656
|
+
def show_table(df):
|
|
1657
|
+
if df is not None:
|
|
1658
|
+
plot_mode = "plot_track_signals"
|
|
1659
|
+
if "TRACK_ID" not in list(df.columns):
|
|
1660
|
+
plot_mode = "static"
|
|
1661
|
+
self.tab_ui = TableUI(
|
|
1662
|
+
df,
|
|
1663
|
+
f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}",
|
|
1664
|
+
population=self.mode,
|
|
1665
|
+
plot_mode=plot_mode,
|
|
1666
|
+
save_inplace_option=True,
|
|
1667
|
+
)
|
|
1668
|
+
self.tab_ui.show()
|
|
1669
|
+
center_window(self.tab_ui)
|
|
1670
|
+
else:
|
|
1671
|
+
logger.info("Table could not be loaded...")
|
|
1672
|
+
msgBox = QMessageBox()
|
|
1673
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1674
|
+
msgBox.setText("No table could be loaded...")
|
|
1675
|
+
msgBox.setWindowTitle("Info")
|
|
1676
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1677
|
+
msgBox.exec()
|
|
1678
|
+
|
|
1679
|
+
if total_positions == 1:
|
|
1680
|
+
# Synchronous load for single position
|
|
1681
|
+
from celldetective.utils.data_loaders import load_experiment_tables
|
|
1682
|
+
|
|
1683
|
+
df = load_experiment_tables(
|
|
1684
|
+
self.exp_dir,
|
|
1554
1685
|
population=self.mode,
|
|
1555
|
-
|
|
1556
|
-
|
|
1686
|
+
well_option=self.well_option,
|
|
1687
|
+
position_option=self.position_option,
|
|
1557
1688
|
)
|
|
1558
|
-
|
|
1559
|
-
center_window(self.tab_ui)
|
|
1689
|
+
show_table(df)
|
|
1560
1690
|
else:
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1691
|
+
# Asynchronous load for multiple positions
|
|
1692
|
+
process_args = {
|
|
1693
|
+
"experiment": self.exp_dir,
|
|
1694
|
+
"population": self.mode,
|
|
1695
|
+
"well_option": self.well_option,
|
|
1696
|
+
"position_option": self.position_option,
|
|
1697
|
+
"show_frame_progress": False,
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
self.df = None
|
|
1701
|
+
|
|
1702
|
+
def on_table_loaded(df):
|
|
1703
|
+
self.df = df
|
|
1704
|
+
show_table(self.df)
|
|
1705
|
+
|
|
1706
|
+
self.job = ProgressWindow(
|
|
1707
|
+
TableLoaderProcess,
|
|
1708
|
+
parent_window=self,
|
|
1709
|
+
title="Loading tables...",
|
|
1710
|
+
process_args=process_args,
|
|
1711
|
+
position_info=False,
|
|
1712
|
+
well_label="Wells loaded:",
|
|
1713
|
+
pos_label="Positions loaded:",
|
|
1714
|
+
)
|
|
1715
|
+
self.job._ProgressWindow__runner.signals.result.connect(on_table_loaded)
|
|
1716
|
+
self.job.exec_()
|
|
1570
1717
|
|
|
1571
1718
|
def load_available_tables(self):
|
|
1572
1719
|
"""
|
|
@@ -1587,8 +1734,87 @@ class ProcessPanel(QFrame, Styles):
|
|
|
1587
1734
|
self.signals = []
|
|
1588
1735
|
if self.df is not None:
|
|
1589
1736
|
self.signals = list(self.df.columns)
|
|
1590
|
-
|
|
1591
|
-
logger.info(
|
|
1737
|
+
else:
|
|
1738
|
+
logger.info(
|
|
1739
|
+
"No table could be found for the selected position(s)... Anticipating measurements..."
|
|
1740
|
+
)
|
|
1741
|
+
|
|
1742
|
+
from celldetective.utils.experiment import extract_experiment_channels
|
|
1743
|
+
|
|
1744
|
+
channel_names, _ = extract_experiment_channels(self.exp_dir)
|
|
1745
|
+
|
|
1746
|
+
# Standard measurements
|
|
1747
|
+
self.signals = ["area"]
|
|
1748
|
+
for ch in channel_names:
|
|
1749
|
+
self.signals.append(f"{ch}_mean")
|
|
1750
|
+
|
|
1751
|
+
# Anticipate from instructions
|
|
1752
|
+
instr_path = os.path.join(
|
|
1753
|
+
self.exp_dir, "configs", f"measurement_instructions_{self.mode}.json"
|
|
1754
|
+
)
|
|
1755
|
+
if os.path.exists(instr_path):
|
|
1756
|
+
try:
|
|
1757
|
+
with open(instr_path, "r") as f:
|
|
1758
|
+
instr = json.load(f)
|
|
1759
|
+
|
|
1760
|
+
# 1. Features
|
|
1761
|
+
features = instr.get("features", [])
|
|
1762
|
+
if features:
|
|
1763
|
+
for f_name in features:
|
|
1764
|
+
if f_name == "intensity_mean":
|
|
1765
|
+
continue # handled by standard
|
|
1766
|
+
if f_name == "area":
|
|
1767
|
+
continue
|
|
1768
|
+
|
|
1769
|
+
# For other features, skimage/celldetective might suffix them.
|
|
1770
|
+
# If it's a generic feature, skimage usually keeps the name.
|
|
1771
|
+
# If it's multichannel, it might need channel names.
|
|
1772
|
+
# For now, let's keep it simple as requested for intensity_mean and area.
|
|
1773
|
+
pass
|
|
1774
|
+
|
|
1775
|
+
# 2. Isotropic measurements
|
|
1776
|
+
radii = instr.get("intensity_measurement_radii", [])
|
|
1777
|
+
ops = instr.get("isotropic_operations", [])
|
|
1778
|
+
if radii and ops:
|
|
1779
|
+
for r in radii if isinstance(radii, list) else [radii]:
|
|
1780
|
+
for op in ops:
|
|
1781
|
+
for ch in channel_names:
|
|
1782
|
+
if isinstance(r, list):
|
|
1783
|
+
self.signals.append(
|
|
1784
|
+
f"{ch}_ring_{int(min(r))}_{int(max(r))}_{op}"
|
|
1785
|
+
)
|
|
1786
|
+
else:
|
|
1787
|
+
self.signals.append(
|
|
1788
|
+
f"{ch}_circle_{int(r)}_{op}"
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
# 3. Border distances
|
|
1792
|
+
borders = instr.get("border_distances", [])
|
|
1793
|
+
if borders:
|
|
1794
|
+
for b in borders if isinstance(borders, list) else [borders]:
|
|
1795
|
+
# Logic from measure.py for suffix
|
|
1796
|
+
b_str = (
|
|
1797
|
+
str(b)
|
|
1798
|
+
.replace("(", "")
|
|
1799
|
+
.replace(")", "")
|
|
1800
|
+
.replace(", ", "_")
|
|
1801
|
+
.replace(",", "_")
|
|
1802
|
+
)
|
|
1803
|
+
suffix = (
|
|
1804
|
+
f"_slice_{b_str.replace('-', 'm')}px"
|
|
1805
|
+
if ("-" in str(b) or "," in str(b))
|
|
1806
|
+
else f"_edge_{b_str}px"
|
|
1807
|
+
)
|
|
1808
|
+
for ch in channel_names:
|
|
1809
|
+
# In measure_features, it's {ch}_mean{suffix}
|
|
1810
|
+
self.signals.append(f"{ch}_mean{suffix}")
|
|
1811
|
+
|
|
1812
|
+
except Exception as e:
|
|
1813
|
+
logger.warning(f"Could not parse measurement instructions: {e}")
|
|
1814
|
+
|
|
1815
|
+
# Remove duplicates and keep order
|
|
1816
|
+
seen = set()
|
|
1817
|
+
self.signals = [x for x in self.signals if not (x in seen or seen.add(x))]
|
|
1592
1818
|
|
|
1593
1819
|
def set_cellpose_scale(self):
|
|
1594
1820
|
|