celldetective 1.5.0b3__py3-none-any.whl → 1.5.0b4__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "1.5.0b3"
1
+ __version__ = "1.5.0b4"
@@ -950,9 +950,9 @@ class MeasureAnnotator(BaseAnnotator):
950
950
  ].to_numpy()
951
951
  )
952
952
  self.colors.append(
953
- self.df_tracks.loc[
954
- self.df_tracks["FRAME"] == t, ["group_color"]
955
- ].to_numpy()
953
+ self.df_tracks.loc[self.df_tracks["FRAME"] == t, ["group_color"]]
954
+ .to_numpy()
955
+ .copy()
956
956
  )
957
957
  if "TRACK_ID" in self.df_tracks.columns:
958
958
  self.tracks.append(
@@ -84,6 +84,12 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
84
84
  self.bg_loader = BackgroundLoader()
85
85
  self.bg_loader.start()
86
86
 
87
+ def closeEvent(self, event):
88
+ if self.bg_loader.isRunning():
89
+ logger.info("Waiting for background loader to finish...")
90
+ self.bg_loader.wait()
91
+ super().closeEvent(event)
92
+
87
93
  def _add_to_layout(self):
88
94
 
89
95
  self._layout.addWidget(self.model_frame)
@@ -111,6 +111,11 @@ class StackLoader(QThread):
111
111
  # If nothing to load, wait
112
112
  self.mutex.lock()
113
113
  self.condition.wait(self.mutex, 500) # Wait 500ms or until new priority
114
+
115
+ if not self.running:
116
+ self.mutex.unlock()
117
+ break
118
+
114
119
  self.mutex.unlock()
115
120
 
116
121
 
@@ -113,13 +113,29 @@ def _prep_cellpose_model(
113
113
 
114
114
  from cellpose.models import CellposeModel
115
115
 
116
- model = CellposeModel(
117
- gpu=use_gpu,
118
- device=device,
119
- pretrained_model=path + model_name,
120
- model_type=None,
121
- nchan=n_channels,
122
- ) # diam_mean=30.0,
116
+ try:
117
+ model = CellposeModel(
118
+ gpu=use_gpu,
119
+ device=device,
120
+ pretrained_model=path + model_name,
121
+ model_type=None,
122
+ nchan=n_channels,
123
+ ) # diam_mean=30.0,
124
+ except AssertionError as e:
125
+ if use_gpu:
126
+ print(
127
+ f"[WARNING] Could not load Cellpose model with GPU ({e}). Retrying with CPU..."
128
+ )
129
+ device = torch.device("cpu")
130
+ model = CellposeModel(
131
+ gpu=False,
132
+ device=device,
133
+ pretrained_model=path + model_name,
134
+ model_type=None,
135
+ nchan=n_channels,
136
+ )
137
+ else:
138
+ raise e
123
139
  if scale is None:
124
140
  scale_model = model.diam_mean / model.diam_labels
125
141
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: celldetective
3
- Version: 1.5.0b3
3
+ Version: 1.5.0b4
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -1,6 +1,6 @@
1
1
  celldetective/__init__.py,sha256=LfnOyfUnYPGDc8xcsF_PfYEL7-CqAb7BMBPBIWGv84o,666
2
2
  celldetective/__main__.py,sha256=Rzzu9ArxZSgfBVjV-lyn-3oanQB2MumQR6itK5ZaRpA,2597
3
- celldetective/_version.py,sha256=xyBMhGEXnWbOInuedgW565vK5NILuhG2r2EYCw9E9eU,24
3
+ celldetective/_version.py,sha256=i0XQAcqdHCcwV-JF_A_FeVVfTjXROj_WnqM2Ktzzq1Q,24
4
4
  celldetective/events.py,sha256=n15R53c7QZ2wT8gjb0oeNikQbuRBrVVbyNsRCqXjzXA,8166
5
5
  celldetective/exceptions.py,sha256=f3VmIYOthWTiqMEV5xQCox2rw5c5e7yog88h-CcV4oI,356
6
6
  celldetective/extra_properties.py,sha256=s_2R4_El2p-gQNZ_EpgDxgrN3UnRitN7KDKHhyLuoHc,21681
@@ -30,7 +30,7 @@ celldetective/gui/gui_utils.py,sha256=t6SjEfjcaRH9a0TlbTGEiVRpCgocaCh4lgkIvRgRRw
30
30
  celldetective/gui/interactions_block.py,sha256=34VaHFrdKsq1hYuXrosmpP15JU26dSfbyx4lyt1jxNg,28440
31
31
  celldetective/gui/interactive_timeseries_viewer.py,sha256=u_amAhLdEHRpYSRwPDtVm5v-JZIz0ANTcG4YGksX1Vo,16079
32
32
  celldetective/gui/json_readers.py,sha256=t5rhtIxACj0pdwLrnPs-QjyhQo3P25UGWGgOCIBhQxs,4572
33
- celldetective/gui/measure_annotator.py,sha256=HdhrU3N207evB9s-pqZNfzJC2Jswx3yHdPPLR8CRILc,38354
33
+ celldetective/gui/measure_annotator.py,sha256=ljNbsKmFXQk0R9zEfBZ6XfBHzFmlL7Gt6QyPHyqh08g,38357
34
34
  celldetective/gui/pair_event_annotator.py,sha256=QJHaM-z0K2I36sLf6XCMFMymfw-nFhzfGJ8oxrMfZco,124091
35
35
  celldetective/gui/plot_measurements.py,sha256=a_Mks-5XUTn2QEYih0PlXGp2lX3C34zuhK9ozzE1guM,56593
36
36
  celldetective/gui/plot_signals_ui.py,sha256=9VmA1yaTcNf1jY7drtK41R1q87dhEk7bXBCq_cQ94fs,28133
@@ -76,7 +76,7 @@ celldetective/gui/settings/_settings_event_model_training.py,sha256=TVPnnYPmdAF6
76
76
  celldetective/gui/settings/_settings_measurements.py,sha256=nrVR1dG3B7iyJayRql1yierhKvgXZiu6qVtfkxI1ABA,47947
77
77
  celldetective/gui/settings/_settings_neighborhood.py,sha256=ws6H99bKU4NYd2IYyaJj7g9-MScr5W6UB2raP88ytfE,23767
78
78
  celldetective/gui/settings/_settings_segmentation.py,sha256=6DihD1mk-dN4Sstdth1iJ-0HR34rTVlTHP-pXUh_rY0,1901
79
- celldetective/gui/settings/_settings_segmentation_model_training.py,sha256=XxqSCxLHOvn1w8V8ZEEfPAvdwfS0IKz107ox4yll7AA,30229
79
+ celldetective/gui/settings/_settings_segmentation_model_training.py,sha256=6Ftih-_1VaICHBFLs6BPO8kf5ZDohwvDF4dEnhYC19M,30440
80
80
  celldetective/gui/settings/_settings_signal_annotator.py,sha256=Mvvre-yHJvRoR-slbkLNwEemuGgiMCQQ4H1BHjFk3r4,12412
81
81
  celldetective/gui/settings/_settings_tracking.py,sha256=5ZxJp3o3stD2NKdhqZofIgsUNp73oAN_pIi_bDFAd0Y,53293
82
82
  celldetective/gui/settings/_stardist_model_params.py,sha256=dEKhaLcJ4r8VxgBU2DI-hcTaTk5S181O-_CN0j7JSgE,4020
@@ -87,7 +87,7 @@ celldetective/gui/table_ops/_merge_one_hot.py,sha256=gKRgem-u_-JENkVkbjRobsH4TkS
87
87
  celldetective/gui/table_ops/_query_table.py,sha256=K-XHSZ1I4v2wwqWjyQAgyFRZJbem3CmTfHmO0vijh9g,1345
88
88
  celldetective/gui/table_ops/_rename_col.py,sha256=UAgDSpXJo_h4pLJpHaNc2w2VhbaW4D2JZTgJ3cYC4-g,1457
89
89
  celldetective/gui/viewers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- celldetective/gui/viewers/base_viewer.py,sha256=1U936rVnRqevXtyqlZnu_1o_VbWx3BJVofKh_oKG1jM,29857
90
+ celldetective/gui/viewers/base_viewer.py,sha256=gHe9QBX6Lrn567Kc-9T6KoGuLqEP_1q4RROFBd9qLWE,29962
91
91
  celldetective/gui/viewers/channel_offset_viewer.py,sha256=aQpxrJX9qM_xX848XnFrxrhTqE22DIshtD-uKL8e4gU,12423
92
92
  celldetective/gui/viewers/contour_viewer.py,sha256=riHj03LKXLoa-Ys2o2EhCE5nULfcHMohx9LFoXbI6zU,14720
93
93
  celldetective/gui/viewers/size_viewer.py,sha256=uXITjaxg5dhQ09Q6TNUxwLxi-ZglyGFcxEH1RtGIZWw,6020
@@ -161,13 +161,14 @@ celldetective/utils/parsing.py,sha256=1zpIH9tyULCRmO5Kwzy6yA01fqm5uE_mZKYtondy-V
161
161
  celldetective/utils/resources.py,sha256=3Fz_W0NYWl_Ixc2AjEmkOv5f7ejXerCLJ2z1iWhGWUI,1153
162
162
  celldetective/utils/stats.py,sha256=4TVHRqi38Y0sed-izaMI51sMP0fd5tC5M68EYyfJjkE,3636
163
163
  celldetective/utils/types.py,sha256=lRfWSMVzTkxgoctGGp0NqD551akuxu0ygna7zVGonTg,397
164
- celldetective/utils/cellpose_utils/__init__.py,sha256=grsglxR29vj42tlgsBoXnVnjrIJWsBXqbtkjFjzqH7A,4898
164
+ celldetective/utils/cellpose_utils/__init__.py,sha256=MeRDojrAkBSXe-wlLu5KIih5wXP9B2aPdP39JLYpoGE,5417
165
165
  celldetective/utils/event_detection/__init__.py,sha256=KX20PwPTevdbZ-25otDy_QTmealcDx5xNCfH2SOVIZM,323
166
166
  celldetective/utils/plots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
167
  celldetective/utils/plots/regression.py,sha256=oUCn29-hp7PxMqC-R0yoL60KMw5ZWpZAIoCDh2ErlcY,1764
168
168
  celldetective/utils/stardist_utils/__init__.py,sha256=e9s3DEaTKCUOGZb5k_DgShBTl4B0U-Jmg3Ioo8D5PyE,3978
169
- celldetective-1.5.0b3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
169
+ celldetective-1.5.0b4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
170
170
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
+ tests/test_cellpose_fallback.py,sha256=BJZTDFF8sFR1x7rDbvZQ2RQOB1OP6wuFBRfc8zbl5zw,3513
171
172
  tests/test_events.py,sha256=eLFwwEEJfQAdwhews3-fn1HSvzozcNNFN_Qn0gOvQkE,685
172
173
  tests/test_filters.py,sha256=uj4NVyUnKXa18EpTSiWCetGKI1VFopDyNSJSUxX44wA,1689
173
174
  tests/test_io.py,sha256=gk5FmoI7ANEczUtNXYRxc48KzkfYzemwS_eYaLq4_NI,2093
@@ -181,10 +182,11 @@ tests/test_tracking.py,sha256=_YLjwQ3EChQssoHDfEnUJ7fI1yC5KEcJsFnAVR64L70,18909
181
182
  tests/test_utils.py,sha256=aSB_eMw9fzTsnxxdYoNmdQQRrXkWqBXB7Uv4TGU6kYE,4778
182
183
  tests/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
183
184
  tests/gui/test_enhancements.py,sha256=3x9au_rkQtMZ94DRj3OaEHKPr511RrWqBAUAcNQn1ys,13453
185
+ tests/gui/test_measure_annotator_bugfix.py,sha256=tPfgWNKC0UkvrVssSrUcVDC1qgpzx6l2yCqvKtKYkM4,4544
184
186
  tests/gui/test_new_project.py,sha256=wRjW2vEaZb0LWT-f8G8-Ptk8CW9z8-FDPLpV5uqj6ck,8778
185
187
  tests/gui/test_project.py,sha256=KzAnodIc0Ovta0ARL5Kr5PkOR5euA6qczT_GhEZpyE4,4710
186
- celldetective-1.5.0b3.dist-info/METADATA,sha256=D4LkOtPQrdeuX4zW5_OKXlN1dxkxaZx_zqdILC36-Hk,10947
187
- celldetective-1.5.0b3.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
188
- celldetective-1.5.0b3.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
189
- celldetective-1.5.0b3.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
190
- celldetective-1.5.0b3.dist-info/RECORD,,
188
+ celldetective-1.5.0b4.dist-info/METADATA,sha256=VgzQFDS1ExiiQdY4MjqmWfrRTC-MJQR1meL7t0Q8eMY,10947
189
+ celldetective-1.5.0b4.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
190
+ celldetective-1.5.0b4.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
191
+ celldetective-1.5.0b4.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
192
+ celldetective-1.5.0b4.dist-info/RECORD,,
@@ -0,0 +1,130 @@
1
+ import pytest
2
+ import os
3
+ import pandas as pd
4
+ import numpy as np
5
+ import logging
6
+ from PyQt5 import QtCore
7
+ from celldetective.gui.InitWindow import AppInitWindow
8
+ from celldetective.gui.measure_annotator import MeasureAnnotator
9
+ from celldetective import get_software_location
10
+ from unittest.mock import patch
11
+ import shutil
12
+ import tifffile
13
+
14
+ software_location = get_software_location()
15
+
16
+
17
+ @pytest.fixture(autouse=True)
18
+ def disable_logging():
19
+ """Disable all logging to avoid Windows OSError with pytest capture."""
20
+ logger = logging.getLogger()
21
+ try:
22
+ logging.disable(logging.CRITICAL)
23
+ yield
24
+ finally:
25
+ logging.disable(logging.NOTSET)
26
+
27
+
28
+ @pytest.fixture
29
+ def app(qtbot):
30
+ test_app = AppInitWindow(software_location=software_location)
31
+ qtbot.addWidget(test_app)
32
+ return test_app
33
+
34
+
35
+ def create_dummy_movie(exp_dir, well="W1", pos="100", prefix="sample", frames=5):
36
+ movie_dir = os.path.join(exp_dir, well, pos, "movie")
37
+ os.makedirs(movie_dir, exist_ok=True)
38
+ # Use a single multi-page TIF as expected by locate_stack
39
+ movie_path = os.path.join(movie_dir, f"{prefix}.tif")
40
+ img = np.zeros((frames, 100, 100), dtype=np.uint16)
41
+ tifffile.imwrite(movie_path, img)
42
+
43
+
44
+ def test_measure_annotator_colors_writable(app, qtbot, tmp_path):
45
+ """
46
+ Test that self.colors in MeasureAnnotator contains writable arrays.
47
+ This verifies the fix for 'ValueError: assignment destination is read-only'.
48
+ """
49
+ exp_dir = str(tmp_path / "ExperimentColors")
50
+ os.makedirs(os.path.join(exp_dir, "W1", "100", "output", "tables"), exist_ok=True)
51
+ os.makedirs(os.path.join(exp_dir, "configs"), exist_ok=True)
52
+
53
+ with open(os.path.join(exp_dir, "config.ini"), "w") as f:
54
+ f.write(
55
+ "[MovieSettings]\nmovie_prefix = sample\nlen_movie = 10\nshape_x = 100\nshape_y = 100\npxtoum = 1.0\nframetomin = 1.0\n"
56
+ )
57
+ f.write(
58
+ "[Labels]\nconcentrations = 0\ncell_types = dummy\nantibodies = none\npharmaceutical_agents = none\n[Channels]\nChannel1 = 0\n"
59
+ )
60
+
61
+ create_dummy_movie(exp_dir, well="W1", pos="100", prefix="sample", frames=10)
62
+
63
+ # DataFrame with tracks
64
+ df = pd.DataFrame(
65
+ {
66
+ "TRACK_ID": [1, 1],
67
+ "FRAME": [0, 1],
68
+ "group_experimental": ["A", "A"],
69
+ "area": [100.0, 110.0],
70
+ "POSITION_X": [10, 12],
71
+ "POSITION_Y": [10, 12],
72
+ "status": [0, 0], # Ensure status column exists
73
+ }
74
+ )
75
+ # The 'group_color' column is usually generated inside MeasureAnnotator,
76
+ # but let's see if we need to let it generate it.
77
+ # MeasureAnnotator calls 'color_from_state', then assigns 'group_color'.
78
+
79
+ traj_path = os.path.join(
80
+ exp_dir, "W1", "100", "output", "tables", "trajectories_effectors.csv"
81
+ )
82
+ df.to_csv(traj_path, index=False)
83
+
84
+ app.experiment_path_selection.setText(exp_dir)
85
+ qtbot.mouseClick(app.validate_button, QtCore.Qt.LeftButton)
86
+ qtbot.waitUntil(lambda: hasattr(app, "control_panel"), timeout=30000)
87
+
88
+ cp = app.control_panel
89
+ p0 = cp.ProcessPopulations[0]
90
+
91
+ qtbot.waitUntil(lambda: cp.well_list.count() > 0, timeout=30000)
92
+
93
+ with patch.object(cp.well_list, "getSelectedIndices", return_value=[0]):
94
+ with patch.object(cp.position_list, "getSelectedIndices", return_value=[0]):
95
+
96
+ cp.update_position_options()
97
+ qtbot.wait(500)
98
+
99
+ qtbot.mouseClick(p0.check_measurements_btn, QtCore.Qt.LeftButton)
100
+
101
+ try:
102
+ qtbot.waitUntil(lambda: hasattr(p0, "measure_annotator"), timeout=15000)
103
+ except Exception:
104
+ print("DEBUG: measure_annotator not found on p0.")
105
+ raise
106
+
107
+ annotator = p0.measure_annotator
108
+ qtbot.wait(1000)
109
+ assert annotator is not None
110
+
111
+ # Verify that self.colors arrays are writable
112
+ # extract_scatter_from_trajectories should have been called during init
113
+ assert hasattr(annotator, "colors")
114
+ assert len(annotator.colors) > 0
115
+
116
+ # Check the first frame's colors
117
+ colors_frame_0 = annotator.colors[0]
118
+
119
+ # Check flags
120
+ assert colors_frame_0.flags[
121
+ "WRITEABLE"
122
+ ], "self.colors arrays must be writable"
123
+
124
+ # Try to modify (should not raise ValueError)
125
+ try:
126
+ colors_frame_0[0] = "lime"
127
+ except ValueError as e:
128
+ pytest.fail(f"Could not modify colors array: {e}")
129
+
130
+ annotator.close()
@@ -0,0 +1,101 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch
3
+ import sys
4
+
5
+ # Do not import torch here to avoid WinError 1114 if environment is broken.
6
+ # We will mock it in setUp.
7
+
8
+
9
+ class TestCellposeFallback(unittest.TestCase):
10
+
11
+ def setUp(self):
12
+ # Create a mock for torch
13
+ self.mock_torch = MagicMock()
14
+ self.mock_torch.device = MagicMock(return_value="cpu")
15
+ self.mock_torch.cuda = MagicMock()
16
+ self.mock_torch.cuda.is_available.return_value = (
17
+ False # Default to CPU environment simulation
18
+ )
19
+
20
+ # Patch modules so that 'import torch' and 'import cellpose' work with our mocks
21
+ # We need to patch 'torch' in sys.modules BEFORE importing code that uses it.
22
+ self.modules_patcher = patch.dict(
23
+ sys.modules,
24
+ {
25
+ "torch": self.mock_torch,
26
+ "cellpose": MagicMock(),
27
+ "cellpose.models": MagicMock(),
28
+ },
29
+ )
30
+ self.modules_patcher.start()
31
+
32
+ # Define a mock CellposeModel that we can control
33
+ self.MockCellposeModel = MagicMock()
34
+ sys.modules["cellpose.models"].CellposeModel = self.MockCellposeModel
35
+
36
+ def tearDown(self):
37
+ self.modules_patcher.stop()
38
+
39
+ def test_gpu_fallback_on_assertion_error(self):
40
+ """
41
+ Test that _prep_cellpose_model falls back to CPU if GPU init fails with AssertionError.
42
+ """
43
+ # Lazy import inside the test method/patch context
44
+ from celldetective.utils.cellpose_utils import _prep_cellpose_model
45
+
46
+ # Side effect for CellposeModel constructor
47
+ def side_effect(gpu=False, **kwargs):
48
+ if gpu:
49
+ raise AssertionError("Torch not compiled with CUDA enabled")
50
+
51
+ # Return a mock model object
52
+ model = MagicMock()
53
+ model.diam_mean = 30.0
54
+ model.diam_labels = 30.0
55
+ return model
56
+
57
+ self.MockCellposeModel.side_effect = side_effect
58
+
59
+ # Call the function with use_gpu=True
60
+ # We expect it to try with gpu=True, fail, print warning, and retry with gpu=False
61
+ model, scale = _prep_cellpose_model(
62
+ model_name="fake_model", path="fake_path/", use_gpu=True, n_channels=2
63
+ )
64
+
65
+ # Check call history
66
+ self.assertEqual(self.MockCellposeModel.call_count, 2)
67
+
68
+ args1, kwargs1 = self.MockCellposeModel.call_args_list[0]
69
+ self.assertTrue(kwargs1.get("gpu"), "First call should try gpu=True")
70
+
71
+ args2, kwargs2 = self.MockCellposeModel.call_args_list[1]
72
+ self.assertFalse(kwargs2.get("gpu"), "Second call should retry with gpu=False")
73
+
74
+ self.assertIsNotNone(model)
75
+
76
+ def test_gpu_success(self):
77
+ """
78
+ Test that _prep_cellpose_model works normally if GPU init succeeds.
79
+ """
80
+ from celldetective.utils.cellpose_utils import _prep_cellpose_model
81
+
82
+ # Side effect for success
83
+ def side_effect(gpu=False, **kwargs):
84
+ model = MagicMock()
85
+ model.diam_mean = 30.0
86
+ model.diam_labels = 30.0
87
+ return model
88
+
89
+ self.MockCellposeModel.side_effect = side_effect
90
+
91
+ model, scale = _prep_cellpose_model(
92
+ model_name="fake_model", path="fake_path/", use_gpu=True, n_channels=2
93
+ )
94
+
95
+ self.assertEqual(self.MockCellposeModel.call_count, 1)
96
+ args, kwargs = self.MockCellposeModel.call_args
97
+ self.assertTrue(kwargs.get("gpu"))
98
+
99
+
100
+ if __name__ == "__main__":
101
+ unittest.main()