|
14 | 14 | from typing import Union |
15 | 15 |
|
16 | 16 | import numpy as np |
| 17 | +from skimage import img_as_ubyte |
17 | 18 |
|
18 | 19 | # Import the environment manager |
19 | 20 | from napari_tmidas.processing_functions.cellpose_env_manager import ( |
@@ -207,26 +208,28 @@ def cellpose_segmentation( |
207 | 208 | if image.dtype != np.uint8: |
208 | 209 | print(f"Converting image from {image.dtype} to uint8...") |
209 | 210 |
|
210 | | - # SAFEST APPROACH: Simple proportional bit-depth conversion |
211 | | - # This preserves relative intensities across all images/timepoints |
212 | | - # No normalization = reproducible and comparable results |
213 | | - |
214 | | - if image.dtype == np.uint16: |
215 | | - # uint16 (0-65535) → uint8 (0-255): divide by 256 |
216 | | - print(" Using proportional scaling: dividing by 256") |
217 | | - image = (image // 256).astype(np.uint8) |
218 | | - elif image.dtype == np.uint32: |
219 | | - # uint32 → uint8: divide by 2^24 |
220 | | - print(" Using proportional scaling: dividing by 16777216") |
221 | | - image = (image // 16777216).astype(np.uint8) |
222 | | - elif image.dtype in [np.float32, np.float64]: |
223 | | - # For float, assume 0-1 range and scale to 0-255 |
224 | | - print(" Assuming 0-1 range, scaling to 0-255") |
225 | | - image = np.clip(image * 255, 0, 255).astype(np.uint8) |
226 | | - else: |
227 | | - # For other types (int8, int16, etc.), clip to 0-255 |
228 | | - print(" Clipping to 0-255 range") |
229 | | - image = np.clip(image, 0, 255).astype(np.uint8) |
| 211 | + # Use scikit-image's img_as_ubyte for proper bit-depth conversion |
| 212 | + # This correctly handles: |
| 213 | + # - uint16 → uint8: proper rescaling (divide by 257, not 256) |
| 214 | + # - float → uint8: detects range and scales appropriately |
| 215 | + # - Preserves relative intensities without data loss |
| 216 | + # - Warns if data will be clipped |
| 217 | + print(" Using scikit-image img_as_ubyte for proper conversion") |
| 218 | + |
| 219 | + try: |
| 220 | + image = img_as_ubyte(image) |
| 221 | + except ValueError as e: |
| 222 | + # If conversion fails (e.g., out of range), fall back to clipping |
| 223 | + print(f" Warning: Conversion issue: {e}") |
| 224 | + print(" Falling back to min-max rescaling within image range") |
| 225 | + img_min = np.min(image) |
| 226 | + img_max = np.max(image) |
| 227 | + if img_max > img_min: |
| 228 | + image = ((image - img_min) / (img_max - img_min) * 255).astype( |
| 229 | + np.uint8 |
| 230 | + ) |
| 231 | + else: |
| 232 | + image = np.zeros_like(image, dtype=np.uint8) |
230 | 233 |
|
231 | 234 | # Handle TZYX data by processing each timepoint separately |
232 | 235 | if "T" in dim_order and image.ndim == 4: |
|
0 commit comments