napari-tmidas 0.1.8__py3-none-any.whl → 0.1.9__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.
@@ -0,0 +1,172 @@
1
+ # processing_functions/cellpose_env_manager.py
2
+ """
3
+ This module manages a dedicated virtual environment for Cellpose.
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ import shutil
9
+ import subprocess
10
+ import tempfile
11
+ import venv
12
+
13
+ import tifffile
14
+
15
+ # Define the environment directory in user's home folder
16
+ ENV_DIR = os.path.join(
17
+ os.path.expanduser("~"), ".napari-tmidas", "envs", "cellpose"
18
+ )
19
+
20
+
21
+ def is_cellpose_installed():
22
+ """Check if cellpose is installed in the current environment."""
23
+ try:
24
+ import importlib.util
25
+
26
+ return importlib.util.find_spec("cellpose") is not None
27
+ except ImportError:
28
+ return False
29
+
30
+
31
+ def is_env_created():
32
+ """Check if the dedicated environment exists."""
33
+ env_python = get_env_python_path()
34
+ return os.path.exists(env_python)
35
+
36
+
37
+ def get_env_python_path():
38
+ """Get the path to the Python executable in the environment."""
39
+ if platform.system() == "Windows":
40
+ return os.path.join(ENV_DIR, "Scripts", "python.exe")
41
+ else:
42
+ return os.path.join(ENV_DIR, "bin", "python")
43
+
44
+
45
+ def create_cellpose_env():
46
+ """Create a dedicated virtual environment for Cellpose."""
47
+ # Ensure the environment directory exists
48
+ os.makedirs(os.path.dirname(ENV_DIR), exist_ok=True)
49
+
50
+ # Remove existing environment if it exists
51
+ if os.path.exists(ENV_DIR):
52
+ shutil.rmtree(ENV_DIR)
53
+
54
+ print(f"Creating Cellpose environment at {ENV_DIR}...")
55
+
56
+ # Create a new virtual environment
57
+ venv.create(ENV_DIR, with_pip=True)
58
+
59
+ # Path to the Python executable in the new environment
60
+ env_python = get_env_python_path()
61
+
62
+ # Install numpy first to ensure correct version
63
+ print("Installing NumPy...")
64
+ subprocess.check_call(
65
+ [env_python, "-m", "pip", "install", "numpy>=1.24,<1.25"]
66
+ )
67
+
68
+ # Install cellpose and other dependencies
69
+ print("Installing Cellpose in the dedicated environment...")
70
+ subprocess.check_call([env_python, "-m", "pip", "install", "cellpose"])
71
+
72
+ # Install tifffile for image handling
73
+ subprocess.check_call([env_python, "-m", "pip", "install", "tifffile"])
74
+
75
+ print("Cellpose environment created successfully.")
76
+ return env_python
77
+
78
+
79
+ def run_cellpose_in_env(func_name, args_dict):
80
+ """
81
+ Run Cellpose in a dedicated environment with minimal complexity.
82
+
83
+ Parameters:
84
+ -----------
85
+ func_name : str
86
+ Name of the Cellpose function to run (currently unused)
87
+ args_dict : dict
88
+ Dictionary of arguments for Cellpose segmentation
89
+
90
+ Returns:
91
+ --------
92
+ numpy.ndarray
93
+ Segmentation masks
94
+ """
95
+ # Ensure the environment exists
96
+ if not is_env_created():
97
+ create_cellpose_env()
98
+
99
+ # Prepare temporary files
100
+ with tempfile.NamedTemporaryFile(
101
+ suffix=".tif", delete=False
102
+ ) as input_file, tempfile.NamedTemporaryFile(
103
+ suffix=".tif", delete=False
104
+ ) as output_file, tempfile.NamedTemporaryFile(
105
+ mode="w", suffix=".py", delete=False
106
+ ) as script_file:
107
+
108
+ # Save input image
109
+ tifffile.imwrite(input_file.name, args_dict["image"])
110
+
111
+ # Prepare a temporary script to run Cellpose
112
+ script = f"""
113
+ import numpy as np
114
+ from cellpose import models
115
+ import tifffile
116
+
117
+ # Load image
118
+ image = tifffile.imread('{input_file.name}')
119
+
120
+ # Create and run model
121
+ model = models.Cellpose(
122
+ gpu={args_dict.get('use_gpu', True)},
123
+ model_type='{args_dict.get('model_type', 'cyto3')}'
124
+ )
125
+
126
+ # Perform segmentation
127
+ masks, *_ = model.eval(
128
+ image,
129
+ diameter={args_dict.get('diameter', 30.0)},
130
+ flow_threshold={args_dict.get('flow_threshold', 0.4)},
131
+ channels={args_dict.get('channels', [0, 0])},
132
+ do_3D={args_dict.get('do_3D', False)},
133
+ z_axis={args_dict.get('z_axis', 0)} if {args_dict.get('do_3D', False)} else None
134
+ )
135
+
136
+ # Save results
137
+ tifffile.imwrite('{output_file.name}', masks)
138
+ """
139
+
140
+ # Write script
141
+ script_file.write(script)
142
+ script_file.flush()
143
+
144
+ try:
145
+ # Run the script in the dedicated environment
146
+ env_python = get_env_python_path()
147
+ result = subprocess.run(
148
+ [env_python, script_file.name], capture_output=True, text=True
149
+ )
150
+
151
+ # Check for errors
152
+ if result.returncode != 0:
153
+ print("Stdout:", result.stdout)
154
+ print("Stderr:", result.stderr)
155
+ raise RuntimeError(
156
+ f"Cellpose segmentation failed: {result.stderr}"
157
+ )
158
+
159
+ # Read and return the results
160
+ return tifffile.imread(output_file.name)
161
+
162
+ except (RuntimeError, subprocess.CalledProcessError) as e:
163
+ print(f"Error in Cellpose segmentation: {e}")
164
+ raise
165
+
166
+ finally:
167
+ # Clean up temporary files using contextlib.suppress
168
+ from contextlib import suppress
169
+
170
+ for fname in [input_file.name, output_file.name, script_file.name]:
171
+ with suppress(OSError, FileNotFoundError):
172
+ os.unlink(fname)
@@ -0,0 +1,511 @@
1
+ # processing_functions/cellpose_segmentation.py
2
+ """
3
+ Processing functions for automatic instance segmentation using Cellpose.
4
+
5
+ This module provides functionality to automatically segment cells or nuclei in images
6
+ using the Cellpose deep learning-based segmentation toolkit. It supports both 2D and 3D images,
7
+ various dimension orders, and handles time series data.
8
+
9
+ Note: This requires the cellpose library to be installed.
10
+ """
11
+ import os
12
+ import sys
13
+
14
+ import numpy as np
15
+
16
+ # Import the environment manager
17
+ from napari_tmidas.processing_functions.cellpose_env_manager import (
18
+ create_cellpose_env,
19
+ is_env_created,
20
+ run_cellpose_in_env,
21
+ )
22
+
23
+ # Check if cellpose is directly available in this environment
24
+ try:
25
+ from cellpose import core, models
26
+
27
+ CELLPOSE_AVAILABLE = True
28
+ USE_GPU = core.use_gpu()
29
+ USE_DEDICATED_ENV = False
30
+ print("Cellpose found in current environment. Using native import.")
31
+ except ImportError:
32
+ CELLPOSE_AVAILABLE = False
33
+ USE_GPU = False
34
+ USE_DEDICATED_ENV = True
35
+ print(
36
+ "Cellpose not found in current environment. Will use dedicated environment."
37
+ )
38
+
39
+ from napari_tmidas._registry import BatchProcessingRegistry
40
+
41
+
42
+ def transpose_dimensions(img, dim_order):
43
+ """
44
+ Transpose image dimensions to match expected Cellpose input.
45
+
46
+ Parameters:
47
+ -----------
48
+ img : numpy.ndarray
49
+ Input image
50
+ dim_order : str
51
+ Dimension order of the input image (e.g., 'ZYX', 'TZYX', 'YXC')
52
+
53
+ Returns:
54
+ --------
55
+ numpy.ndarray
56
+ Transposed image
57
+ str
58
+ New dimension order
59
+ bool
60
+ Whether the image is 3D
61
+ """
62
+ # Handle time dimension if present
63
+ has_time = "T" in dim_order
64
+
65
+ # Determine if the image is 3D (has Z dimension)
66
+ is_3d = "Z" in dim_order
67
+
68
+ # Standardize dimension order
69
+ if has_time:
70
+ # For time series, we want to end up with TZYX
71
+ target_dims = "TZYX"
72
+ transpose_order = [
73
+ dim_order.index(d) for d in target_dims if d in dim_order
74
+ ]
75
+ new_dim_order = "".join([dim_order[i] for i in transpose_order])
76
+ else:
77
+ # For single timepoints, we want ZYX
78
+ target_dims = "ZYX"
79
+ transpose_order = [
80
+ dim_order.index(d) for d in target_dims if d in dim_order
81
+ ]
82
+ new_dim_order = "".join([dim_order[i] for i in transpose_order])
83
+
84
+ # Perform the transpose
85
+ img_transposed = np.transpose(img, transpose_order)
86
+
87
+ return img_transposed, new_dim_order, is_3d
88
+
89
+
90
+ def run_cellpose(
91
+ img,
92
+ model,
93
+ channels,
94
+ diameter,
95
+ flow_threshold=0.4,
96
+ dim_order="ZYX",
97
+ max_pixels=4000000,
98
+ ):
99
+ """
100
+ Run Cellpose segmentation on an image.
101
+
102
+ Parameters:
103
+ -----------
104
+ img : numpy.ndarray
105
+ Input image
106
+ model : cellpose.models.Cellpose
107
+ Cellpose model to use
108
+ channels : list
109
+ Channels to use for segmentation [0,0] = grayscale, [1,0] = green channel, [2,0] = red channel
110
+ diameter : float
111
+ Diameter of objects to segment
112
+ flow_threshold : float
113
+ Flow threshold for Cellpose
114
+ dim_order : str
115
+ Dimension order of the input image
116
+ max_pixels : int
117
+ Maximum number of pixels to process (for 2D images)
118
+
119
+ Returns:
120
+ --------
121
+ numpy.ndarray
122
+ Segmented image with labels
123
+ """
124
+ # First check if the image is too large (for 2D images)
125
+ if len(img.shape) == 2 or (len(img.shape) == 3 and "C" in dim_order):
126
+ # For 2D images (potentially with channels)
127
+ height, width = img.shape[:2]
128
+ total_pixels = height * width
129
+ if total_pixels > max_pixels:
130
+ raise ValueError(
131
+ f"Image size ({height}x{width}={total_pixels} pixels) exceeds the "
132
+ f"maximum size of {max_pixels} pixels. Consider downsampling."
133
+ )
134
+
135
+ # Transpose to expected dimension order
136
+ img_transposed, new_dim_order, is_3d = transpose_dimensions(img, dim_order)
137
+
138
+ # Check if we have a time series
139
+ has_time = "T" in new_dim_order
140
+
141
+ if has_time:
142
+ # Handle time series - process each time point
143
+ n_timepoints = img_transposed.shape[0]
144
+ result = np.zeros(img_transposed.shape, dtype=np.uint32)
145
+
146
+ # Process each time point
147
+ for t in range(n_timepoints):
148
+ img_t = img_transposed[t]
149
+ mask, _, _, _ = model.eval(
150
+ img_t,
151
+ diameter=diameter,
152
+ flow_threshold=flow_threshold,
153
+ channels=channels,
154
+ z_axis=0 if is_3d else None,
155
+ do_3D=is_3d,
156
+ niter=2000, # Maximum iterations
157
+ )
158
+ result[t] = mask
159
+ else:
160
+ # Process single time point
161
+ result, _, _, _ = model.eval(
162
+ img_transposed,
163
+ diameter=diameter,
164
+ flow_threshold=flow_threshold,
165
+ channels=channels,
166
+ z_axis=0 if is_3d else None,
167
+ do_3D=is_3d,
168
+ niter=2000, # Maximum iterations
169
+ )
170
+
171
+ return result.astype(np.uint32)
172
+
173
+
174
+ @BatchProcessingRegistry.register(
175
+ name="Segment cells or nuclei (Cellpose)",
176
+ suffix="_labels",
177
+ description="Automatic instance segmentation using Cellpose 3.0 (dedicated environment)",
178
+ parameters={
179
+ "model_type": {
180
+ "type": str,
181
+ "default": "cyto3",
182
+ "description": "Cellpose model type: 'cyto'/'cyto2'/'cyto3' for cells, 'nuclei' for nuclei",
183
+ },
184
+ "diameter": {
185
+ "type": float,
186
+ "default": 40.0,
187
+ "min": 5.0,
188
+ "max": 1000.0,
189
+ "description": "Approximate diameter of objects to segment (pixels)",
190
+ },
191
+ "dim_order": {
192
+ "type": str,
193
+ "default": "YX",
194
+ "description": "Dimension order of the input (e.g., 'YX', 'ZYX', 'TZYX')",
195
+ },
196
+ "channel_1": {
197
+ "type": int,
198
+ "default": 0,
199
+ "min": 0,
200
+ "max": 3,
201
+ "description": "First channel: 0=grayscale, 1=green, 2=red, 3=blue",
202
+ },
203
+ "channel_2": {
204
+ "type": int,
205
+ "default": 0,
206
+ "min": 0,
207
+ "max": 3,
208
+ "description": "Second channel: 0=none, 1=green, 2=red, 3=blue",
209
+ },
210
+ "flow_threshold": {
211
+ "type": float,
212
+ "default": 0.4,
213
+ "min": 0.1,
214
+ "max": 0.9,
215
+ "description": "Flow threshold for Cellpose segmentation",
216
+ },
217
+ "force_dedicated_env": {
218
+ "type": bool,
219
+ "default": False,
220
+ "description": "Force using dedicated environment even if Cellpose is available",
221
+ },
222
+ },
223
+ )
224
+ def cellpose_segmentation(
225
+ image: np.ndarray,
226
+ model_type: str = "cyto3",
227
+ diameter: float = 40.0,
228
+ dim_order: str = "YX",
229
+ channel_1: int = 0,
230
+ channel_2: int = 0,
231
+ flow_threshold: float = 0.4,
232
+ force_dedicated_env: bool = False,
233
+ ) -> np.ndarray:
234
+ """
235
+ Run Cellpose segmentation on an image.
236
+
237
+ This function takes an image and performs automatic instance segmentation using
238
+ Cellpose. It supports both 2D and 3D images, various dimension orders, and handles
239
+ time series data.
240
+
241
+ If Cellpose is not available in the current environment, a dedicated virtual
242
+ environment will be created to run Cellpose.
243
+
244
+ Parameters:
245
+ -----------
246
+ image : numpy.ndarray
247
+ Input image
248
+ model_type : str
249
+ Cellpose model type: 'cyto'/'cyto2'/'cyto3' for cells, 'nuclei' for nuclei (default: "cyto3")
250
+ diameter : float
251
+ Approximate diameter of objects to segment in pixels (default: 40.0)
252
+ dim_order : str
253
+ Dimension order of the input (e.g., 'YX', 'ZYX', 'TZYX') (default: "YX")
254
+ channel_1 : int
255
+ First channel: 0=grayscale, 1=green, 2=red, 3=blue (default: 0)
256
+ channel_2 : int
257
+ Second channel: 0=none, 1=green, 2=red, 3=blue (default: 0)
258
+ flow_threshold : float
259
+ Flow threshold for Cellpose segmentation (default: 0.4)
260
+ force_dedicated_env : bool
261
+ Force using dedicated environment even if Cellpose is available (default: False)
262
+
263
+ Returns:
264
+ --------
265
+ numpy.ndarray
266
+ Segmented image with instance labels
267
+ """
268
+ # Convert channel parameters to Cellpose channels list
269
+ channels = [channel_1, channel_2]
270
+
271
+ # Validate parameters
272
+ valid_models = ["cyto", "cyto2", "cyto3", "nuclei"]
273
+ if model_type not in valid_models:
274
+ raise ValueError(
275
+ f"Invalid model_type: {model_type}. "
276
+ f"Must be one of: {', '.join(valid_models)}"
277
+ )
278
+
279
+ # Determine whether to use dedicated environment
280
+ use_env = force_dedicated_env or USE_DEDICATED_ENV
281
+
282
+ if use_env:
283
+ print("Using dedicated Cellpose environment...")
284
+
285
+ # First check if the environment exists, create if not
286
+ if not is_env_created():
287
+ print(
288
+ "Creating dedicated Cellpose environment (this may take a few minutes)..."
289
+ )
290
+ create_cellpose_env()
291
+ print("Environment created successfully.")
292
+
293
+ # Prepare arguments for the Cellpose function
294
+ args = {
295
+ "image": image,
296
+ "model_type": model_type,
297
+ "diameter": diameter,
298
+ "channels": channels,
299
+ "flow_threshold": flow_threshold,
300
+ "use_gpu": USE_GPU,
301
+ "do_3D": "Z" in dim_order,
302
+ "z_axis": 0 if "Z" in dim_order else None,
303
+ }
304
+
305
+ # Run Cellpose in the dedicated environment
306
+ print(f"Running Cellpose ({model_type}) in dedicated environment...")
307
+ result = run_cellpose_in_env("eval", args)
308
+ print(f"Segmentation complete. Found {np.max(result)} objects.")
309
+ return result
310
+
311
+ else:
312
+ print(f"Running Cellpose ({model_type}) in current environment...")
313
+ # Initialize Cellpose model in current environment
314
+ model = models.Cellpose(gpu=USE_GPU, model_type=model_type)
315
+
316
+ # Print status information
317
+ gpu_status = "GPU" if USE_GPU else "CPU"
318
+ print(f"Using Cellpose {model_type} model on {gpu_status}")
319
+ print(
320
+ f"Processing image with shape {image.shape}, dimension order: {dim_order}"
321
+ )
322
+
323
+ # Run segmentation
324
+ try:
325
+ result = run_cellpose(
326
+ image, model, channels, diameter, flow_threshold, dim_order
327
+ )
328
+
329
+ print(f"Segmentation complete. Found {np.max(result)} objects.")
330
+ return result
331
+
332
+ except (Exception, MemoryError) as e:
333
+ print(f"Error during segmentation in current environment: {str(e)}")
334
+
335
+ # If we haven't already tried using the dedicated environment, try that as a fallback
336
+ if not USE_DEDICATED_ENV and not force_dedicated_env:
337
+ print("Attempting fallback to dedicated Cellpose environment...")
338
+ try:
339
+ args = {
340
+ "image": image,
341
+ "model_type": model_type,
342
+ "diameter": diameter,
343
+ "channels": channels,
344
+ "flow_threshold": flow_threshold,
345
+ "use_gpu": USE_GPU,
346
+ "do_3D": "Z" in dim_order,
347
+ "z_axis": 0 if "Z" in dim_order else None,
348
+ }
349
+
350
+ if not is_env_created():
351
+ create_cellpose_env()
352
+
353
+ result = run_cellpose_in_env("eval", args)
354
+ print(f"Fallback successful. Found {np.max(result)} objects.")
355
+ return result
356
+ except (Exception, MemoryError) as fallback_e:
357
+ print(f"Fallback also failed: {str(fallback_e)}")
358
+ raise Exception(
359
+ f"Both direct and dedicated environment approaches failed: {str(e)} | {str(fallback_e)}"
360
+ ) from fallback_e
361
+ else:
362
+ raise
363
+
364
+
365
+ # Add a command-line function to run cellpose segmentation
366
+ def run_cellpose_segmentation():
367
+ """Run Cellpose segmentation from the command line."""
368
+ import argparse
369
+
370
+ from skimage.io import imread
371
+ from tifffile import imwrite
372
+ from tqdm import tqdm
373
+
374
+ # Parse arguments
375
+ parser = argparse.ArgumentParser(
376
+ description="Runs automatic mask generation on images using Cellpose."
377
+ )
378
+ parser.add_argument(
379
+ "--input", type=str, required=True, help="Path to input images."
380
+ )
381
+ parser.add_argument(
382
+ "--diameter", type=float, default=40.0, help="Diameter of objects."
383
+ )
384
+ parser.add_argument(
385
+ "--channels",
386
+ type=int,
387
+ nargs="+",
388
+ default=[0, 0],
389
+ help="Channels to use.",
390
+ )
391
+ parser.add_argument(
392
+ "--dim_order",
393
+ type=str,
394
+ default="ZYX",
395
+ help="Dimension order of the input images.",
396
+ )
397
+ parser.add_argument(
398
+ "--model_type",
399
+ type=str,
400
+ default="cyto3",
401
+ choices=["cyto", "cyto2", "cyto3", "nuclei"],
402
+ help="Model type: 'cyto'/'cyto2'/'cyto3' for cells, 'nuclei' for nuclei",
403
+ )
404
+ parser.add_argument(
405
+ "--flow_threshold",
406
+ type=float,
407
+ default=0.4,
408
+ help="Flow threshold for Cellpose (default: 0.4)",
409
+ )
410
+ parser.add_argument(
411
+ "--max_pixels",
412
+ type=int,
413
+ default=4000000,
414
+ help="Maximum number of pixels to process for 2D images (default: 4000000)",
415
+ )
416
+
417
+ args = parser.parse_args()
418
+
419
+ # Validate input folder
420
+ input_folder = args.input
421
+ if not os.path.isdir(input_folder):
422
+ print(
423
+ f"Error: The input folder '{input_folder}' does not exist or is not accessible."
424
+ )
425
+ return 1
426
+
427
+ # Find input files
428
+ input_files = [
429
+ f
430
+ for f in os.listdir(input_folder)
431
+ if f.endswith(".tif") and not f.endswith("_labels.tif")
432
+ ]
433
+
434
+ if not input_files:
435
+ print(f"No .tif files found in {input_folder}")
436
+ return 1
437
+
438
+ print(f"Found {len(input_files)} files to process")
439
+
440
+ # Check if Cellpose is available
441
+ if not CELLPOSE_AVAILABLE:
442
+ print(
443
+ "Error: Cellpose is not installed. Please install it with: pip install cellpose"
444
+ )
445
+ return 1
446
+
447
+ # Initialize model
448
+ model = models.Cellpose(gpu=USE_GPU, model_type=args.model_type)
449
+
450
+ # Print status
451
+ gpu_status = "GPU" if USE_GPU else "CPU"
452
+ print(f"Using Cellpose {args.model_type} model on {gpu_status}")
453
+
454
+ # Process each file
455
+ for input_file in tqdm(input_files, desc="Processing images"):
456
+ try:
457
+ # Check image size
458
+ img = imread(os.path.join(input_folder, input_file))
459
+
460
+ if len(img.shape) == 2 or (
461
+ len(img.shape) == 3 and "C" in args.dim_order
462
+ ):
463
+ # For 2D images (potentially with channels)
464
+ height, width = img.shape[:2]
465
+ total_pixels = height * width
466
+ if total_pixels > args.max_pixels:
467
+ print(
468
+ f"Skipping {input_file} as it exceeds the maximum size of {args.max_pixels} pixels."
469
+ )
470
+ continue
471
+
472
+ print(
473
+ "\nCheck if image shape corresponds to the dim order that you have given:"
474
+ )
475
+ print(
476
+ f"Image shape: {img.shape}, dimension order: {args.dim_order}\n"
477
+ )
478
+
479
+ # Run segmentation
480
+ result = run_cellpose(
481
+ img,
482
+ model,
483
+ args.channels,
484
+ args.diameter,
485
+ args.flow_threshold,
486
+ args.dim_order,
487
+ args.max_pixels,
488
+ )
489
+
490
+ # Save result
491
+ output_file = os.path.join(
492
+ input_folder, input_file.replace(".tif", "_labels.tif")
493
+ )
494
+ imwrite(output_file, result, compression="zlib")
495
+
496
+ print(
497
+ f"Saved segmentation with {np.max(result)} objects to {output_file}"
498
+ )
499
+
500
+ except (Exception, MemoryError) as e:
501
+ print(f"Error processing {input_file}: {str(e)}")
502
+
503
+ print("\nProcessing complete.")
504
+ return 0
505
+
506
+
507
+ # Run the command-line function if this script is run directly
508
+ if __name__ == "__main__":
509
+ import sys
510
+
511
+ sys.exit(run_cellpose_segmentation())