celldetective 1.5.0b4__py3-none-any.whl → 1.5.0b6__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/base/figure_canvas.py +17 -3
- celldetective/gui/viewers/base_viewer.py +75 -19
- celldetective/gui/viewers/channel_offset_viewer.py +119 -5
- celldetective/gui/viewers/threshold_viewer.py +14 -6
- celldetective/utils/cellpose_utils/__init__.py +15 -2
- celldetective/utils/downloaders.py +48 -14
- celldetective/utils/stardist_utils/__init__.py +8 -1
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/METADATA +20 -7
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/RECORD +15 -14
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/WHEEL +1 -1
- tests/test_partial_install.py +75 -0
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.5.0b4.dist-info → celldetective-1.5.0b6.dist-info}/top_level.txt +0 -0
celldetective/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.5.
|
|
1
|
+
__version__ = "1.5.0b6"
|
|
@@ -22,19 +22,33 @@ class FigureCanvas(CelldetectiveWidget):
|
|
|
22
22
|
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
|
|
23
23
|
|
|
24
24
|
self.toolbar = NavigationToolbar2QT(self.canvas)
|
|
25
|
+
self.toolbar.setStyleSheet(
|
|
26
|
+
"QToolButton:hover {background-color: lightgray;} QToolButton {background-color: transparent; border: none;}"
|
|
27
|
+
)
|
|
25
28
|
self.layout = QVBoxLayout(self)
|
|
26
29
|
self.layout.addWidget(self.canvas, 90)
|
|
27
30
|
if interactive:
|
|
28
31
|
self.layout.addWidget(self.toolbar)
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
self.manual_layout = False
|
|
34
|
+
# center_window(self)
|
|
31
35
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
32
36
|
|
|
33
37
|
def resizeEvent(self, event):
|
|
34
|
-
|
|
38
|
+
print("DEBUG: resizeEvent called")
|
|
35
39
|
super().resizeEvent(event)
|
|
36
40
|
try:
|
|
37
|
-
self
|
|
41
|
+
manual_layout = getattr(self, "manual_layout", False)
|
|
42
|
+
|
|
43
|
+
# Double check for profile axes manually (robust fallback)
|
|
44
|
+
if not manual_layout and hasattr(self.fig, "axes"):
|
|
45
|
+
for ax in self.fig.axes:
|
|
46
|
+
if ax.get_label() == "profile_axes":
|
|
47
|
+
manual_layout = True
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
if not manual_layout:
|
|
51
|
+
self.fig.tight_layout()
|
|
38
52
|
except:
|
|
39
53
|
pass
|
|
40
54
|
|
|
@@ -285,6 +285,10 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
285
285
|
self.lock_y_action.setEnabled(True)
|
|
286
286
|
self.canvas.toolbar.mode = ""
|
|
287
287
|
|
|
288
|
+
# Enable manual layout control to prevent tight_layout interference
|
|
289
|
+
if hasattr(self.canvas, "manual_layout"):
|
|
290
|
+
self.canvas.manual_layout = True
|
|
291
|
+
|
|
288
292
|
# Connect events
|
|
289
293
|
self.cid_press = self.fig.canvas.mpl_connect(
|
|
290
294
|
"button_press_event", self.on_line_press
|
|
@@ -297,8 +301,8 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
297
301
|
)
|
|
298
302
|
|
|
299
303
|
# Save original position if not saved
|
|
300
|
-
if not hasattr(self, "ax_original_pos"):
|
|
301
|
-
|
|
304
|
+
# if not hasattr(self, "ax_original_pos"):
|
|
305
|
+
# self.ax_original_pos = self.ax.get_position()
|
|
302
306
|
|
|
303
307
|
# Disable tight_layout/layout engine to prevent fighting manual positioning
|
|
304
308
|
if hasattr(self.fig, "set_layout_engine"):
|
|
@@ -334,6 +338,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
334
338
|
self.ax_profile.set_position(gs[1].get_position(self.fig))
|
|
335
339
|
|
|
336
340
|
self.ax_profile.set_visible(True)
|
|
341
|
+
self.ax_profile.set_label("profile_axes")
|
|
337
342
|
self.ax_profile.set_facecolor("none")
|
|
338
343
|
self.ax_profile.tick_params(axis="y", which="major", labelsize=8)
|
|
339
344
|
self.ax_profile.set_xticks([])
|
|
@@ -346,11 +351,40 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
346
351
|
self.ax_profile.spines["bottom"].set_color("black")
|
|
347
352
|
self.ax_profile.spines["left"].set_color("black")
|
|
348
353
|
|
|
354
|
+
# Update Toolbar Home State to match new layout BUT with full field of view
|
|
355
|
+
# 1. Save current zoom
|
|
356
|
+
current_xlim = self.ax.get_xlim()
|
|
357
|
+
current_ylim = self.ax.get_ylim()
|
|
358
|
+
|
|
359
|
+
# 2. Set limits to full extent (Home State)
|
|
360
|
+
if hasattr(self, "im"):
|
|
361
|
+
extent = self.im.get_extent() # (left, right, bottom, top) or similar
|
|
362
|
+
self.ax.set_xlim(extent[0], extent[1])
|
|
363
|
+
self.ax.set_ylim(extent[2], extent[3])
|
|
364
|
+
|
|
365
|
+
# 3. Reset Stack and save Home
|
|
366
|
+
self.canvas.toolbar._nav_stack.clear()
|
|
367
|
+
self.canvas.toolbar.push_current()
|
|
368
|
+
|
|
369
|
+
# 4. Restore User Zoom
|
|
370
|
+
self.ax.set_xlim(current_xlim)
|
|
371
|
+
self.ax.set_ylim(current_ylim)
|
|
372
|
+
|
|
373
|
+
# 5. Push restored zoom state so "Back"/"Forward" logic works from here?
|
|
374
|
+
# Actually, if we just restore, we are "live" at a new state.
|
|
375
|
+
# If we don't push, "Home" works. "Back" might not exist yet. That's fine.
|
|
376
|
+
self.canvas.toolbar.push_current()
|
|
377
|
+
|
|
349
378
|
self.canvas.draw()
|
|
350
379
|
else:
|
|
351
380
|
self.line_mode = False
|
|
352
381
|
self.lock_y_action.setChecked(False)
|
|
353
382
|
self.lock_y_action.setEnabled(False)
|
|
383
|
+
|
|
384
|
+
# Disable manual layout control
|
|
385
|
+
if hasattr(self.canvas, "manual_layout"):
|
|
386
|
+
self.canvas.manual_layout = False
|
|
387
|
+
|
|
354
388
|
# Disconnect events
|
|
355
389
|
if hasattr(self, "cid_press"):
|
|
356
390
|
self.fig.canvas.mpl_disconnect(self.cid_press)
|
|
@@ -372,17 +406,24 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
372
406
|
self.ax_profile = None
|
|
373
407
|
|
|
374
408
|
# Restore original layout
|
|
375
|
-
if hasattr(self, "ax_original_pos"):
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
409
|
+
# if hasattr(self, "ax_original_pos"):
|
|
410
|
+
# standard 1x1 GridSpec or manual restore
|
|
411
|
+
import matplotlib.gridspec as gridspec
|
|
412
|
+
|
|
413
|
+
gs = gridspec.GridSpec(1, 1)
|
|
414
|
+
self.ax.set_subplotspec(gs[0])
|
|
415
|
+
# self.ax.set_position(gs[0].get_position(self.fig))
|
|
416
|
+
self.fig.subplots_adjust(
|
|
417
|
+
top=1, bottom=0, right=1, left=0, hspace=0, wspace=0
|
|
418
|
+
)
|
|
419
|
+
# self.ax.set_position(self.ax_original_pos) # tight layout should fix it
|
|
420
|
+
|
|
421
|
+
# Re-enable tight_layout via standard resize event later or explicit call
|
|
422
|
+
self.fig.tight_layout()
|
|
423
|
+
|
|
424
|
+
# Reset Toolbar Stack for Standard View
|
|
425
|
+
self.canvas.toolbar._nav_stack.clear()
|
|
426
|
+
self.canvas.toolbar.push_current()
|
|
386
427
|
|
|
387
428
|
self.canvas.draw()
|
|
388
429
|
self.info_lbl.setText("")
|
|
@@ -553,7 +594,10 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
553
594
|
self.mode = "direct"
|
|
554
595
|
self.stack_length = len(self.stack)
|
|
555
596
|
self.mid_time = self.stack_length // 2
|
|
556
|
-
self.
|
|
597
|
+
self.current_time_index = 0
|
|
598
|
+
self.init_frame = self.stack[
|
|
599
|
+
self.current_time_index, :, :, self.target_channel
|
|
600
|
+
]
|
|
557
601
|
self.last_frame = self.stack[-1, :, :, self.target_channel]
|
|
558
602
|
else:
|
|
559
603
|
self.mode = "virtual"
|
|
@@ -566,6 +610,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
566
610
|
|
|
567
611
|
self.stack_length = auto_load_number_of_frames(self.stack_path)
|
|
568
612
|
self.mid_time = self.stack_length // 2
|
|
613
|
+
self.current_time_index = 0
|
|
569
614
|
self.img_num_per_channel = _get_img_num_per_channel(
|
|
570
615
|
np.arange(self.n_channels), self.stack_length, self.n_channels
|
|
571
616
|
)
|
|
@@ -578,7 +623,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
578
623
|
self.loader_thread.start()
|
|
579
624
|
|
|
580
625
|
self.init_frame = load_frames(
|
|
581
|
-
self.img_num_per_channel[self.target_channel, self.
|
|
626
|
+
self.img_num_per_channel[self.target_channel, self.current_time_index],
|
|
582
627
|
self.stack_path,
|
|
583
628
|
normalize_input=False,
|
|
584
629
|
)[:, :, 0]
|
|
@@ -713,7 +758,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
713
758
|
layout = QHBoxLayout()
|
|
714
759
|
self.frame_slider = QLabeledSlider(Qt.Horizontal)
|
|
715
760
|
self.frame_slider.setRange(0, self.stack_length - 1)
|
|
716
|
-
self.frame_slider.setValue(self.
|
|
761
|
+
self.frame_slider.setValue(self.current_time_index)
|
|
717
762
|
self.frame_slider.valueChanged.connect(self.change_frame)
|
|
718
763
|
layout.addWidget(QLabel("Time: "), 15)
|
|
719
764
|
layout.addWidget(self.frame_slider, 85)
|
|
@@ -721,7 +766,7 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
721
766
|
|
|
722
767
|
def set_target_channel(self, value):
|
|
723
768
|
self.target_channel = value
|
|
724
|
-
self.init_frame = self.stack[self.
|
|
769
|
+
self.init_frame = self.stack[self.current_time_index, :, :, self.target_channel]
|
|
725
770
|
self.im.set_data(self.init_frame)
|
|
726
771
|
self.canvas.draw()
|
|
727
772
|
self.update_profile()
|
|
@@ -739,7 +784,9 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
739
784
|
self.change_frame_from_channel_switch(self.frame_slider.value())
|
|
740
785
|
else:
|
|
741
786
|
if self.stack is not None and self.stack.ndim == 4:
|
|
742
|
-
self.init_frame = self.stack[
|
|
787
|
+
self.init_frame = self.stack[
|
|
788
|
+
self.current_time_index, :, :, self.target_channel
|
|
789
|
+
]
|
|
743
790
|
self.im.set_data(self.init_frame)
|
|
744
791
|
self.canvas.draw()
|
|
745
792
|
self.update_profile()
|
|
@@ -752,7 +799,8 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
752
799
|
p01 = np.nanpercentile(self.init_frame, 0.1)
|
|
753
800
|
p99 = np.nanpercentile(self.init_frame, 99.9)
|
|
754
801
|
self.im.set_clim(vmin=p01, vmax=p99)
|
|
755
|
-
self.
|
|
802
|
+
if self.create_contrast_slider and hasattr(self, "contrast_slider"):
|
|
803
|
+
self.contrast_slider.setValue((p01, p99))
|
|
756
804
|
self.channel_trigger = False
|
|
757
805
|
self.canvas.draw()
|
|
758
806
|
|
|
@@ -831,6 +879,14 @@ class StackVisualizer(CelldetectiveWidget):
|
|
|
831
879
|
# Event handler for closing the widget
|
|
832
880
|
if self.loader_thread:
|
|
833
881
|
self.loader_thread.stop()
|
|
882
|
+
self.loader_thread = None
|
|
834
883
|
if hasattr(self, "frame_cache") and isinstance(self.frame_cache, OrderedDict):
|
|
835
884
|
self.frame_cache.clear()
|
|
836
885
|
self.canvas.close()
|
|
886
|
+
|
|
887
|
+
def __del__(self):
|
|
888
|
+
try:
|
|
889
|
+
if hasattr(self, "loader_thread") and self.loader_thread:
|
|
890
|
+
self.loader_thread.stop()
|
|
891
|
+
except:
|
|
892
|
+
pass
|
|
@@ -9,11 +9,16 @@ from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider
|
|
|
9
9
|
from celldetective.gui.base.components import QHSeperationLine
|
|
10
10
|
from celldetective.gui.gui_utils import QuickSliderLayout, ThresholdLineEdit
|
|
11
11
|
from celldetective.gui.viewers.base_viewer import StackVisualizer
|
|
12
|
-
from celldetective.utils.image_loaders import
|
|
12
|
+
from celldetective.utils.image_loaders import (
|
|
13
|
+
load_frames,
|
|
14
|
+
auto_load_number_of_frames,
|
|
15
|
+
_get_img_num_per_channel,
|
|
16
|
+
)
|
|
13
17
|
from celldetective import get_logger
|
|
14
18
|
|
|
15
19
|
logger = get_logger(__name__)
|
|
16
20
|
|
|
21
|
+
|
|
17
22
|
class ChannelOffsetViewer(StackVisualizer):
|
|
18
23
|
|
|
19
24
|
def __init__(self, parent_window=None, *args, **kwargs):
|
|
@@ -22,16 +27,32 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
22
27
|
self.overlay_target_channel = -1
|
|
23
28
|
self.shift_vertical = 0
|
|
24
29
|
self.shift_horizontal = 0
|
|
30
|
+
self.overlay_init_contrast = False
|
|
25
31
|
super().__init__(*args, **kwargs)
|
|
26
32
|
|
|
27
33
|
self.load_stack()
|
|
34
|
+
|
|
35
|
+
if self.mode == "direct":
|
|
36
|
+
# Initialize overlay frames for direct mode
|
|
37
|
+
default_overlay_idx = -1
|
|
38
|
+
if self.stack.ndim == 4:
|
|
39
|
+
self.overlay_init_frame = self.stack[
|
|
40
|
+
self.current_time_index, :, :, default_overlay_idx
|
|
41
|
+
]
|
|
42
|
+
self.overlay_last_frame = self.stack[-1, :, :, default_overlay_idx]
|
|
43
|
+
else:
|
|
44
|
+
# Should rely on 4D stack assumption from StackVisualizer
|
|
45
|
+
self.overlay_init_frame = self.init_frame
|
|
46
|
+
self.overlay_last_frame = self.last_frame
|
|
47
|
+
|
|
28
48
|
self.canvas.layout.addWidget(QHSeperationLine())
|
|
29
49
|
|
|
30
50
|
self.generate_overlay_channel_cb()
|
|
31
51
|
self.generate_overlay_imshow()
|
|
32
52
|
|
|
33
53
|
self.generate_overlay_alpha_slider()
|
|
34
|
-
self.
|
|
54
|
+
if self.create_contrast_slider:
|
|
55
|
+
self.generate_overlay_contrast_slider()
|
|
35
56
|
|
|
36
57
|
self.generate_overlay_shift()
|
|
37
58
|
self.generate_add_to_parent_btn()
|
|
@@ -190,7 +211,7 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
190
211
|
|
|
191
212
|
self.im_overlay.set_data(self.overlay_init_frame)
|
|
192
213
|
|
|
193
|
-
if self.overlay_init_contrast:
|
|
214
|
+
if self.overlay_init_contrast and self.create_contrast_slider:
|
|
194
215
|
self.im_overlay.autoscale()
|
|
195
216
|
I_min, I_max = self.im_overlay.get_clim()
|
|
196
217
|
self.overlay_contrast_slider.setRange(
|
|
@@ -214,12 +235,13 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
214
235
|
gc.collect()
|
|
215
236
|
|
|
216
237
|
self.mid_time = self.stack_length // 2
|
|
238
|
+
self.current_time_index = 0
|
|
217
239
|
self.img_num_per_channel = _get_img_num_per_channel(
|
|
218
240
|
np.arange(self.n_channels), self.stack_length, self.n_channels
|
|
219
241
|
)
|
|
220
242
|
|
|
221
243
|
self.init_frame = load_frames(
|
|
222
|
-
self.img_num_per_channel[self.target_channel, self.
|
|
244
|
+
self.img_num_per_channel[self.target_channel, self.current_time_index],
|
|
223
245
|
self.stack_path,
|
|
224
246
|
normalize_input=False,
|
|
225
247
|
).astype(float)[:, :, 0]
|
|
@@ -229,7 +251,9 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
229
251
|
normalize_input=False,
|
|
230
252
|
).astype(float)[:, :, 0]
|
|
231
253
|
self.overlay_init_frame = load_frames(
|
|
232
|
-
self.img_num_per_channel[
|
|
254
|
+
self.img_num_per_channel[
|
|
255
|
+
self.overlay_target_channel, self.current_time_index
|
|
256
|
+
],
|
|
233
257
|
self.stack_path,
|
|
234
258
|
normalize_input=False,
|
|
235
259
|
).astype(float)[:, :, 0]
|
|
@@ -306,6 +330,7 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
306
330
|
)
|
|
307
331
|
self.im_overlay.set_data(self.shifted_frame)
|
|
308
332
|
self.fig.canvas.draw_idle()
|
|
333
|
+
self.update_profile()
|
|
309
334
|
|
|
310
335
|
def generate_add_to_parent_btn(self):
|
|
311
336
|
|
|
@@ -318,6 +343,95 @@ class ChannelOffsetViewer(StackVisualizer):
|
|
|
318
343
|
add_hbox.addWidget(QLabel(""), 33)
|
|
319
344
|
self.canvas.layout.addLayout(add_hbox)
|
|
320
345
|
|
|
346
|
+
def update_profile(self):
|
|
347
|
+
if not self.line_mode or not hasattr(self, "line_x") or not self.line_x:
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
# Calculate profile
|
|
351
|
+
x0, y0 = self.line_x[0], self.line_y[0]
|
|
352
|
+
x1, y1 = self.line_x[1], self.line_y[1]
|
|
353
|
+
length_px = np.hypot(x1 - x0, y1 - y0)
|
|
354
|
+
if length_px == 0:
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
num_points = int(length_px)
|
|
358
|
+
if num_points < 2:
|
|
359
|
+
num_points = 2
|
|
360
|
+
|
|
361
|
+
x, y = np.linspace(x0, x1, num_points), np.linspace(y0, y1, num_points)
|
|
362
|
+
|
|
363
|
+
# Use self.init_frame and overlay frame
|
|
364
|
+
profiles = []
|
|
365
|
+
colors = ["black", "tab:blue"]
|
|
366
|
+
|
|
367
|
+
# Main channel profile
|
|
368
|
+
if hasattr(self, "init_frame") and self.init_frame is not None:
|
|
369
|
+
from scipy.ndimage import map_coordinates
|
|
370
|
+
|
|
371
|
+
profile = map_coordinates(
|
|
372
|
+
self.init_frame, np.vstack((y, x)), order=1, mode="nearest"
|
|
373
|
+
)
|
|
374
|
+
profiles.append(profile)
|
|
375
|
+
else:
|
|
376
|
+
profiles.append(None)
|
|
377
|
+
|
|
378
|
+
# Overlay channel profile
|
|
379
|
+
# Use data currently in im_overlay, which accounts for shifts
|
|
380
|
+
overlay_data = self.im_overlay.get_array()
|
|
381
|
+
if overlay_data is not None:
|
|
382
|
+
from scipy.ndimage import map_coordinates
|
|
383
|
+
|
|
384
|
+
profile_overlay = map_coordinates(
|
|
385
|
+
overlay_data, np.vstack((y, x)), order=1, mode="nearest"
|
|
386
|
+
)
|
|
387
|
+
profiles.append(profile_overlay)
|
|
388
|
+
else:
|
|
389
|
+
profiles.append(None)
|
|
390
|
+
|
|
391
|
+
# Basic setup
|
|
392
|
+
self.ax_profile.clear()
|
|
393
|
+
self.ax_profile.set_facecolor("none")
|
|
394
|
+
|
|
395
|
+
# Distance axis
|
|
396
|
+
dist_axis = np.arange(num_points)
|
|
397
|
+
title_str = f"{round(length_px,2)} [px]"
|
|
398
|
+
if self.PxToUm is not None:
|
|
399
|
+
title_str += f" | {round(length_px*self.PxToUm,3)} [µm]"
|
|
400
|
+
|
|
401
|
+
# Handle Y-Axis Locking
|
|
402
|
+
current_ylim = None
|
|
403
|
+
if self.lock_y_action.isChecked():
|
|
404
|
+
current_ylim = self.ax_profile.get_ylim()
|
|
405
|
+
|
|
406
|
+
# Plot profiles
|
|
407
|
+
for i, (profile, color) in enumerate(zip(profiles, colors)):
|
|
408
|
+
if profile is not None:
|
|
409
|
+
if np.all(np.isnan(profile)):
|
|
410
|
+
profile = np.zeros_like(profile)
|
|
411
|
+
profile[:] = np.nan
|
|
412
|
+
|
|
413
|
+
self.ax_profile.plot(
|
|
414
|
+
dist_axis, profile, color=color, linestyle="-", label=f"Ch{i}"
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
self.ax_profile.set_xticks([])
|
|
418
|
+
self.ax_profile.set_ylabel("Intensity", fontsize=8)
|
|
419
|
+
self.ax_profile.set_xlabel(title_str, fontsize=8)
|
|
420
|
+
self.ax_profile.tick_params(axis="y", which="major", labelsize=6)
|
|
421
|
+
|
|
422
|
+
# Hide spines
|
|
423
|
+
self.ax_profile.spines["top"].set_visible(False)
|
|
424
|
+
self.ax_profile.spines["right"].set_visible(False)
|
|
425
|
+
self.ax_profile.spines["bottom"].set_color("black")
|
|
426
|
+
self.ax_profile.spines["left"].set_color("black")
|
|
427
|
+
|
|
428
|
+
self.fig.set_facecolor("none")
|
|
429
|
+
|
|
430
|
+
if current_ylim:
|
|
431
|
+
self.ax_profile.set_ylim(current_ylim)
|
|
432
|
+
|
|
433
|
+
self.fig.canvas.draw_idle()
|
|
434
|
+
|
|
321
435
|
def set_parent_attributes(self):
|
|
322
436
|
|
|
323
437
|
idx = self.channels_overlay_cb.currentIndex()
|
|
@@ -65,8 +65,6 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
65
65
|
self.thresh_min = 0.0
|
|
66
66
|
self.thresh_max = 30.0
|
|
67
67
|
|
|
68
|
-
self.thresh_max = 30.0
|
|
69
|
-
|
|
70
68
|
# Cache for processed images
|
|
71
69
|
self.processed_cache = OrderedDict()
|
|
72
70
|
self.processed_image = None
|
|
@@ -74,6 +72,15 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
74
72
|
|
|
75
73
|
self.generate_threshold_slider()
|
|
76
74
|
|
|
75
|
+
# Ensure we start at frame 0 for consistent mask caching and UX
|
|
76
|
+
if self.create_frame_slider and hasattr(self, "frame_slider"):
|
|
77
|
+
self.frame_slider.blockSignals(True)
|
|
78
|
+
self.frame_slider.setValue(0)
|
|
79
|
+
self.frame_slider.blockSignals(False)
|
|
80
|
+
self.change_frame(0)
|
|
81
|
+
elif self.stack_length > 0:
|
|
82
|
+
self.change_frame(0)
|
|
83
|
+
|
|
77
84
|
if self.thresh is not None:
|
|
78
85
|
self.compute_mask(self.thresh)
|
|
79
86
|
|
|
@@ -138,8 +145,8 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
138
145
|
slider_range=(self.thresh_min, np.amax([self.thresh_max, init_value])),
|
|
139
146
|
decimal_option=True,
|
|
140
147
|
precision=4,
|
|
148
|
+
layout_ratio=(0.15, 0.85),
|
|
141
149
|
)
|
|
142
|
-
thresh_layout.setContentsMargins(15, 0, 15, 0)
|
|
143
150
|
self.threshold_slider.valueChanged.connect(self.change_threshold)
|
|
144
151
|
if self.show_threshold_slider:
|
|
145
152
|
self.canvas.layout.addLayout(thresh_layout)
|
|
@@ -154,8 +161,8 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
154
161
|
slider_range=(0, 1),
|
|
155
162
|
decimal_option=True,
|
|
156
163
|
precision=3,
|
|
164
|
+
layout_ratio=(0.15, 0.85),
|
|
157
165
|
)
|
|
158
|
-
opacity_layout.setContentsMargins(15, 0, 15, 0)
|
|
159
166
|
self.opacity_slider.valueChanged.connect(self.change_mask_opacity)
|
|
160
167
|
if self.show_opacity_slider:
|
|
161
168
|
self.canvas.layout.addLayout(opacity_layout)
|
|
@@ -190,7 +197,8 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
190
197
|
if self.thresh is not None:
|
|
191
198
|
self.compute_mask(self.thresh)
|
|
192
199
|
mask = np.ma.masked_where(self.mask == 0, self.mask)
|
|
193
|
-
self
|
|
200
|
+
if hasattr(self, "im_mask"):
|
|
201
|
+
self.im_mask.set_data(mask)
|
|
194
202
|
self.canvas.canvas.draw_idle()
|
|
195
203
|
|
|
196
204
|
def change_frame(self, value):
|
|
@@ -219,7 +227,7 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
219
227
|
threshold_image,
|
|
220
228
|
)
|
|
221
229
|
|
|
222
|
-
edge = estimate_unreliable_edge(self.preprocessing)
|
|
230
|
+
edge = estimate_unreliable_edge(self.preprocessing or [])
|
|
223
231
|
|
|
224
232
|
if isinstance(threshold_value, (list, np.ndarray, tuple)):
|
|
225
233
|
self.mask = threshold_image(
|
|
@@ -104,14 +104,27 @@ def _prep_cellpose_model(
|
|
|
104
104
|
`diam_labels` are attributes of the model.
|
|
105
105
|
"""
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
try:
|
|
108
|
+
import torch
|
|
109
|
+
except ImportError as e:
|
|
110
|
+
raise RuntimeError(
|
|
111
|
+
"Torch is not installed. Please install it to use this feature.\n"
|
|
112
|
+
"You can install the full package with: pip install celldetective[process]\n"
|
|
113
|
+
) from e
|
|
108
114
|
|
|
109
115
|
if not use_gpu:
|
|
110
116
|
device = torch.device("cpu")
|
|
111
117
|
else:
|
|
112
118
|
device = torch.device("cuda")
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
try:
|
|
121
|
+
from cellpose.models import CellposeModel
|
|
122
|
+
except ImportError as e:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
"Cellpose is not installed. Please install it to use this feature.\n"
|
|
125
|
+
"You can install the full package with: pip install celldetective[process]\n"
|
|
126
|
+
"Or specifically: pip install celldetective[cellpose]"
|
|
127
|
+
) from e
|
|
115
128
|
|
|
116
129
|
try:
|
|
117
130
|
model = CellposeModel(
|
|
@@ -77,18 +77,39 @@ def download_url_to_file(url, dst, progress=True):
|
|
|
77
77
|
Default: True
|
|
78
78
|
|
|
79
79
|
"""
|
|
80
|
-
file_size = None
|
|
81
80
|
import ssl
|
|
81
|
+
import time
|
|
82
|
+
from urllib.error import HTTPError, URLError
|
|
82
83
|
|
|
84
|
+
file_size = None
|
|
83
85
|
ssl._create_default_https_context = ssl._create_unverified_context
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
|
|
87
|
+
# Retry configuration
|
|
88
|
+
max_retries = 5
|
|
89
|
+
retry_delay = 10 # Initial delay in seconds
|
|
90
|
+
|
|
91
|
+
for attempt in range(max_retries):
|
|
92
|
+
try:
|
|
93
|
+
u = urlopen(url)
|
|
94
|
+
meta = u.info()
|
|
95
|
+
if hasattr(meta, "getheaders"):
|
|
96
|
+
content_length = meta.getheaders("Content-Length")
|
|
97
|
+
else:
|
|
98
|
+
content_length = meta.get_all("Content-Length")
|
|
99
|
+
if content_length is not None and len(content_length) > 0:
|
|
100
|
+
file_size = int(content_length[0])
|
|
101
|
+
break # Success
|
|
102
|
+
except (HTTPError, URLError) as e:
|
|
103
|
+
if attempt < max_retries - 1:
|
|
104
|
+
logger.warning(
|
|
105
|
+
f"Download check failed: {e}. Retrying in {retry_delay}s..."
|
|
106
|
+
)
|
|
107
|
+
time.sleep(retry_delay)
|
|
108
|
+
retry_delay *= 2 # Exponential backoff
|
|
109
|
+
else:
|
|
110
|
+
logger.error(f"Download check failed after {max_retries} attempts: {e}")
|
|
111
|
+
raise e
|
|
112
|
+
|
|
92
113
|
# We deliberately save it in a temp file and move it after
|
|
93
114
|
dst = os.path.expanduser(dst)
|
|
94
115
|
dst_dir = os.path.dirname(dst)
|
|
@@ -143,14 +164,27 @@ def download_url_to_file(url, dst, progress=True):
|
|
|
143
164
|
unit_divisor=1024,
|
|
144
165
|
) as pbar:
|
|
145
166
|
while True:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
try:
|
|
168
|
+
buffer = u.read(8192) # 8192
|
|
169
|
+
if len(buffer) == 0:
|
|
170
|
+
break
|
|
171
|
+
f.write(buffer)
|
|
172
|
+
pbar.update(len(buffer))
|
|
173
|
+
except (HTTPError, URLError) as e:
|
|
174
|
+
# Attempt rudimentary resume-like behavior or just fail?
|
|
175
|
+
# Simple retry of read is hard without Range headers on a stream.
|
|
176
|
+
# Best to just fail the whole download and rely on outer retry if we wrapped the whole thing.
|
|
177
|
+
# For now, let's just let it raise, but really we should wrap the whole download block.
|
|
178
|
+
raise e
|
|
151
179
|
|
|
152
180
|
f.close()
|
|
153
181
|
shutil.move(f.name, dst)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
f.close()
|
|
184
|
+
remove_file_if_exists(f.name)
|
|
185
|
+
# If we failed during download reading (after open), we should probably retry the whole function from start
|
|
186
|
+
# but that requires significant refactoring. Given the error was 504 on open, the retry block above handles it.
|
|
187
|
+
raise e
|
|
154
188
|
finally:
|
|
155
189
|
f.close()
|
|
156
190
|
remove_file_if_exists(f.name)
|
|
@@ -43,7 +43,14 @@ def _prep_stardist_model(
|
|
|
43
43
|
- GPU support depends on the availability of compatible hardware and software setup.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
try:
|
|
47
|
+
from stardist.models import StarDist2D
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
"StarDist is not installed. Please install it to use this feature.\n"
|
|
51
|
+
"You can install the full package with: pip install celldetective[process]\n"
|
|
52
|
+
"Or specifically: pip install celldetective[stardist]"
|
|
53
|
+
) from e
|
|
47
54
|
|
|
48
55
|
model = StarDist2D(None, name=model_name, basedir=path)
|
|
49
56
|
model.config.use_gpu = use_gpu
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: celldetective
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.0b6
|
|
4
4
|
Summary: description
|
|
5
5
|
Home-page: http://github.com/remyeltorro/celldetective
|
|
6
6
|
Author: Rémy Torro
|
|
@@ -15,11 +15,8 @@ Requires-Dist: sphinx_rtd_theme
|
|
|
15
15
|
Requires-Dist: sphinx
|
|
16
16
|
Requires-Dist: jinja2
|
|
17
17
|
Requires-Dist: ipykernel
|
|
18
|
-
Requires-Dist: stardist
|
|
19
|
-
Requires-Dist: cellpose<3
|
|
20
18
|
Requires-Dist: scikit-learn
|
|
21
19
|
Requires-Dist: btrack
|
|
22
|
-
Requires-Dist: tensorflow~=2.15.0
|
|
23
20
|
Requires-Dist: napari<0.6.0
|
|
24
21
|
Requires-Dist: tqdm
|
|
25
22
|
Requires-Dist: mahotas
|
|
@@ -48,6 +45,19 @@ Requires-Dist: prettyprint
|
|
|
48
45
|
Requires-Dist: pandas
|
|
49
46
|
Requires-Dist: matplotlib
|
|
50
47
|
Requires-Dist: prettytable
|
|
48
|
+
Requires-Dist: scikit-image
|
|
49
|
+
Requires-Dist: natsort
|
|
50
|
+
Provides-Extra: tensorflow
|
|
51
|
+
Requires-Dist: tensorflow~=2.15.0; extra == "tensorflow"
|
|
52
|
+
Requires-Dist: stardist; extra == "tensorflow"
|
|
53
|
+
Provides-Extra: process
|
|
54
|
+
Requires-Dist: cellpose<3; extra == "process"
|
|
55
|
+
Requires-Dist: stardist; extra == "process"
|
|
56
|
+
Requires-Dist: tensorflow~=2.15.0; extra == "process"
|
|
57
|
+
Provides-Extra: all
|
|
58
|
+
Requires-Dist: cellpose<3; extra == "all"
|
|
59
|
+
Requires-Dist: stardist; extra == "all"
|
|
60
|
+
Requires-Dist: tensorflow~=2.15.0; extra == "all"
|
|
51
61
|
Dynamic: author
|
|
52
62
|
Dynamic: author-email
|
|
53
63
|
Dynamic: description
|
|
@@ -55,6 +65,7 @@ Dynamic: description-content-type
|
|
|
55
65
|
Dynamic: home-page
|
|
56
66
|
Dynamic: license
|
|
57
67
|
Dynamic: license-file
|
|
68
|
+
Dynamic: provides-extra
|
|
58
69
|
Dynamic: requires-dist
|
|
59
70
|
Dynamic: summary
|
|
60
71
|
|
|
@@ -148,10 +159,12 @@ To use the software, you must install python, *e.g.* through
|
|
|
148
159
|
|
|
149
160
|
Celldetective requires a version of Python between 3.9 and 3.11 (included). If your Python version is older or more recent, consider using `conda` to create an environment as described below.
|
|
150
161
|
|
|
151
|
-
With the proper Python version, Celldetective can be directly installed with `pip
|
|
162
|
+
With the proper Python version, Celldetective can be directly installed with `pip`.
|
|
163
|
+
**Important**: By default, `pip install celldetective` will **not** install deep-learning libraries (`tensorflow`, `cellpose`, `stardist`) to allow users to manage their own GPU environment (e.g. `torch`, `cuda`).
|
|
152
164
|
|
|
165
|
+
If you want the standard full installation (recommended for most users), use:
|
|
153
166
|
``` bash
|
|
154
|
-
pip install celldetective
|
|
167
|
+
pip install celldetective[all]
|
|
155
168
|
```
|
|
156
169
|
|
|
157
170
|
We recommend that you create an environment to use Celldetective, to protect your package versions and fix the Python version *e.g.*
|
|
@@ -160,7 +173,7 @@ with `conda`:
|
|
|
160
173
|
``` bash
|
|
161
174
|
conda create -n celldetective python=3.11 pyqt
|
|
162
175
|
conda activate celldetective
|
|
163
|
-
pip install celldetective
|
|
176
|
+
pip install celldetective[all]
|
|
164
177
|
```
|
|
165
178
|
|
|
166
179
|
Need an update? Simply type the following in the terminal (in your
|
|
@@ -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=
|
|
3
|
+
celldetective/_version.py,sha256=2t7jfGYhecNEZFNHUmz-D7GUPyvfrkDim4ynpi-TYII,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
|
|
@@ -45,7 +45,7 @@ celldetective/gui/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
45
45
|
celldetective/gui/base/channel_norm_generator.py,sha256=JqNAo87zA3nMKzkGjvoeV-SHI89eIATwQj4jHv3-LpI,13973
|
|
46
46
|
celldetective/gui/base/components.py,sha256=jNUsCU_QE7QUFR0_xEvEPFEBYolMJt7YXGUKMjF7uOE,8155
|
|
47
47
|
celldetective/gui/base/feature_choice.py,sha256=n1T2fPoNLiTDS_6fa_GuGReDhBW11HdUrKy2RywotF8,2800
|
|
48
|
-
celldetective/gui/base/figure_canvas.py,sha256=
|
|
48
|
+
celldetective/gui/base/figure_canvas.py,sha256=DAAGQAcoR6yubFSsRNRZcpxgdYOWgEHtJqWhv_G7lj0,2195
|
|
49
49
|
celldetective/gui/base/list_widget.py,sha256=_WU3ZRU7UcJZIxm8qx_5HF7IK7dUu8IU1FY2AaW_qgo,4694
|
|
50
50
|
celldetective/gui/base/styles.py,sha256=3Kz1eXw6OLr90wtErhK0KPJyJbyhAlqkniqm0JNGwFc,7407
|
|
51
51
|
celldetective/gui/base/utils.py,sha256=KojauRKGM9XKNhaWn211p6mJNZWIHLH75yeLpDd7pvA,1103
|
|
@@ -87,12 +87,12 @@ 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=
|
|
91
|
-
celldetective/gui/viewers/channel_offset_viewer.py,sha256=
|
|
90
|
+
celldetective/gui/viewers/base_viewer.py,sha256=wA2Zrje5nQSUmwnAFCe9hcL1MOWg8uMKwFTWhEYiMXI,32159
|
|
91
|
+
celldetective/gui/viewers/channel_offset_viewer.py,sha256=cywBkxyMPyKIuwZTGA03DBSS4a-H1SAohMJYOPISLEE,16289
|
|
92
92
|
celldetective/gui/viewers/contour_viewer.py,sha256=riHj03LKXLoa-Ys2o2EhCE5nULfcHMohx9LFoXbI6zU,14720
|
|
93
93
|
celldetective/gui/viewers/size_viewer.py,sha256=uXITjaxg5dhQ09Q6TNUxwLxi-ZglyGFcxEH1RtGIZWw,6020
|
|
94
94
|
celldetective/gui/viewers/spot_detection_viewer.py,sha256=JO7kcqATHXR91lLvo8aQ5xVYdtxkMxV-xx36s01VlNQ,12545
|
|
95
|
-
celldetective/gui/viewers/threshold_viewer.py,sha256=
|
|
95
|
+
celldetective/gui/viewers/threshold_viewer.py,sha256=F-13JF2wFhyvvKfUvgRcSjWL3leAliOXy5yUergndnE,12000
|
|
96
96
|
celldetective/icons/logo-large.png,sha256=FXSwV3u6zEKcfpuSn4unnqB0oUnN9cHqQ9BCKWytrpg,36631
|
|
97
97
|
celldetective/icons/logo.png,sha256=wV2OS8_dU5Td5cgdPbCOU3JpMpTwNuYLnfVcnQX0tJA,2437
|
|
98
98
|
celldetective/icons/signals_icon.png,sha256=vEiKoqWTtN0-uJgVqtAlwCuP-f4QeWYOlO3sdp2tg2w,3969
|
|
@@ -143,7 +143,7 @@ celldetective/utils/color_mappings.py,sha256=yarqOTSrTsnOPPiPrrN_vLoPCbgWqF3wjqF
|
|
|
143
143
|
celldetective/utils/data_cleaning.py,sha256=K-2gScxLreX7QkrM0h3dZdP0IsmvCzcxNh2-M9PALZY,22025
|
|
144
144
|
celldetective/utils/data_loaders.py,sha256=6Jg99U93qYjjs2xZErc2cz37tcH5e4vEqDH8PJgoEJs,17077
|
|
145
145
|
celldetective/utils/dataset_helpers.py,sha256=3ezpHO6nytw2Mx0D3maP_4535V2ohOTQn6Qpfk8gnms,6898
|
|
146
|
-
celldetective/utils/downloaders.py,sha256=
|
|
146
|
+
celldetective/utils/downloaders.py,sha256=o5sogEeYr-LR8mp1D7uNHH1aUJN3V82VVwYZvoSNTkQ,9506
|
|
147
147
|
celldetective/utils/experiment.py,sha256=bgADS70QuW4KGbzDJbVpVM-tw4qmZKMWtDT1cSxugrY,58342
|
|
148
148
|
celldetective/utils/image_augmenters.py,sha256=USYd8z6dVn5z1x96IYJ4mG0smN9I_S21QMGU0wyHmjc,11654
|
|
149
149
|
celldetective/utils/image_cleaning.py,sha256=KliQ3K5hdwPx4eFxJnmg3yi-ZIoimEveunPJkbbA6wA,2388
|
|
@@ -161,12 +161,12 @@ 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=
|
|
164
|
+
celldetective/utils/cellpose_utils/__init__.py,sha256=XUG6T9RFwJ2H2jrz9eO58gYHjI6vxMe93ZeDD35DChg,5999
|
|
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
|
-
celldetective/utils/stardist_utils/__init__.py,sha256=
|
|
169
|
-
celldetective-1.5.
|
|
168
|
+
celldetective/utils/stardist_utils/__init__.py,sha256=eQiZz7UQbwkFS6zXOJFofoNm2wE7ylt-7hdCOd9uOWc,4304
|
|
169
|
+
celldetective-1.5.0b6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
170
170
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
171
171
|
tests/test_cellpose_fallback.py,sha256=BJZTDFF8sFR1x7rDbvZQ2RQOB1OP6wuFBRfc8zbl5zw,3513
|
|
172
172
|
tests/test_events.py,sha256=eLFwwEEJfQAdwhews3-fn1HSvzozcNNFN_Qn0gOvQkE,685
|
|
@@ -175,6 +175,7 @@ tests/test_io.py,sha256=gk5FmoI7ANEczUtNXYRxc48KzkfYzemwS_eYaLq4_NI,2093
|
|
|
175
175
|
tests/test_measure.py,sha256=FEUAs1rVHylvIvubCb0bJDNGZLVmkgXNgI3NaGQ1dA8,4542
|
|
176
176
|
tests/test_neighborhood.py,sha256=gk5FmoI7ANEczUtNXYRxc48KzkfYzemwS_eYaLq4_NI,2093
|
|
177
177
|
tests/test_notebooks.py,sha256=7HVmYiytsz0QIJ11iRkGGs4_hzNjofXAUs_OZou3Gm0,301
|
|
178
|
+
tests/test_partial_install.py,sha256=G69-GNcJ9YNgs6K2bVTEZO0Jpb14xMRQWTm8A6VuIco,2841
|
|
178
179
|
tests/test_preprocessing.py,sha256=c0rKS9d5h37uDcV7fVOTnn5GMVbEB84b8ZTCTdRmvFs,1422
|
|
179
180
|
tests/test_segmentation.py,sha256=k1b_zIZdlytEdJcHjAUQEO3gTBAHtv5WvrwQN2xD4kc,3470
|
|
180
181
|
tests/test_signals.py,sha256=No4cah6KxplhDcKXnU8RrA7eDla4hWw6ccf7xGnBokU,3599
|
|
@@ -185,8 +186,8 @@ tests/gui/test_enhancements.py,sha256=3x9au_rkQtMZ94DRj3OaEHKPr511RrWqBAUAcNQn1y
|
|
|
185
186
|
tests/gui/test_measure_annotator_bugfix.py,sha256=tPfgWNKC0UkvrVssSrUcVDC1qgpzx6l2yCqvKtKYkM4,4544
|
|
186
187
|
tests/gui/test_new_project.py,sha256=wRjW2vEaZb0LWT-f8G8-Ptk8CW9z8-FDPLpV5uqj6ck,8778
|
|
187
188
|
tests/gui/test_project.py,sha256=KzAnodIc0Ovta0ARL5Kr5PkOR5euA6qczT_GhEZpyE4,4710
|
|
188
|
-
celldetective-1.5.
|
|
189
|
-
celldetective-1.5.
|
|
190
|
-
celldetective-1.5.
|
|
191
|
-
celldetective-1.5.
|
|
192
|
-
celldetective-1.5.
|
|
189
|
+
celldetective-1.5.0b6.dist-info/METADATA,sha256=7e7KJTEzTLZx7T0o4ITML6CI6PM8EwXU4ENpB0uymNc,11691
|
|
190
|
+
celldetective-1.5.0b6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
191
|
+
celldetective-1.5.0b6.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
|
|
192
|
+
celldetective-1.5.0b6.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
|
|
193
|
+
celldetective-1.5.0b6.dist-info/RECORD,,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
import sys
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestPartialValidation(unittest.TestCase):
|
|
8
|
+
|
|
9
|
+
def test_imports_without_extras(self):
|
|
10
|
+
"""Test that main modules can be imported even if optional extras are missing."""
|
|
11
|
+
# This test assumes the environment MIGHT have them, so we must mock them as missing
|
|
12
|
+
# to ensure the code handles it.
|
|
13
|
+
|
|
14
|
+
with patch.dict(
|
|
15
|
+
sys.modules,
|
|
16
|
+
{
|
|
17
|
+
"tensorflow": None,
|
|
18
|
+
"torch": None,
|
|
19
|
+
"stardist": None,
|
|
20
|
+
"cellpose": None,
|
|
21
|
+
"cellpose.models": None,
|
|
22
|
+
"stardist.models": None,
|
|
23
|
+
},
|
|
24
|
+
):
|
|
25
|
+
# Force reload of celldetective.segmentation to test its imports
|
|
26
|
+
try:
|
|
27
|
+
import celldetective.segmentation
|
|
28
|
+
|
|
29
|
+
importlib.reload(celldetective.segmentation)
|
|
30
|
+
except ImportError as e:
|
|
31
|
+
self.fail(
|
|
32
|
+
f"Could not import celldetective.segmentation without extras: {e}"
|
|
33
|
+
)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
self.fail(f"Unexpected error importing celldetective.segmentation: {e}")
|
|
36
|
+
|
|
37
|
+
def test_graceful_failure_stardist(self):
|
|
38
|
+
"""Test that calling stardist functions raises RuntimeError if missing."""
|
|
39
|
+
with patch.dict(sys.modules, {"stardist": None, "stardist.models": None}):
|
|
40
|
+
# We need to reload the util module to pick up the missing module
|
|
41
|
+
import celldetective.utils.stardist_utils
|
|
42
|
+
|
|
43
|
+
importlib.reload(celldetective.utils.stardist_utils)
|
|
44
|
+
|
|
45
|
+
from celldetective.utils.stardist_utils import _prep_stardist_model
|
|
46
|
+
|
|
47
|
+
with self.assertRaises(RuntimeError) as cm:
|
|
48
|
+
_prep_stardist_model("fake_model", "fake_path")
|
|
49
|
+
|
|
50
|
+
self.assertIn("StarDist is not installed", str(cm.exception))
|
|
51
|
+
|
|
52
|
+
def test_graceful_failure_cellpose(self):
|
|
53
|
+
"""Test that calling cellpose functions raises RuntimeError if missing."""
|
|
54
|
+
with patch.dict(
|
|
55
|
+
sys.modules, {"cellpose": None, "cellpose.models": None, "torch": None}
|
|
56
|
+
):
|
|
57
|
+
import celldetective.utils.cellpose_utils
|
|
58
|
+
|
|
59
|
+
importlib.reload(celldetective.utils.cellpose_utils)
|
|
60
|
+
|
|
61
|
+
from celldetective.utils.cellpose_utils import _prep_cellpose_model
|
|
62
|
+
|
|
63
|
+
with self.assertRaises(RuntimeError) as cm:
|
|
64
|
+
_prep_cellpose_model("fake_model", "fake_path")
|
|
65
|
+
|
|
66
|
+
# Message check might correspond to torch or cellpose depending on which import hits first
|
|
67
|
+
# Our code checks torch first.
|
|
68
|
+
self.assertTrue(
|
|
69
|
+
"Torch is not installed" in str(cm.exception)
|
|
70
|
+
or "Cellpose is not installed" in str(cm.exception)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|