|
| 1 | +"""Functions for running [Cellpose] segmentation in Fiji. |
| 2 | +
|
| 3 | +This module provides wrappers around the [BIOP Cellpose plugin] for Fiji, |
| 4 | +including support for distributed (batch) processing of multiple images. |
| 5 | +
|
| 6 | +[Cellpose]: https://cellpose.readthedocs.io/ |
| 7 | +[BIOP Cellpose plugin]: https://github.com/BIOP/ijl-utilities-wrappers |
| 8 | +""" |
| 9 | + |
| 10 | +import os |
| 11 | + |
| 12 | +from ch.epfl.biop.wrappers.cellpose import CellposeTaskSettings |
| 13 | +from ch.epfl.biop.wrappers.cellpose.ij2commands import Cellpose_SegmentImgPlusAdvanced |
| 14 | +from ij import IJ |
| 15 | + |
| 16 | +from ..log import LOG as log |
| 17 | + |
| 18 | + |
| 19 | +VALID_MODELS = ["cyto", "cyto2", "cyto3", "nuclei"] |
| 20 | + |
| 21 | +DIMENSION_MODE_2D = "2D" |
| 22 | +DIMENSION_MODE_3D = "3D" |
| 23 | + |
| 24 | + |
| 25 | +def build_cellpose_settings( |
| 26 | + model, |
| 27 | + diameter, |
| 28 | + cyto_channel, |
| 29 | + nuclei_channel=0, |
| 30 | + cellproba_threshold=0.0, |
| 31 | + flow_threshold=0.4, |
| 32 | + use_gpu=True, |
| 33 | + dimension_mode=DIMENSION_MODE_2D, |
| 34 | + stitch_threshold=-1.0, |
| 35 | + omni=False, |
| 36 | + cluster=False, |
| 37 | + additional_flags="", |
| 38 | +): |
| 39 | + """Build a Cellpose settings object for use with the BIOP Cellpose plugin. |
| 40 | +
|
| 41 | + Parameters |
| 42 | + ---------- |
| 43 | + model : str |
| 44 | + Name of the Cellpose model to use. Must be one of: cyto, cyto2, cyto3, |
| 45 | + nuclei. |
| 46 | + diameter : float |
| 47 | + Expected diameter of the objects to segment in pixels. Use 0 to let |
| 48 | + Cellpose estimate the diameter automatically. |
| 49 | + cyto_channel : int |
| 50 | + Index of the cytoplasm channel (1-based). Use 0 if not applicable. |
| 51 | + nuclei_channel : int, optional |
| 52 | + Index of the nuclei channel (1-based). Use 0 to disable the nuclei |
| 53 | + channel, by default 0. |
| 54 | + cellproba_threshold : float, optional |
| 55 | + Cell probability threshold. Lower values result in more detected cells, |
| 56 | + by default 0.0. |
| 57 | + flow_threshold : float, optional |
| 58 | + Flow error threshold for discarding masks. Higher values allow more |
| 59 | + errors, by default 0.4. |
| 60 | + use_gpu : bool, optional |
| 61 | + Whether to use GPU acceleration, by default True. |
| 62 | + dimension_mode : str, optional |
| 63 | + Dimension mode for segmentation, either ``"2D"`` or ``"3D"``, by |
| 64 | + default ``"2D"``. |
| 65 | + stitch_threshold : float, optional |
| 66 | + Threshold for stitching masks across z-planes. A value of -1.0 |
| 67 | + disables stitching, by default -1.0. |
| 68 | + omni : bool, optional |
| 69 | + Whether to use OmniPose segmentation, by default False. |
| 70 | + cluster : bool, optional |
| 71 | + Whether to use DBSCAN clustering for cell detection, by default False. |
| 72 | + additional_flags : str, optional |
| 73 | + Additional command-line flags passed to Cellpose, by default ``""``. |
| 74 | +
|
| 75 | + Returns |
| 76 | + ------- |
| 77 | + ch.epfl.biop.wrappers.cellpose.CellposeTaskSettings |
| 78 | + A configured ``CellposeTaskSettings`` object. |
| 79 | +
|
| 80 | + Raises |
| 81 | + ------ |
| 82 | + ValueError |
| 83 | + If ``model`` is not one of the valid model names. |
| 84 | + ValueError |
| 85 | + If ``dimension_mode`` is not ``"2D"`` or ``"3D"``. |
| 86 | + """ |
| 87 | + if model.lower() not in VALID_MODELS: |
| 88 | + raise ValueError( |
| 89 | + "model '%s' is not valid, must be one of: %s" |
| 90 | + % (model, ", ".join(VALID_MODELS)) |
| 91 | + ) |
| 92 | + |
| 93 | + if dimension_mode not in (DIMENSION_MODE_2D, DIMENSION_MODE_3D): |
| 94 | + raise ValueError( |
| 95 | + "dimension_mode must be '%s' or '%s', got '%s'" |
| 96 | + % (DIMENSION_MODE_2D, DIMENSION_MODE_3D, dimension_mode) |
| 97 | + ) |
| 98 | + |
| 99 | + log.debug( |
| 100 | + "Building Cellpose settings: model=%s, diameter=%s, cyto_ch=%s, " |
| 101 | + "nuclei_ch=%s, dimension_mode=%s" |
| 102 | + % (model, diameter, cyto_channel, nuclei_channel, dimension_mode) |
| 103 | + ) |
| 104 | + |
| 105 | + settings = CellposeTaskSettings() |
| 106 | + settings.setModel(model.lower()) |
| 107 | + settings.setDiameter(diameter) |
| 108 | + settings.setCytoCh(cyto_channel) |
| 109 | + settings.setNucleiCh(nuclei_channel) |
| 110 | + settings.setCellProbaTreshold(cellproba_threshold) |
| 111 | + settings.setFlowThreshold(flow_threshold) |
| 112 | + settings.useGpu(use_gpu) |
| 113 | + settings.setDimensionMode(dimension_mode) |
| 114 | + settings.setStitchThreshold(stitch_threshold) |
| 115 | + settings.setOmni(omni) |
| 116 | + settings.setCluster(cluster) |
| 117 | + settings.setAdditionalFlags(additional_flags) |
| 118 | + |
| 119 | + return settings |
| 120 | + |
| 121 | + |
| 122 | +def run_cellpose(imp, settings): |
| 123 | + """Run Cellpose segmentation on an ImagePlus. |
| 124 | +
|
| 125 | + Parameters |
| 126 | + ---------- |
| 127 | + imp : ij.ImagePlus |
| 128 | + Input ImagePlus to segment. |
| 129 | + settings : ch.epfl.biop.wrappers.cellpose.CellposeTaskSettings |
| 130 | + Configured ``CellposeTaskSettings`` object, see |
| 131 | + :func:`build_cellpose_settings`. |
| 132 | +
|
| 133 | + Returns |
| 134 | + ------- |
| 135 | + ij.ImagePlus |
| 136 | + Label image produced by Cellpose, or ``None`` if segmentation fails. |
| 137 | + """ |
| 138 | + log.info("Running Cellpose on image '%s'" % imp.getTitle()) |
| 139 | + |
| 140 | + command = Cellpose_SegmentImgPlusAdvanced() |
| 141 | + command.imp = imp |
| 142 | + command.cellposeSettings = settings |
| 143 | + |
| 144 | + try: |
| 145 | + command.run() |
| 146 | + except Exception as err: |
| 147 | + log.error("Cellpose segmentation failed: %s" % err) |
| 148 | + return None |
| 149 | + |
| 150 | + result = command.output_imp |
| 151 | + log.info( |
| 152 | + "Cellpose segmentation completed, result image: '%s'" % result.getTitle() |
| 153 | + ) |
| 154 | + |
| 155 | + return result |
| 156 | + |
| 157 | + |
| 158 | +def run_cellpose_distributed(imp_list, settings, show_progress=True): |
| 159 | + """Run Cellpose segmentation on a list of ImagePlus objects. |
| 160 | +
|
| 161 | + Processes each image sequentially while reporting progress, allowing |
| 162 | + Cellpose to be applied across a large batch of images (distributed |
| 163 | + processing). |
| 164 | +
|
| 165 | + Parameters |
| 166 | + ---------- |
| 167 | + imp_list : list(ij.ImagePlus) |
| 168 | + List of ImagePlus objects to segment. |
| 169 | + settings : ch.epfl.biop.wrappers.cellpose.CellposeTaskSettings |
| 170 | + Configured ``CellposeTaskSettings`` object shared across all images, |
| 171 | + see :func:`build_cellpose_settings`. |
| 172 | + show_progress : bool, optional |
| 173 | + Whether to show progress in the ImageJ progress bar, by default True. |
| 174 | +
|
| 175 | + Returns |
| 176 | + ------- |
| 177 | + list(ij.ImagePlus) |
| 178 | + List of label images produced by Cellpose, with ``None`` entries for |
| 179 | + images where segmentation failed. |
| 180 | +
|
| 181 | + Example |
| 182 | + ------- |
| 183 | + >>> settings = build_cellpose_settings( |
| 184 | + ... model="cyto2", |
| 185 | + ... diameter=30.0, |
| 186 | + ... cyto_channel=1, |
| 187 | + ... nuclei_channel=2, |
| 188 | + ... ) |
| 189 | + >>> results = run_cellpose_distributed(image_list, settings) |
| 190 | + """ |
| 191 | + total = len(imp_list) |
| 192 | + log.info("Starting distributed Cellpose run on %d images" % total) |
| 193 | + |
| 194 | + results = [] |
| 195 | + |
| 196 | + for idx, imp in enumerate(imp_list): |
| 197 | + log.info( |
| 198 | + "Processing image %d / %d: '%s'" % (idx + 1, total, imp.getTitle()) |
| 199 | + ) |
| 200 | + |
| 201 | + if show_progress: |
| 202 | + IJ.showProgress(idx, total) |
| 203 | + IJ.showStatus( |
| 204 | + "Cellpose: processing image %d / %d" % (idx + 1, total) |
| 205 | + ) |
| 206 | + |
| 207 | + result = run_cellpose(imp, settings) |
| 208 | + results.append(result) |
| 209 | + |
| 210 | + if show_progress: |
| 211 | + IJ.showProgress(total, total) |
| 212 | + IJ.showStatus("Cellpose: done processing %d images" % total) |
| 213 | + |
| 214 | + failed = sum(1 for r in results if r is None) |
| 215 | + if failed: |
| 216 | + log.warning( |
| 217 | + "Cellpose distributed run finished: %d / %d images failed" |
| 218 | + % (failed, total) |
| 219 | + ) |
| 220 | + else: |
| 221 | + log.info( |
| 222 | + "Cellpose distributed run finished successfully: %d images processed" |
| 223 | + % total |
| 224 | + ) |
| 225 | + |
| 226 | + return results |
0 commit comments