napari-tmidas 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- napari_tmidas/_crop_anything.py +55 -7
- napari_tmidas/_file_selector.py +12 -10
- napari_tmidas/_registry.py +15 -14
- napari_tmidas/_tests/test_file_selector.py +90 -0
- napari_tmidas/_tests/test_registry.py +67 -0
- napari_tmidas/_version.py +2 -2
- napari_tmidas/processing_functions/trackastra_tracking.py +24 -5
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/METADATA +36 -23
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/RECORD +13 -11
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/WHEEL +0 -0
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/entry_points.txt +0 -0
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {napari_tmidas-0.2.1.dist-info → napari_tmidas-0.2.2.dist-info}/top_level.txt +0 -0
napari_tmidas/_crop_anything.py
CHANGED
|
@@ -8,11 +8,8 @@ The plugin supports both 2D (YX) and 3D (TYX/ZYX) data.
|
|
|
8
8
|
|
|
9
9
|
import contextlib
|
|
10
10
|
import os
|
|
11
|
-
|
|
12
|
-
# Add this at the beginning of your plugin file
|
|
13
11
|
import sys
|
|
14
12
|
|
|
15
|
-
sys.path.append("/opt/sam2")
|
|
16
13
|
import numpy as np
|
|
17
14
|
import requests
|
|
18
15
|
import torch
|
|
@@ -40,10 +37,30 @@ from tifffile import imwrite
|
|
|
40
37
|
|
|
41
38
|
from napari_tmidas.processing_functions.sam2_mp4 import tif_to_mp4
|
|
42
39
|
|
|
40
|
+
sam2_paths = [
|
|
41
|
+
os.environ.get("SAM2_PATH"),
|
|
42
|
+
"/opt/sam2",
|
|
43
|
+
os.path.expanduser("~/sam2"),
|
|
44
|
+
"./sam2",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
for path in sam2_paths:
|
|
48
|
+
if path and os.path.exists(path):
|
|
49
|
+
sys.path.append(path)
|
|
50
|
+
break
|
|
51
|
+
else:
|
|
52
|
+
print(
|
|
53
|
+
"Warning: SAM2 not found in common locations. Please set SAM2_PATH environment variable."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
43
57
|
def get_device():
|
|
44
58
|
if sys.platform == "darwin":
|
|
45
59
|
# MacOS: Only check for MPS
|
|
46
|
-
if
|
|
60
|
+
if (
|
|
61
|
+
hasattr(torch.backends, "mps")
|
|
62
|
+
and torch.backends.mps.is_available()
|
|
63
|
+
):
|
|
47
64
|
device = torch.device("mps")
|
|
48
65
|
print("Using Apple Silicon GPU (MPS)")
|
|
49
66
|
else:
|
|
@@ -60,8 +77,6 @@ def get_device():
|
|
|
60
77
|
return device
|
|
61
78
|
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
|
|
65
80
|
class BatchCropAnything:
|
|
66
81
|
"""Class for processing images with SAM2 and cropping selected objects."""
|
|
67
82
|
|
|
@@ -104,7 +119,7 @@ class BatchCropAnything:
|
|
|
104
119
|
filename = os.path.join(dest_folder, url.split("/")[-1])
|
|
105
120
|
if not os.path.exists(filename):
|
|
106
121
|
print(f"Downloading checkpoint to {filename}...")
|
|
107
|
-
response = requests.get(url, stream=True)
|
|
122
|
+
response = requests.get(url, stream=True, timeout=30)
|
|
108
123
|
response.raise_for_status()
|
|
109
124
|
with open(filename, "wb") as f:
|
|
110
125
|
for chunk in response.iter_content(chunk_size=8192):
|
|
@@ -733,6 +748,15 @@ class BatchCropAnything:
|
|
|
733
748
|
border_width=1,
|
|
734
749
|
opacity=0.8,
|
|
735
750
|
)
|
|
751
|
+
|
|
752
|
+
with contextlib.suppress(AttributeError, ValueError):
|
|
753
|
+
points_layer.mouse_drag_callbacks.remove(
|
|
754
|
+
self._on_points_clicked
|
|
755
|
+
)
|
|
756
|
+
points_layer.mouse_drag_callbacks.append(
|
|
757
|
+
self._on_points_clicked
|
|
758
|
+
)
|
|
759
|
+
|
|
736
760
|
# Initialize points for this object
|
|
737
761
|
if not hasattr(self, "sam2_points_by_obj"):
|
|
738
762
|
self.sam2_points_by_obj = {}
|
|
@@ -1107,6 +1131,14 @@ class BatchCropAnything:
|
|
|
1107
1131
|
opacity=0.8,
|
|
1108
1132
|
)
|
|
1109
1133
|
|
|
1134
|
+
with contextlib.suppress(AttributeError, ValueError):
|
|
1135
|
+
points_layer.mouse_drag_callbacks.remove(
|
|
1136
|
+
self._on_points_clicked
|
|
1137
|
+
)
|
|
1138
|
+
points_layer.mouse_drag_callbacks.append(
|
|
1139
|
+
self._on_points_clicked
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1110
1142
|
# Connect points layer mouse click event
|
|
1111
1143
|
points_layer.mouse_drag_callbacks.append(self._on_points_clicked)
|
|
1112
1144
|
|
|
@@ -1646,6 +1678,14 @@ class BatchCropAnything:
|
|
|
1646
1678
|
border_width=1,
|
|
1647
1679
|
opacity=0.8,
|
|
1648
1680
|
)
|
|
1681
|
+
with contextlib.suppress(AttributeError, ValueError):
|
|
1682
|
+
points_layer.mouse_drag_callbacks.remove(
|
|
1683
|
+
self._on_points_clicked
|
|
1684
|
+
)
|
|
1685
|
+
points_layer.mouse_drag_callbacks.append(
|
|
1686
|
+
self._on_points_clicked
|
|
1687
|
+
)
|
|
1688
|
+
|
|
1649
1689
|
self.obj_points[current_obj_id] = [[x, y]]
|
|
1650
1690
|
self.obj_labels[current_obj_id] = [point_label]
|
|
1651
1691
|
else:
|
|
@@ -1923,6 +1963,14 @@ class BatchCropAnything:
|
|
|
1923
1963
|
opacity=0.8,
|
|
1924
1964
|
)
|
|
1925
1965
|
|
|
1966
|
+
with contextlib.suppress(AttributeError, ValueError):
|
|
1967
|
+
self.points_layer.mouse_drag_callbacks.remove(
|
|
1968
|
+
self._on_points_clicked
|
|
1969
|
+
)
|
|
1970
|
+
self.points_layer.mouse_drag_callbacks.append(
|
|
1971
|
+
self._on_points_clicked
|
|
1972
|
+
)
|
|
1973
|
+
|
|
1926
1974
|
def create_label_table(self, parent_widget):
|
|
1927
1975
|
"""Create a table widget displaying all detected labels."""
|
|
1928
1976
|
# Create table widget
|
napari_tmidas/_file_selector.py
CHANGED
|
@@ -558,6 +558,10 @@ class ProcessingWorker(QThread):
|
|
|
558
558
|
self.stop_requested = False
|
|
559
559
|
self.thread_count = max(1, (os.cpu_count() or 4) - 1) # Default value
|
|
560
560
|
|
|
561
|
+
def stop(self):
|
|
562
|
+
"""Request the worker to stop processing"""
|
|
563
|
+
self.stop_requested = True
|
|
564
|
+
|
|
561
565
|
def run(self):
|
|
562
566
|
"""Process files in a separate thread"""
|
|
563
567
|
# Track processed files
|
|
@@ -653,9 +657,12 @@ class ProcessingWorker(QThread):
|
|
|
653
657
|
# Generate new filename base
|
|
654
658
|
filename = os.path.basename(filepath)
|
|
655
659
|
name, ext = os.path.splitext(filename)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
660
|
+
if name.endswith(self.input_suffix):
|
|
661
|
+
new_filename_base = (
|
|
662
|
+
name[: -len(self.input_suffix)] + self.output_suffix
|
|
663
|
+
)
|
|
664
|
+
else:
|
|
665
|
+
new_filename_base = name + self.output_suffix
|
|
659
666
|
|
|
660
667
|
# Check if the first dimension should be treated as channels
|
|
661
668
|
# If processed_image has more dimensions than the original image,
|
|
@@ -779,13 +786,8 @@ class ProcessingWorker(QThread):
|
|
|
779
786
|
"labels" in new_filename_base
|
|
780
787
|
or "semantic" in new_filename_base
|
|
781
788
|
):
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
save_dtype = np.uint8
|
|
785
|
-
elif data_max <= 65535:
|
|
786
|
-
save_dtype = np.uint16
|
|
787
|
-
else:
|
|
788
|
-
save_dtype = np.uint32
|
|
789
|
+
|
|
790
|
+
save_dtype = np.uint32
|
|
789
791
|
|
|
790
792
|
print(
|
|
791
793
|
f"Saving label image as {save_dtype.__name__} with bigtiff={use_bigtiff}"
|
napari_tmidas/_registry.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Registry for batch processing functions.
|
|
4
4
|
"""
|
|
5
|
+
import threading
|
|
5
6
|
from typing import Any, Dict, List, Optional
|
|
6
7
|
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ class BatchProcessingRegistry:
|
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
_processing_functions = {}
|
|
15
|
+
_lock = threading.RLock() # Add thread lock
|
|
14
16
|
|
|
15
17
|
@classmethod
|
|
16
18
|
def register(
|
|
@@ -43,26 +45,25 @@ class BatchProcessingRegistry:
|
|
|
43
45
|
parameters = {}
|
|
44
46
|
|
|
45
47
|
def decorator(func):
|
|
46
|
-
cls.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
with cls._lock: # Thread-safe registration
|
|
49
|
+
cls._processing_functions[name] = {
|
|
50
|
+
"func": func,
|
|
51
|
+
"suffix": suffix,
|
|
52
|
+
"description": description,
|
|
53
|
+
"parameters": parameters,
|
|
54
|
+
}
|
|
52
55
|
return func
|
|
53
56
|
|
|
54
57
|
return decorator
|
|
55
58
|
|
|
56
59
|
@classmethod
|
|
57
60
|
def get_function_info(cls, name: str) -> Optional[dict]:
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return cls._processing_functions.get(name)
|
|
61
|
+
"""Thread-safe retrieval"""
|
|
62
|
+
with cls._lock:
|
|
63
|
+
return cls._processing_functions.get(name)
|
|
62
64
|
|
|
63
65
|
@classmethod
|
|
64
66
|
def list_functions(cls) -> List[str]:
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return list(cls._processing_functions.keys())
|
|
67
|
+
"""Thread-safe listing"""
|
|
68
|
+
with cls._lock:
|
|
69
|
+
return list(cls._processing_functions.keys())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# src/napari_tmidas/_tests/test_file_selector.py
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from napari_tmidas._file_selector import ProcessingWorker, file_selector
|
|
9
|
+
from napari_tmidas._registry import BatchProcessingRegistry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestProcessingWorker:
|
|
13
|
+
def setup_method(self):
|
|
14
|
+
"""Setup test environment"""
|
|
15
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
16
|
+
BatchProcessingRegistry._processing_functions.clear()
|
|
17
|
+
|
|
18
|
+
# Register a test function
|
|
19
|
+
@BatchProcessingRegistry.register(name="Test Process", suffix="_proc")
|
|
20
|
+
def test_process(image):
|
|
21
|
+
return image * 2
|
|
22
|
+
|
|
23
|
+
self.test_func = BatchProcessingRegistry.get_function_info(
|
|
24
|
+
"Test Process"
|
|
25
|
+
)["func"]
|
|
26
|
+
|
|
27
|
+
def teardown_method(self):
|
|
28
|
+
"""Cleanup"""
|
|
29
|
+
import shutil
|
|
30
|
+
|
|
31
|
+
shutil.rmtree(self.temp_dir)
|
|
32
|
+
|
|
33
|
+
def test_process_file(self):
|
|
34
|
+
"""Test processing a single file"""
|
|
35
|
+
# Create test image
|
|
36
|
+
test_image = np.random.rand(100, 100)
|
|
37
|
+
input_path = os.path.join(self.temp_dir, "test.tif")
|
|
38
|
+
|
|
39
|
+
import tifffile
|
|
40
|
+
|
|
41
|
+
tifffile.imwrite(input_path, test_image)
|
|
42
|
+
|
|
43
|
+
# Create worker
|
|
44
|
+
worker = ProcessingWorker(
|
|
45
|
+
[input_path], self.test_func, {}, self.temp_dir, "", "_proc"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Process file
|
|
49
|
+
result = worker.process_file(input_path)
|
|
50
|
+
|
|
51
|
+
assert result is not None
|
|
52
|
+
assert "original_file" in result
|
|
53
|
+
assert "processed_file" in result
|
|
54
|
+
assert os.path.exists(result["processed_file"])
|
|
55
|
+
|
|
56
|
+
def test_multi_channel_output(self):
|
|
57
|
+
"""Test processing that outputs multiple channels"""
|
|
58
|
+
|
|
59
|
+
@BatchProcessingRegistry.register(
|
|
60
|
+
name="Split Channels", suffix="_split"
|
|
61
|
+
)
|
|
62
|
+
def split_channels(image):
|
|
63
|
+
return np.stack([image, image * 2, image * 3])
|
|
64
|
+
|
|
65
|
+
test_image = np.random.rand(100, 100)
|
|
66
|
+
input_path = os.path.join(self.temp_dir, "test.tif")
|
|
67
|
+
|
|
68
|
+
import tifffile
|
|
69
|
+
|
|
70
|
+
tifffile.imwrite(input_path, test_image)
|
|
71
|
+
|
|
72
|
+
func_info = BatchProcessingRegistry.get_function_info("Split Channels")
|
|
73
|
+
worker = ProcessingWorker(
|
|
74
|
+
[input_path], func_info["func"], {}, self.temp_dir, "", "_split"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
result = worker.process_file(input_path)
|
|
78
|
+
|
|
79
|
+
assert "processed_files" in result
|
|
80
|
+
assert len(result["processed_files"]) == 3
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestFileSelector:
|
|
84
|
+
def test_file_selector_widget_creation(self):
|
|
85
|
+
"""Test that file selector widget is created properly"""
|
|
86
|
+
viewer_mock = Mock()
|
|
87
|
+
|
|
88
|
+
# Test the widget can be called
|
|
89
|
+
result = file_selector(viewer_mock, "/tmp", ".tif")
|
|
90
|
+
assert isinstance(result, list)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# src/napari_tmidas/_tests/test_registry.py
|
|
2
|
+
from napari_tmidas._registry import BatchProcessingRegistry
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestBatchProcessingRegistry:
|
|
6
|
+
def setup_method(self):
|
|
7
|
+
"""Clear registry before each test"""
|
|
8
|
+
BatchProcessingRegistry._processing_functions.clear()
|
|
9
|
+
|
|
10
|
+
def test_register_function(self):
|
|
11
|
+
"""Test registering a processing function"""
|
|
12
|
+
|
|
13
|
+
@BatchProcessingRegistry.register(
|
|
14
|
+
name="Test Function",
|
|
15
|
+
suffix="_test",
|
|
16
|
+
description="Test description",
|
|
17
|
+
parameters={"param1": {"type": int, "default": 5}},
|
|
18
|
+
)
|
|
19
|
+
def test_func(image, param1=5):
|
|
20
|
+
return image + param1
|
|
21
|
+
|
|
22
|
+
assert "Test Function" in BatchProcessingRegistry.list_functions()
|
|
23
|
+
info = BatchProcessingRegistry.get_function_info("Test Function")
|
|
24
|
+
assert info["suffix"] == "_test"
|
|
25
|
+
assert info["description"] == "Test description"
|
|
26
|
+
assert info["func"] == test_func
|
|
27
|
+
|
|
28
|
+
def test_list_functions(self):
|
|
29
|
+
"""Test listing registered functions"""
|
|
30
|
+
|
|
31
|
+
@BatchProcessingRegistry.register(name="Func1")
|
|
32
|
+
def func1(image):
|
|
33
|
+
return image
|
|
34
|
+
|
|
35
|
+
@BatchProcessingRegistry.register(name="Func2")
|
|
36
|
+
def func2(image):
|
|
37
|
+
return image
|
|
38
|
+
|
|
39
|
+
functions = BatchProcessingRegistry.list_functions()
|
|
40
|
+
assert len(functions) == 2
|
|
41
|
+
assert "Func1" in functions
|
|
42
|
+
assert "Func2" in functions
|
|
43
|
+
|
|
44
|
+
def test_thread_safety(self):
|
|
45
|
+
"""Test thread-safe registration"""
|
|
46
|
+
import threading
|
|
47
|
+
|
|
48
|
+
results = []
|
|
49
|
+
|
|
50
|
+
def register_func(i):
|
|
51
|
+
@BatchProcessingRegistry.register(name=f"ThreadFunc{i}")
|
|
52
|
+
def func(image):
|
|
53
|
+
return image
|
|
54
|
+
|
|
55
|
+
results.append(i)
|
|
56
|
+
|
|
57
|
+
threads = [
|
|
58
|
+
threading.Thread(target=register_func, args=(i,))
|
|
59
|
+
for i in range(10)
|
|
60
|
+
]
|
|
61
|
+
for t in threads:
|
|
62
|
+
t.start()
|
|
63
|
+
for t in threads:
|
|
64
|
+
t.join()
|
|
65
|
+
|
|
66
|
+
assert len(results) == 10
|
|
67
|
+
assert len(BatchProcessingRegistry.list_functions()) == 10
|
napari_tmidas/_version.py
CHANGED
|
@@ -252,16 +252,35 @@ def trackastra_tracking(
|
|
|
252
252
|
|
|
253
253
|
temp_dir = Path(os.path.dirname(img_path))
|
|
254
254
|
|
|
255
|
-
# Save the mask data
|
|
256
|
-
mask_path = img_path.replace(".tif", "_labels.tif")
|
|
257
255
|
# Create the tracking script
|
|
258
256
|
script_path = temp_dir / "run_tracking.py"
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
# Save the mask data
|
|
258
|
+
# For label images, use the original path as mask_path
|
|
259
|
+
if label_pattern in os.path.basename(img_path):
|
|
260
|
+
mask_path = img_path
|
|
261
|
+
# Find corresponding raw image by removing the label pattern
|
|
262
|
+
raw_base = os.path.basename(img_path).replace(label_pattern, "")
|
|
263
|
+
raw_path = os.path.join(os.path.dirname(img_path), raw_base + ".tif")
|
|
264
|
+
if not os.path.exists(raw_path):
|
|
265
|
+
print(f"Warning: Could not find raw image for {img_path}")
|
|
266
|
+
raw_path = img_path # Fallback to using label as input
|
|
267
|
+
else:
|
|
268
|
+
# For raw images, find the corresponding label image
|
|
269
|
+
raw_path = img_path
|
|
270
|
+
base_name = os.path.basename(img_path).replace(".tif", "")
|
|
271
|
+
mask_path = os.path.join(
|
|
272
|
+
os.path.dirname(img_path), base_name + label_pattern
|
|
273
|
+
)
|
|
274
|
+
if not os.path.exists(mask_path):
|
|
275
|
+
print(f"No label file found for {img_path}")
|
|
276
|
+
return image
|
|
277
|
+
|
|
278
|
+
output_path = temp_dir / os.path.basename(mask_path).replace(
|
|
279
|
+
label_pattern, "_tracked.tif"
|
|
261
280
|
)
|
|
262
281
|
|
|
263
282
|
script_content = create_trackastra_script(
|
|
264
|
-
str(
|
|
283
|
+
str(raw_path), str(mask_path), model, mode, str(output_path)
|
|
265
284
|
)
|
|
266
285
|
|
|
267
286
|
with open(script_path, "w") as f:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-tmidas
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A plugin for batch processing of confocal and whole-slide microscopy images of biological tissues
|
|
5
5
|
Author: Marco Meer
|
|
6
6
|
Author-email: marco.meer@pm.me
|
|
@@ -48,13 +48,13 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
48
48
|
Classifier: Programming Language :: Python :: 3.9
|
|
49
49
|
Classifier: Programming Language :: Python :: 3.10
|
|
50
50
|
Classifier: Programming Language :: Python :: 3.11
|
|
51
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
52
51
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
53
52
|
Requires-Python: >=3.9
|
|
54
53
|
Description-Content-Type: text/markdown
|
|
55
54
|
License-File: LICENSE
|
|
56
55
|
Requires-Dist: numpy
|
|
57
56
|
Requires-Dist: magicgui
|
|
57
|
+
Requires-Dist: tqdm
|
|
58
58
|
Requires-Dist: qtpy
|
|
59
59
|
Requires-Dist: scikit-image
|
|
60
60
|
Requires-Dist: pyqt5
|
|
@@ -66,6 +66,14 @@ Requires-Dist: torch
|
|
|
66
66
|
Requires-Dist: torchvision
|
|
67
67
|
Requires-Dist: timm
|
|
68
68
|
Requires-Dist: opencv-python
|
|
69
|
+
Requires-Dist: cmake
|
|
70
|
+
Requires-Dist: nd2
|
|
71
|
+
Requires-Dist: pylibCZIrw
|
|
72
|
+
Requires-Dist: readlif
|
|
73
|
+
Requires-Dist: tiffslide
|
|
74
|
+
Requires-Dist: hydra-core
|
|
75
|
+
Requires-Dist: eva-decord
|
|
76
|
+
Requires-Dist: acquifer-napari
|
|
69
77
|
Provides-Extra: testing
|
|
70
78
|
Requires-Dist: tox; extra == "testing"
|
|
71
79
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -90,9 +98,11 @@ Currently, napari-tmidas provides pipelines as widgets for batch image conversio
|
|
|
90
98
|
|
|
91
99
|
## Installation
|
|
92
100
|
|
|
101
|
+
(Video installation guides: https://www.youtube.com/@macromeer/videos)
|
|
102
|
+
|
|
93
103
|
First, install Napari in a virtual environment:
|
|
94
104
|
|
|
95
|
-
mamba create -y -n napari-tmidas -c conda-forge python=3.11
|
|
105
|
+
mamba create -y -n napari-tmidas -c conda-forge python=3.11
|
|
96
106
|
mamba activate napari-tmidas
|
|
97
107
|
python -m pip install "napari[all]"
|
|
98
108
|
|
|
@@ -100,34 +110,28 @@ Now you can install `napari-tmidas` via [pip]:
|
|
|
100
110
|
|
|
101
111
|
pip install napari-tmidas
|
|
102
112
|
|
|
103
|
-
It is recommended to install the latest development version
|
|
113
|
+
It is recommended though to install the **latest development version**. Please also execute this command from time to time in the activated environment to benefit from newly added features:
|
|
104
114
|
|
|
105
115
|
pip install git+https://github.com/macromeer/napari-tmidas.git
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
To use the Batch Microscopy Image Conversion pipeline, we need some libraries to read microscopy formats:
|
|
110
|
-
|
|
111
|
-
# mamba activate napari-tmidas
|
|
112
|
-
pip install nd2 readlif tiffslide pylibCZIrw acquifer-napari
|
|
117
|
+
To use the Batch Crop Anything pipeline, we need to install **Segment Anything 2** (2D/3D):
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
cd /opt # if the folder does not exist: mkdir /opt && cd /opt
|
|
120
|
+
git clone https://github.com/facebookresearch/sam2.git && cd sam2
|
|
121
|
+
pip install -e .
|
|
122
|
+
curl -L https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt -o checkpoints/sam2.1_hiera_large.pt
|
|
123
|
+
mamba install -c conda-forge ffmpeg # we also need ffmpeg
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
brew install zstd # for macOS
|
|
118
|
-
choco install zstandard # for Windows
|
|
125
|
+
If you want to batch compress image data using [Zstandard](https://github.com/facebook/zstd), use the package manager of your operating system to install it:
|
|
119
126
|
|
|
120
|
-
|
|
127
|
+
~~sudo apt-get install zstd~~ # Pre-installed on Linux :man_shrugging:
|
|
121
128
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
git clone https://github.com/facebookresearch/sam2.git && cd sam2
|
|
125
|
-
pip install -e .
|
|
126
|
-
wget https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt -P checkpoints/
|
|
127
|
-
pip install decord
|
|
129
|
+
brew install zstd # for macOS (requires [Homebrew](https://brew.sh/)
|
|
130
|
+
pip install zstandard # Windows with Python >= 3.7
|
|
128
131
|
|
|
129
132
|
|
|
130
133
|
|
|
134
|
+
And you are done!
|
|
131
135
|
|
|
132
136
|
## Usage
|
|
133
137
|
|
|
@@ -166,18 +170,27 @@ You can start this pipeline via `Plugins > T-MIDAS > Batch Microscopy Image Conv
|
|
|
166
170
|
|
|
167
171
|
Note that whenever you click on an `Original File` or `Processed File` in the table, it will replace the one that is currently shown in the viewer. So naturally, you'd first select the original image, and then the processed image to correctly see the image pair that you want to inspect.
|
|
168
172
|
|
|
173
|
+
|
|
174
|
+
#### Processing Function Credits
|
|
175
|
+
|
|
176
|
+
The image processing capabilities are powered by several excellent open-source tools:
|
|
177
|
+
- [Cellpose 4](https://github.com/MouseLand/cellpose): Advanced cell segmentation
|
|
178
|
+
- [Trackastra](https://github.com/weigertlab/trackastra): Cell tracking and analysis
|
|
179
|
+
- [CAREamics](https://github.com/CAREamics/careamics): Content-aware image restoration and enhancement
|
|
180
|
+
|
|
169
181
|
### Batch Label Inspection
|
|
170
182
|
If you have already segmented a folder full of images and now you want to maybe inspect and edit each label image, you can use the `Plugins > T-MIDAS > Batch Label Inspection`, which automatically saves your changes to the existing label image once you click the `Save Changes and Continue` button (bottom right).
|
|
171
183
|
|
|
172
184
|
<img src="https://github.com/user-attachments/assets/0bf8c6ae-4212-449d-8183-e91b23ba740e" alt="Batch Label Inspection Widget" style="width:75%; height:auto;">
|
|
173
185
|
|
|
174
|
-
|
|
175
186
|
### Crop Anything
|
|
176
|
-
This pipeline combines the Segment Anything Model (SAM) for automatic object detection with an interactive interface for selecting and cropping multiple objects from images. To launch the widget, open `Plugins > T-MIDAS > Batch Crop Anything`.
|
|
187
|
+
This pipeline combines the Segment Anything Model (SAM) for automatic object detection with an interactive interface for selecting and cropping multiple objects from images. To launch the widget, open `Plugins > T-MIDAS > Batch Crop Anything`. Cropping works like this: Enter 2D view and go to the first z slice where the object to be cropped is appearing. Activate/select the points layer and click on the object. Terminal shows progress. You can then proceed to select another object (always do this in 2D mode)
|
|
177
188
|
|
|
178
189
|
<img src="https://github.com/user-attachments/assets/6d72c2a2-1064-4a27-b398-a9b86fcbc443" alt="Crop Anything Widget" style="width:75%; height:auto;">
|
|
179
190
|
|
|
180
191
|
|
|
192
|
+
|
|
193
|
+
|
|
181
194
|
### ROI Colocalization
|
|
182
195
|
This pipeline quantifies colocalization between labeled regions of interest (ROIs) across multiple image channels. It determines the extent of overlap between ROIs in a reference channel and those in one or two other channels. The output is a table of colocalization counts. Optionally, the size of reference channel ROIs, as well as the total or median size of colocalizing ROIs in the other channels, can be included. Colocalization is determined using Boolean masking. The number of colocalizing instances is determined by counting unique label IDs within the overlapping regions. Typically, the reference channel contains larger structures, while other channels contain smaller, potentially nested, structures. For example, the reference channel might contain cell bodies, with the second and third channels containing nuclei and sub-nuclear objects, respectively.
|
|
183
196
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
napari_tmidas/__init__.py,sha256=YNBLESwk8jr_TlDdkSC1CwH0tf0CKHF1i2_efzLjdpk,589
|
|
2
|
-
napari_tmidas/_crop_anything.py,sha256=
|
|
2
|
+
napari_tmidas/_crop_anything.py,sha256=2UMUddQ6mdX96FiJPKpQnG1P1atFrxlz41JYWM3933k,103088
|
|
3
3
|
napari_tmidas/_file_conversion.py,sha256=V6evJmggUwOFzJO203Y5ltboHXEWNJQckZPedGRkrLI,72203
|
|
4
|
-
napari_tmidas/_file_selector.py,sha256=
|
|
4
|
+
napari_tmidas/_file_selector.py,sha256=tuWxfmHuvILp70cGl6bsEyhRxKNum45GKD6-st-1hnM,43056
|
|
5
5
|
napari_tmidas/_label_inspection.py,sha256=74V36y5EnGs0vWK1FC7Kui4CPLBW_SIg885PSKeZsJ4,9184
|
|
6
6
|
napari_tmidas/_reader.py,sha256=A9_hdDxtVkVGmbOsbqgnARCSvpEh7GGPo7ylzmbnu8o,2485
|
|
7
|
-
napari_tmidas/_registry.py,sha256=
|
|
7
|
+
napari_tmidas/_registry.py,sha256=fxBPLFCvXtjSHfcVIRb6KI9DkqIWRUpPPg_3pD8sXns,2110
|
|
8
8
|
napari_tmidas/_roi_colocalization.py,sha256=OVjdHvtFN07DgrtTX8uqbrxZL6jVwl2L3klorgW2C9k,43196
|
|
9
9
|
napari_tmidas/_sample_data.py,sha256=khuv1jemz_fCjqNwEKMFf83Ju0EN4S89IKydsUMmUxw,645
|
|
10
|
-
napari_tmidas/_version.py,sha256=
|
|
10
|
+
napari_tmidas/_version.py,sha256=OjGGK5TcHVG44Y62aAqeJH4CskkZoY9ydbHOtCDew50,511
|
|
11
11
|
napari_tmidas/_widget.py,sha256=u9uf9WILAwZg_InhFyjWInY4ej1TV1a59dR8Fe3vNF8,4794
|
|
12
12
|
napari_tmidas/_writer.py,sha256=wbVfHFjjHdybSg37VR4lVmL-kdCkDZsUPDJ66AVLaFQ,1941
|
|
13
13
|
napari_tmidas/napari.yaml,sha256=1Am1dA0-ZtCXk6veIT6jrMz3zwQ7dF8_p9tZTFx_vTg,2641
|
|
14
14
|
napari_tmidas/_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
napari_tmidas/_tests/test_file_selector.py,sha256=Sbu0BCXTaQAeUJLtOVjIC87AUARbi8J0bXBlMJe53ew,2687
|
|
15
16
|
napari_tmidas/_tests/test_reader.py,sha256=gN_2StATLZYUL56X27ImJTVru_qSoFiY4vtgajcx3H0,975
|
|
17
|
+
napari_tmidas/_tests/test_registry.py,sha256=DSI6NdmuIS1sYAa3LrVge0rOS5Ycb3TXFXxol3vDyRA,2061
|
|
16
18
|
napari_tmidas/_tests/test_sample_data.py,sha256=D1HU_C3hWpO3mlSW_7Z94xaYHDtxz0XUrMjQoYop9Ag,104
|
|
17
19
|
napari_tmidas/_tests/test_widget.py,sha256=I_d-Cra_CTcS0QdMItg_HMphvhj0XCx81JnFyCHk9lg,2204
|
|
18
20
|
napari_tmidas/_tests/test_writer.py,sha256=4_MlZM9a5So74J16_4tIOJc6pwTOw9R0-oAE_YioIx4,122
|
|
@@ -29,10 +31,10 @@ napari_tmidas/processing_functions/sam2_mp4.py,sha256=NF0dWar2uyP_yQWxC8e08J6198
|
|
|
29
31
|
napari_tmidas/processing_functions/scipy_filters.py,sha256=kKpDAlQQ0ZNbkt77QUWi-Bwolk6MMDvtG_bZJV3MjOo,1612
|
|
30
32
|
napari_tmidas/processing_functions/skimage_filters.py,sha256=tSBx0nal88ixxVbu5o7ojTn90HgsUTt-aA_T6XLvmyY,16320
|
|
31
33
|
napari_tmidas/processing_functions/timepoint_merger.py,sha256=DwL5vZBSplXt9dBBrKtMm9aH_NvT3mY7cdbeGg2OU_Y,16567
|
|
32
|
-
napari_tmidas/processing_functions/trackastra_tracking.py,sha256=
|
|
33
|
-
napari_tmidas-0.2.
|
|
34
|
-
napari_tmidas-0.2.
|
|
35
|
-
napari_tmidas-0.2.
|
|
36
|
-
napari_tmidas-0.2.
|
|
37
|
-
napari_tmidas-0.2.
|
|
38
|
-
napari_tmidas-0.2.
|
|
34
|
+
napari_tmidas/processing_functions/trackastra_tracking.py,sha256=IkFk5HoEZmKdcu5jXri4WMhHN1KTADDMxSpeYfPgSbo,9976
|
|
35
|
+
napari_tmidas-0.2.2.dist-info/licenses/LICENSE,sha256=tSjiOqj57exmEIfP2YVPCEeQf0cH49S6HheQR8IiY3g,1485
|
|
36
|
+
napari_tmidas-0.2.2.dist-info/METADATA,sha256=cY8vgH2bnjC9elLZ0uxIBVJeTckVbg7UxnV2L5u4wTc,12742
|
|
37
|
+
napari_tmidas-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
napari_tmidas-0.2.2.dist-info/entry_points.txt,sha256=fbVjzbJTm4aDMIBtel1Lyqvq-CwXY7wmCOo_zJ-jtRY,60
|
|
39
|
+
napari_tmidas-0.2.2.dist-info/top_level.txt,sha256=63ybdxCZ4SeT13f_Ou4TsivGV_2Gtm_pJOXToAt30_E,14
|
|
40
|
+
napari_tmidas-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|