From 07eceb8ec3761b2d68e9da05d2a9fb1771be142e Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:27:24 -0700 Subject: [PATCH 01/19] get started --- active_plugins/runcellpose.py | 365 +++++++++++++++++++++++++++------- 1 file changed, 293 insertions(+), 72 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 47426d20..4fe5e20d 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -85,25 +85,44 @@ """ "Select Cellpose Docker Image" +# V2 Dockers CELLPOSE_DOCKER_NO_PRETRAINED_v232 = "cellprofiler/runcellpose_no_pretrained:2.3.2" CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232 = "cellprofiler/runcellpose_with_pretrained:2.3.2" CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" +# V3 Dockers +CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" #TODO edit for v3 +CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" #TODO edit for v3 +# V4 Dockers +CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" #TODO edit for SAM +CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" #TODO edit for SAM "Detection mode" -MODEL_NAMES = ['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', +MODEL_NAMES_V2 = ['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'] +MODEL_NAMES_V3 = [ + "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", + "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto" +] +MODEL_NAMES_V4 = [] #TODO +DENOISER_NAMES = ['denoise_cyto3', 'deblur_cyto3', 'upsample_cyto3', + 'denoise_nuclei', 'deblur_nuclei', 'upsample_nuclei'] +# Only these models support size scaling +SIZED_MODELS = {"cyto3", "cyto2", "cyto", "nuclei"} class RunCellpose(ImageSegmentation): category = "Object Processing" module_name = "RunCellpose" - variable_revision_number = 6 + variable_revision_number = 7 doi = { - "Please cite the following when using RunCellPose:": "https://doi.org/10.1038/s41592-020-01018-x", + "Please also cite Cellpose when using RunCellpose:": "https://doi.org/10.1038/s41592-020-01018-x", + "If you are using Cellpose 2 also cite the following:": "https://doi.org/10.1038/s41592-022-01663-4", + "If you are using Cellpose 3 also cite the following:": "https://doi.org/10.1038/s41592-025-02595-5", + "If you are using Cellpose 4 also cite the following:": "https://doi.org/10.1101/2025.04.28.651001", "If you are using Omnipose also cite the following:": "https://doi.org/10.1101/2021.11.03.467199", } @@ -134,8 +153,13 @@ def create_settings(self): are installed will be used. """, ) - - self.docker_image = Choice( + self.cellpose_version = Choice( + text="Select Cellpose version", + choices=['v2', 'v3', 'v4'], + value='v3', + doc="Select the version of Cellpose you want to use. Note that v2 is compatible with either v1 or v2.") + + self.docker_image_v2 = Choice( text="Select Cellpose docker image", choices=[CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220, CELLPOSE_DOCKER_NO_PRETRAINED_v220,CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232, CELLPOSE_DOCKER_NO_PRETRAINED_v232], value=CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232, @@ -152,25 +176,70 @@ def create_settings(self): "CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232": CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232} ), ) + self.docker_image_v3 = Choice( + text="Select Cellpose docker image", + choices=[],#TODO + value=''#TODO, + doc="""\ +Select which Docker image to use for running Cellpose. +If you are not using a custom model, select a Docker image **with pretrained**. If you are using a custom model, +you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB).""", + ) + self.docker_image_v4 = Choice( + text="Select Cellpose docker image", + choices=[],#TODO + value=''#TODO, + doc="""\ +Select which Docker image to use for running Cellpose. +If you are not using a custom model, select a Docker image **with pretrained**. If you are using a custom model, +you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB).""", + ) + self.specify_diameter = Binary( + text="Specify expected object diameter?", + value=True, + doc="""\ +Cellpose 4 was trained on images with ROI diameters from size 7.5 to 120, with a mean diameter of 30 pixels. +Thus the model has a good amount of size-invariance, meaning that specifying the diameter is optional. +However, you can have them downsampled by Cellpose 4 if you specify a larger diameter. +""",) + self.expected_diameter = Integer( text="Expected object diameter", value=30, minval=0, doc="""\ -The average diameter of the objects to be detected. Setting this to 0 will attempt to automatically detect object size. -Note that automatic diameter mode does not work when running on 3D images. - -Cellpose models come with a pre-defined object diameter. Your image will be resized during detection to attempt to +The average diameter of the objects to be detected. +In Cellpose 1-3, Cellpose models come with a pre-defined object diameter. Your image will be resized during detection to attempt to match the diameter expected by the model. The default models have an expected diameter of ~16 pixels, if trying to detect much smaller objects it may be more efficient to resize the image first using the Resize module. +If set to 0 in Cellpose 1-3, it will attempt to automatically detect object size. Note that automatic diameter mode does not work when running on 3D images. +Note that automatic diameter mode does not work when running on 3D images. """, ) - self.mode = Choice( + self.mode_v2 = Choice( + text="Detection mode", + choices=MODEL_NAMES_V2, + value=MODEL_NAMES_V2[0], + doc="""\ +CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model +generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. +""", + ) + self.mode_v3 = Choice( text="Detection mode", - choices=MODEL_NAMES, - value=MODEL_NAMES[0], + choices=MODEL_NAMES_V3, + value=MODEL_NAMES_V3[0], + doc="""\ +CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model +generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. +""", + ) + self.mode_v4 = Choice( + text="Detection mode", + choices=MODEL_NAMES_V4, + value=MODEL_NAMES_V4[0], doc="""\ CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. @@ -359,15 +428,45 @@ def set_directory_fn(path): Activate to rescale probability map to 0-255 (which matches the scale used when running this module from Docker) """, ) + self.denoise = Binary( + text="Preprocess image before segmentation?", + value=False, + doc=""" + If enabled, a separate Cellpose model will be used to clean the input image before segmentation. + Try this if your input images are blurred, noisy or otherwise need cleanup. + """, + ) + + self.denoise_type = Choice( + text="Preprocessing model", + choices=DENOISER_NAMES, + value=DENOISER_NAMES[0], + doc="""\ + Model to use for preprocessing of images. An AI model can be applied to denoise, remove blur or upsample images prior to + segmentation. Select nucleus models for nuclei or cyto3 models for anything else. + + 'Denoise' models may help if your staining is inconsistent. + 'Deblur' attempts to improve out-of-focus images + 'Upsample' will attempt to resize the images so that the object sizes match the native diameter of the segmentation model. + + N.b. for upsampling it is essential that the "Expected diameter" setting is correct for the input images + """, + ) def settings(self): return [ self.x_name, self.rescale, self.docker_or_python, - self.docker_image, + self.cellpose_version, + self.docker_image_v2, + self.docker_image_v3, + self.docker_image_v4, + self.specify_diameter, self.expected_diameter, - self.mode, + self.mode_v2, + self.mode_v3, + self.mode_v4, self.y_name, self.use_gpu, self.use_averaging, @@ -387,16 +486,29 @@ def settings(self): self.invert, self.remove_edge_masks, self.probability_rescale_setting, + self.denoise, + self.denoise_type, ] def visible_settings(self): - vis_settings = [self.rescale, self.docker_or_python] - + vis_settings = [self.rescale, self.docker_or_python, self.cellpose_version] if self.docker_or_python.value == "Docker": - vis_settings += [self.docker_image] - - vis_settings += [self.mode, self.x_name] + if self.cellpose_version.value == 'v2': + vis_settings += [self.docker_image_v2] + elif self.cellpose_version.value == 'v3': + vis_settings += [self.docker_image_v3] + elif self.cellpose_version.value == 'v4': + vis_settings += [self.docker_image_v4] + + if self.cellpose_version.value == 'v2': + vis_settings += [self.mode_v2] + elif self.cellpose_version.value == 'v3': + vis_settings += [self.mode_v3] + elif self.cellpose_version.value == 'v4': + vis_settings += [self.mode_v4] + + vis_settings += [self.x_name] if self.docker_or_python.value == "Python": vis_settings += [self.omni] @@ -410,9 +522,12 @@ def visible_settings(self): self.model_directory, self.model_file_name, ] + if self.cellpose_version.value == 'v4': + vis_settings += [self.specify_diameter] + if self.specify_diameter.value: + vis_settings += [self.expected_diameter] vis_settings += [ - self.expected_diameter, self.cellprob_threshold, self.min_size, self.flow_threshold, @@ -437,6 +552,11 @@ def visible_settings(self): if self.use_gpu.value: vis_settings += [self.gpu_test, self.manual_GPU_memory_share] + if self.cellpose_version.value == 'v3': + vis_settings += [self.denoise] + if self.denoise.value: + vis_settings += [self.denoise_type] + return vis_settings def validate_module(self, pipeline): @@ -470,6 +590,16 @@ def run(self, workspace): dimensions = x.dimensions x_data = x.pixel_data + if self.cellpose_version.value == 'v2': + self.mode = self.mode_v2 + self.docker_image = self.docker_image_v2 + elif self.cellpose_version.value == 'v3': + self.mode = self.mode_v3 + self.docker_image = self.docker_image_v3 + elif self.cellpose_version.value == 'v4': + self.mode = self.mode_v4 + self.docker_image = self.docker_image_v4 + if self.rescale.value: rescale_x = x_data.copy() x01 = numpy.percentile(rescale_x, 1) @@ -480,7 +610,8 @@ def run(self, workspace): if self.do_3D.value: anisotropy = x.spacing[0] / x.spacing[1] - diam = self.expected_diameter.value if self.expected_diameter.value > 0 else None + if self.specify_diameter.value: + diam = self.expected_diameter.value if self.expected_diameter.value > 0 else None if x.multichannel: raise ValueError( @@ -489,7 +620,7 @@ def run(self, workspace): if self.mode.value != "nuclei" and self.supply_nuclei.value: nuc_image = images.get_image(self.nuclei_image.value) - # CellPose expects RGB, we'll have a blank red channel, cells in green and nuclei in blue. + # CellPose 1-3 expects RGB, we'll have a blank red channel, cells in green and nuclei in blue. if self.do_3D.value: x_data = numpy.stack( (numpy.zeros_like(x_data), x_data, nuc_image.pixel_data), axis=1 @@ -507,52 +638,123 @@ def run(self, workspace): if self.docker_or_python.value == "Python": from cellpose import models, io, core, utils self.cellpose_ver = importlib.metadata.version('cellpose') - if float(self.cellpose_ver[0:3]) >= 0.6 and int(self.cellpose_ver[0])<2: - if self.mode.value != 'custom': - model = models.Cellpose(model_type= self.mode.value, - gpu=self.use_gpu.value) - else: - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) - model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) - - else: - if self.mode.value != 'custom': - model = models.CellposeModel(model_type= self.mode.value, - gpu=self.use_gpu.value) - else: - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) - model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) if self.use_gpu.value and model.torch: from torch import cuda cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) - try: - if float(self.cellpose_ver[0:3]) >= 0.7 and int(self.cellpose_ver[0])<2: - y_data, flows, *_ = model.eval( - x_data, - channels=channels, - diameter=diam, - net_avg=self.use_averaging.value, - do_3D=self.do_3D.value, - anisotropy=anisotropy, - flow_threshold=self.flow_threshold.value, - cellprob_threshold=self.cellprob_threshold.value, - stitch_threshold=self.stitch_threshold.value, - min_size=self.min_size.value, - omni=self.omni.value, - invert=self.invert.value, - ) + if self.cellpose_version.value == 'v2': + assert int(self.cellpose_ver[0])<=2, "Cellpose version selected in RunCellpose module doesn't match version in Python" + if float(self.cellpose_ver[0:3]) >= 0.6 and int(self.cellpose_ver[0])<2: + if self.mode.value != 'custom': + model = models.Cellpose(model_type= self.mode.value, + gpu=self.use_gpu.value) + else: + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) + model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + + else: + if self.mode.value != 'custom': + model = models.CellposeModel(model_type= self.mode.value, + gpu=self.use_gpu.value) + else: + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) + model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + + try: + if float(self.cellpose_ver[0:3]) >= 0.7 and int(self.cellpose_ver[0])<2: + y_data, flows, *_ = model.eval( + x_data, + channels=channels, + diameter=diam, + net_avg=self.use_averaging.value, + do_3D=self.do_3D.value, + anisotropy=anisotropy, + flow_threshold=self.flow_threshold.value, + cellprob_threshold=self.cellprob_threshold.value, + stitch_threshold=self.stitch_threshold.value, + min_size=self.min_size.value, + omni=self.omni.value, + invert=self.invert.value, + ) + else: + y_data, flows, *_ = model.eval( + x_data, + channels=channels, + diameter=diam, + net_avg=self.use_averaging.value, + do_3D=self.do_3D.value, + anisotropy=anisotropy, + flow_threshold=self.flow_threshold.value, + cellprob_threshold=self.cellprob_threshold.value, + stitch_threshold=self.stitch_threshold.value, + min_size=self.min_size.value, + invert=self.invert.value, + ) + except Exception as a: + print(f"Unable to create masks. Check your module settings. {a}") + finally: + if self.use_gpu.value and model.torch: + # Try to clear some GPU memory for other worker processes. + try: + cuda.empty_cache() + except Exception as e: + print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + + elif self.cellpose_version.value == 'v3': + assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" + if self.mode.value == 'custom': + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) + model_params = (self.mode.value, self.use_gpu.value) + LOGGER.info(f"Loading new model: {self.mode.value}") + if self.mode.value in SIZED_MODELS: + self.current_model = models.Cellpose( + model_type=self.mode.value, gpu=self.use_gpu.value) else: - y_data, flows, *_ = model.eval( - x_data, + self.current_model = models.CellposeModel( + model_type=self.mode.value, gpu=self.use_gpu.value) + self.current_model_params = model_params + + if self.denoise.value: + from cellpose import denoise + recon_params = ( + self.denoise_type.value, + self.use_gpu.value, + self.mode.value != "nuclei" and self.supply_nuclei.value + ) + self.recon_model = denoise.DenoiseModel( + model_type=recon_params[0], + gpu=recon_params[1], + chan2=recon_params[2] + ) + if self.recon_model is not None: + input_data = self.recon_model.eval( + x_data, + diameter=diam, + channels=channels + ) + # Upsampling models scale object diameter to a target size + if self.denoise_type.value == "upsample_cyto3": + diam = 30 + elif self.denoise_type.value == "upsample_nuclei": + diam = 17 + # Result only includes input channels + if self.mode.value != "nuclei" and self.supply_nuclei.value: + channels = [0, 1] + else: + input_data = x_data + + try: + y_data, flows, *_ = self.current_model.eval( + input_data, channels=channels, diameter=diam, - net_avg=self.use_averaging.value, do_3D=self.do_3D.value, anisotropy=anisotropy, flow_threshold=self.flow_threshold.value, @@ -560,20 +762,27 @@ def run(self, workspace): stitch_threshold=self.stitch_threshold.value, min_size=self.min_size.value, invert=self.invert.value, - ) + ) + + if self.denoise.value and "upsample" in self.denoise_type.value: + y_data = skimage.transform.resize(y_data, x.pixel_data.shape, + preserve_range=True, order=0) - if self.remove_edge_masks: - y_data = utils.remove_edge_masks(y_data) + except Exception as a: + print(f"Unable to create masks. Check your module settings. {a}") + finally: + if self.use_gpu.value and model.torch: + # Try to clear some GPU memory for other worker processes. + try: + cuda.empty_cache() + except Exception as e: + print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") - except Exception as a: - print(f"Unable to create masks. Check your module settings. {a}") - finally: - if self.use_gpu.value and model.torch: - # Try to clear some GPU memory for other worker processes. - try: - cuda.empty_cache() - except Exception as e: - print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + elif self.cellpose_version.value == 'v4': + assert int(self.cellpose_ver[0])==4, "Cellpose version selected in RunCellpose module doesn't match version in Python" + + if self.remove_edge_masks: + y_data = utils.remove_edge_masks(y_data) elif self.docker_or_python.value == "Docker": # Define how to call docker @@ -642,6 +851,14 @@ def run(self, workspace): objects = workspace.object_set objects.add_objects(y, y_name) + if self.denoise.value and self.show_window: + # Need to remove unnecessary extra axes + denoised_image = numpy.squeeze(input_data) + if "upsample" in self.denoise_type.value: + denoised_image = skimage.transform.resize( + denoised_image, x_data.shape) + workspace.display_data.recon = denoised_image + if self.save_probabilities.value: if self.docker_or_python.value == "Docker": # get rid of extra dimension @@ -748,6 +965,10 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name if variable_revision_number == 5: setting_values = setting_values + [False] variable_revision_number = 6 + if variable_revision_number == 6: + #TODO + setting_values = setting_values[0:2] + variable_revision_number = 7 return setting_values, variable_revision_number From fc59d7f676160738ab604cd5df2a7dcc7e287bed Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:38:46 -0700 Subject: [PATCH 02/19] make dictionaries --- active_plugins/runcellpose.py | 109 +++++++++++++++------------------- 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 4fe5e20d..6878d49d 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -85,26 +85,18 @@ """ "Select Cellpose Docker Image" -# V2 Dockers -CELLPOSE_DOCKER_NO_PRETRAINED_v232 = "cellprofiler/runcellpose_no_pretrained:2.3.2" -CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232 = "cellprofiler/runcellpose_with_pretrained:2.3.2" -CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" -CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" -# V3 Dockers -CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" #TODO edit for v3 -CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" #TODO edit for v3 -# V4 Dockers -CELLPOSE_DOCKER_NO_PRETRAINED_v220 = "cellprofiler/runcellpose_no_pretrained:2.2" #TODO edit for SAM -CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220 = "cellprofiler/runcellpose_with_pretrained:2.2" #TODO edit for SAM +CELLPOSE_DOCKERS = {'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", + "cellprofiler/runcellpose_with_pretrained:2.3.2", + "cellprofiler/runcellpose_with_pretrained:2.2"], + 'v3': ["docker3"], #TODO + 'v4': ["docker4"]} #TODO "Detection mode" -MODEL_NAMES_V2 = ['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', - 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'] -MODEL_NAMES_V3 = [ - "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", - "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto" -] -MODEL_NAMES_V4 = [] #TODO +MODEL_NAMES = {'v2':['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', + 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'], + 'v3':[ "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", + "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto"], + 'v4':['model4']} #TODO DENOISER_NAMES = ['denoise_cyto3', 'deblur_cyto3', 'upsample_cyto3', 'denoise_nuclei', 'deblur_nuclei', 'upsample_nuclei'] @@ -161,37 +153,29 @@ def create_settings(self): self.docker_image_v2 = Choice( text="Select Cellpose docker image", - choices=[CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220, CELLPOSE_DOCKER_NO_PRETRAINED_v220,CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232, CELLPOSE_DOCKER_NO_PRETRAINED_v232], - value=CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232, + choices=CELLPOSE_DOCKERS['v2'], + value=CELLPOSE_DOCKERS['v2'][0], doc="""\ Select which Docker image to use for running Cellpose. - -If you are not using a custom model, you can select -**"{CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232}"** or "{CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220}"**. If you are using a custom model, +If you are not using a custom model, you should select a Docker image **with pretrained**. If you are using a custom model, you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB). -""".format( - **{"CELLPOSE_DOCKER_NO_PRETRAINED_v220": CELLPOSE_DOCKER_NO_PRETRAINED_v220, - "CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220": CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v220, - "CELLPOSE_DOCKER_NO_PRETRAINED_v232": CELLPOSE_DOCKER_NO_PRETRAINED_v232, - "CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232": CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232} -), - ) +""") self.docker_image_v3 = Choice( text="Select Cellpose docker image", - choices=[],#TODO - value=''#TODO, + choices=CELLPOSE_DOCKERS['v3'], + value=CELLPOSE_DOCKERS['v3'][0], doc="""\ Select which Docker image to use for running Cellpose. -If you are not using a custom model, select a Docker image **with pretrained**. If you are using a custom model, +If you are not using a custom model, you should select a Docker image **with pretrained**. If you are using a custom model, you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB).""", ) self.docker_image_v4 = Choice( text="Select Cellpose docker image", - choices=[],#TODO - value=''#TODO, + choices=CELLPOSE_DOCKERS['v4'], + value=CELLPOSE_DOCKERS['v4'][0], doc="""\ Select which Docker image to use for running Cellpose. -If you are not using a custom model, select a Docker image **with pretrained**. If you are using a custom model, +If you are not using a custom model, you should select a Docker image **with pretrained**. If you are using a custom model, you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB).""", ) @@ -220,8 +204,8 @@ def create_settings(self): self.mode_v2 = Choice( text="Detection mode", - choices=MODEL_NAMES_V2, - value=MODEL_NAMES_V2[0], + choices=MODEL_NAMES['v2'], + value=MODEL_NAMES['v2'][0], doc="""\ CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. @@ -229,8 +213,8 @@ def create_settings(self): ) self.mode_v3 = Choice( text="Detection mode", - choices=MODEL_NAMES_V3, - value=MODEL_NAMES_V3[0], + choices=MODEL_NAMES['v3'], + value=MODEL_NAMES['v3'][0], doc="""\ CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. @@ -238,8 +222,8 @@ def create_settings(self): ) self.mode_v4 = Choice( text="Detection mode", - choices=MODEL_NAMES_V4, - value=MODEL_NAMES_V4[0], + choices=MODEL_NAMES['v4'], + value=MODEL_NAMES['v4'][0], doc="""\ CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. @@ -513,11 +497,11 @@ def visible_settings(self): if self.docker_or_python.value == "Python": vis_settings += [self.omni] - if self.mode.value != "nuclei": + if self.mode_v2.value != "nuclei": vis_settings += [self.supply_nuclei] if self.supply_nuclei.value: vis_settings += [self.nuclei_image] - if self.mode.value == "custom": + if self.mode_v2.value == "custom": vis_settings += [ self.model_directory, self.model_file_name, @@ -562,7 +546,7 @@ def visible_settings(self): def validate_module(self, pipeline): """If using custom model, validate the model file opens and works""" from cellpose import models - if self.mode.value == "custom": + if self.mode_v2.value == "custom": model_file = self.model_file_name.value model_directory = self.model_directory.get_absolute_path() model_path = os.path.join(model_directory, model_file) @@ -618,7 +602,7 @@ def run(self, workspace): "Color images are not currently supported. Please provide greyscale images." ) - if self.mode.value != "nuclei" and self.supply_nuclei.value: + if self.mode_v2.value != "nuclei" and self.supply_nuclei.value: nuc_image = images.get_image(self.nuclei_image.value) # CellPose 1-3 expects RGB, we'll have a blank red channel, cells in green and nuclei in blue. if self.do_3D.value: @@ -646,8 +630,8 @@ def run(self, workspace): if self.cellpose_version.value == 'v2': assert int(self.cellpose_ver[0])<=2, "Cellpose version selected in RunCellpose module doesn't match version in Python" if float(self.cellpose_ver[0:3]) >= 0.6 and int(self.cellpose_ver[0])<2: - if self.mode.value != 'custom': - model = models.Cellpose(model_type= self.mode.value, + if self.mode_v2.value != 'custom': + model = models.Cellpose(model_type= self.mode_v2.value, gpu=self.use_gpu.value) else: model_file = self.model_file_name.value @@ -656,8 +640,8 @@ def run(self, workspace): model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) else: - if self.mode.value != 'custom': - model = models.CellposeModel(model_type= self.mode.value, + if self.mode_v2.value != 'custom': + model = models.CellposeModel(model_type= self.mode_v2.value, gpu=self.use_gpu.value) else: model_file = self.model_file_name.value @@ -707,18 +691,18 @@ def run(self, workspace): elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" - if self.mode.value == 'custom': + if self.mode_v3.value == 'custom': model_file = self.model_file_name.value model_directory = self.model_directory.get_absolute_path() model_path = os.path.join(model_directory, model_file) - model_params = (self.mode.value, self.use_gpu.value) - LOGGER.info(f"Loading new model: {self.mode.value}") - if self.mode.value in SIZED_MODELS: + model_params = (self.mode_v3.value, self.use_gpu.value) + LOGGER.info(f"Loading new model: {self.mode_v3.value}") + if self.mode_v3.value in SIZED_MODELS: self.current_model = models.Cellpose( - model_type=self.mode.value, gpu=self.use_gpu.value) + model_type=self.mode_v3.value, gpu=self.use_gpu.value) else: self.current_model = models.CellposeModel( - model_type=self.mode.value, gpu=self.use_gpu.value) + model_type=self.mode_v3.value, gpu=self.use_gpu.value) self.current_model_params = model_params if self.denoise.value: @@ -726,7 +710,7 @@ def run(self, workspace): recon_params = ( self.denoise_type.value, self.use_gpu.value, - self.mode.value != "nuclei" and self.supply_nuclei.value + self.mode_v3.value != "nuclei" and self.supply_nuclei.value ) self.recon_model = denoise.DenoiseModel( model_type=recon_params[0], @@ -745,7 +729,7 @@ def run(self, workspace): elif self.denoise_type.value == "upsample_nuclei": diam = 17 # Result only includes input channels - if self.mode.value != "nuclei" and self.supply_nuclei.value: + if self.mode_v2.value != "nuclei" and self.supply_nuclei.value: channels = [0, 1] else: input_data = x_data @@ -780,6 +764,7 @@ def run(self, workspace): elif self.cellpose_version.value == 'v4': assert int(self.cellpose_ver[0])==4, "Cellpose version selected in RunCellpose module doesn't match version in Python" + # TODO if self.remove_edge_masks: y_data = utils.remove_edge_masks(y_data) @@ -797,7 +782,7 @@ def run(self, workspace): os.makedirs(temp_img_dir, exist_ok=True) temp_img_path = os.path.join(temp_img_dir, unique_name+".tiff") - if self.mode.value == "custom": + if self.mode_v2.value == "custom": model_file = self.model_file_name.value model_directory = self.model_directory.get_absolute_path() model_path = os.path.join(model_directory, model_file) @@ -814,8 +799,8 @@ def run(self, workspace): if self.use_gpu.value: cmd += ['--gpus', 'all'] cmd += ['cellpose', '--verbose', '--dir', '/data/img', '--pretrained_model'] - if self.mode.value !='custom': - cmd += [self.mode.value] + if self.mode_v2.value !='custom': + cmd += [self.mode_v2.value] else: cmd += ['/data/model/' + model_file] cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] @@ -957,7 +942,7 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name setting_values = setting_values + ["0.0", False, "15", "1.0", False, False] variable_revision_number = 3 if variable_revision_number == 3: - setting_values = [setting_values[0]] + ["Python",CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED_v232] + setting_values[1:] + setting_values = [setting_values[0]] + ["Python",CELLPOSE_DOCKERS['v2'][0]] + setting_values[1:] variable_revision_number = 4 if variable_revision_number == 4: setting_values = [setting_values[0]] + ['No'] + setting_values[1:] From 905df45b0bf1170d63d732f6461b65c7719ab521 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:43:29 -0700 Subject: [PATCH 03/19] abstract custom models, support v3/v4 --- active_plugins/runcellpose.py | 86 +++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 6878d49d..6cd685af 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -88,7 +88,7 @@ CELLPOSE_DOCKERS = {'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.2"], - 'v3': ["docker3"], #TODO + 'v3': ["biocontainers/cellpose:3.1.0_cv1"], #TODO 'v4': ["docker4"]} #TODO "Detection mode" @@ -96,13 +96,19 @@ 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'], 'v3':[ "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto"], - 'v4':['model4']} #TODO + 'v4':['cpsam']} DENOISER_NAMES = ['denoise_cyto3', 'deblur_cyto3', 'upsample_cyto3', 'denoise_nuclei', 'deblur_nuclei', 'upsample_nuclei'] # Only these models support size scaling SIZED_MODELS = {"cyto3", "cyto2", "cyto", "nuclei"} +def get_custom_model_vars(self): + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) + return model_file, model_directory, model_path + class RunCellpose(ImageSegmentation): category = "Object Processing" @@ -197,7 +203,7 @@ def create_settings(self): In Cellpose 1-3, Cellpose models come with a pre-defined object diameter. Your image will be resized during detection to attempt to match the diameter expected by the model. The default models have an expected diameter of ~16 pixels, if trying to detect much smaller objects it may be more efficient to resize the image first using the Resize module. -If set to 0 in Cellpose 1-3, it will attempt to automatically detect object size. Note that automatic diameter mode does not work when running on 3D images. +If set to 0 in Cellpose 1-3, it will attempt to automatically detect object size. Note that automatic diameter mode does not work when running on 3D images. """, ) @@ -497,11 +503,11 @@ def visible_settings(self): if self.docker_or_python.value == "Python": vis_settings += [self.omni] - if self.mode_v2.value != "nuclei": + if self.mode.value != "nuclei": vis_settings += [self.supply_nuclei] if self.supply_nuclei.value: vis_settings += [self.nuclei_image] - if self.mode_v2.value == "custom": + if self.mode.value == "custom": vis_settings += [ self.model_directory, self.model_file_name, @@ -516,9 +522,10 @@ def visible_settings(self): self.min_size, self.flow_threshold, self.y_name, - self.invert, self.save_probabilities, ] + if self.cellpose_version.value in ['v2','v3']: + vis_settings += [self.invert] vis_settings += [self.do_3D, self.stitch_threshold, self.remove_edge_masks] @@ -546,7 +553,7 @@ def visible_settings(self): def validate_module(self, pipeline): """If using custom model, validate the model file opens and works""" from cellpose import models - if self.mode_v2.value == "custom": + if self.mode.value == "custom": model_file = self.model_file_name.value model_directory = self.model_directory.get_absolute_path() model_path = os.path.join(model_directory, model_file) @@ -602,7 +609,7 @@ def run(self, workspace): "Color images are not currently supported. Please provide greyscale images." ) - if self.mode_v2.value != "nuclei" and self.supply_nuclei.value: + if self.mode.value != "nuclei" and self.supply_nuclei.value: nuc_image = images.get_image(self.nuclei_image.value) # CellPose 1-3 expects RGB, we'll have a blank red channel, cells in green and nuclei in blue. if self.do_3D.value: @@ -630,23 +637,19 @@ def run(self, workspace): if self.cellpose_version.value == 'v2': assert int(self.cellpose_ver[0])<=2, "Cellpose version selected in RunCellpose module doesn't match version in Python" if float(self.cellpose_ver[0:3]) >= 0.6 and int(self.cellpose_ver[0])<2: - if self.mode_v2.value != 'custom': - model = models.Cellpose(model_type= self.mode_v2.value, + if self.mode.value != 'custom': + model = models.Cellpose(model_type= self.mode.value, gpu=self.use_gpu.value) else: - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) + model_file, model_directory, model_path = get_custom_model_vars(self) model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) else: - if self.mode_v2.value != 'custom': - model = models.CellposeModel(model_type= self.mode_v2.value, + if self.mode.value != 'custom': + model = models.CellposeModel(model_type= self.mode.value, gpu=self.use_gpu.value) else: - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) + model_file, model_directory, model_path = get_custom_model_vars(self) model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) try: @@ -691,18 +694,16 @@ def run(self, workspace): elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" - if self.mode_v3.value == 'custom': - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) - model_params = (self.mode_v3.value, self.use_gpu.value) - LOGGER.info(f"Loading new model: {self.mode_v3.value}") - if self.mode_v3.value in SIZED_MODELS: + if self.mode.value == 'custom': + model_file, model_directory, model_path = get_custom_model_vars(self) + model_params = (self.mode.value, self.use_gpu.value) + LOGGER.info(f"Loading new model: {self.mode.value}") + if self.mode.value in SIZED_MODELS: self.current_model = models.Cellpose( - model_type=self.mode_v3.value, gpu=self.use_gpu.value) + model_type=self.mode.value, gpu=self.use_gpu.value) else: self.current_model = models.CellposeModel( - model_type=self.mode_v3.value, gpu=self.use_gpu.value) + model_type=self.mode.value, gpu=self.use_gpu.value) self.current_model_params = model_params if self.denoise.value: @@ -710,7 +711,7 @@ def run(self, workspace): recon_params = ( self.denoise_type.value, self.use_gpu.value, - self.mode_v3.value != "nuclei" and self.supply_nuclei.value + self.mode.value != "nuclei" and self.supply_nuclei.value ) self.recon_model = denoise.DenoiseModel( model_type=recon_params[0], @@ -729,7 +730,7 @@ def run(self, workspace): elif self.denoise_type.value == "upsample_nuclei": diam = 17 # Result only includes input channels - if self.mode_v2.value != "nuclei" and self.supply_nuclei.value: + if self.mode.value != "nuclei" and self.supply_nuclei.value: channels = [0, 1] else: input_data = x_data @@ -782,12 +783,11 @@ def run(self, workspace): os.makedirs(temp_img_dir, exist_ok=True) temp_img_path = os.path.join(temp_img_dir, unique_name+".tiff") - if self.mode_v2.value == "custom": - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) + if self.mode.value == "custom": + model_file, model_directory, model_path = get_custom_model_vars(self) + model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + temp_model_dir = os.path.join(temp_dir, "model") - os.makedirs(temp_model_dir, exist_ok=True) # Copy the model shutil.copy(model_path, os.path.join(temp_model_dir, model_file)) @@ -799,19 +799,27 @@ def run(self, workspace): if self.use_gpu.value: cmd += ['--gpus', 'all'] cmd += ['cellpose', '--verbose', '--dir', '/data/img', '--pretrained_model'] - if self.mode_v2.value !='custom': - cmd += [self.mode_v2.value] + if self.mode.value !='custom': + cmd += [self.mode.value] else: cmd += ['/data/model/' + model_file] - cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] + if self.cellpose_version.value == 'v3': + if self.denoise.value: + cmd += ['--denoise', self.denoise_type.value] + if self.cellpose_version.value in ['v2','v3']: + cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] + if self.cellpose_version.value in ['v4']: + if self.specify_diameter.value: + cmd += ['--diameter', str(diam)] if self.use_averaging.value: cmd += ['--net_avg'] if self.do_3D.value: cmd += ['--do_3D'] cmd += ['--anisotropy', str(anisotropy), '--flow_threshold', str(self.flow_threshold.value), '--cellprob_threshold', str(self.cellprob_threshold.value), '--stitch_threshold', str(self.stitch_threshold.value), '--min_size', str(self.min_size.value)] - if self.invert.value: - cmd += ['--invert'] + if self.cellpose_version.value in ['v2','v3']: + if self.invert.value: + cmd += ['--invert'] if self.remove_edge_masks.value: cmd += ['--exclude_on_edges'] print(cmd) From e386b1005696c14e58750f440ea5b8fef9ae0d2c Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:00:13 -0700 Subject: [PATCH 04/19] working display, dockers 3 and 4 --- active_plugins/runcellpose.py | 98 +++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 6cd685af..14df45cf 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -13,6 +13,8 @@ import shutil import logging import sys +import math +import scipy.ndimage ################################# # @@ -88,15 +90,15 @@ CELLPOSE_DOCKERS = {'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.2"], - 'v3': ["biocontainers/cellpose:3.1.0_cv1"], #TODO - 'v4': ["docker4"]} #TODO + 'v3': ["erinweisbart/cellpose:3.1.1.2"], #TODO + 'v4': ["erinweisbart/cellpose:4.0.5"]} #TODO "Detection mode" MODEL_NAMES = {'v2':['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'], 'v3':[ "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", - "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto"], - 'v4':['cpsam']} + "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto", "custom"], + 'v4':['cpsam','custom']} DENOISER_NAMES = ['denoise_cyto3', 'deblur_cyto3', 'upsample_cyto3', 'denoise_nuclei', 'deblur_nuclei', 'upsample_nuclei'] @@ -493,10 +495,13 @@ def visible_settings(self): if self.cellpose_version.value == 'v2': vis_settings += [self.mode_v2] + self.mode = self.mode_v2 elif self.cellpose_version.value == 'v3': vis_settings += [self.mode_v3] + self.mode = self.mode_v3 elif self.cellpose_version.value == 'v4': vis_settings += [self.mode_v4] + self.mode = self.mode_v4 vis_settings += [self.x_name] @@ -805,7 +810,7 @@ def run(self, workspace): cmd += ['/data/model/' + model_file] if self.cellpose_version.value == 'v3': if self.denoise.value: - cmd += ['--denoise', self.denoise_type.value] + cmd += ['--restore_type', self.denoise_type.value] if self.cellpose_version.value in ['v2','v3']: cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] if self.cellpose_version.value in ['v4']: @@ -826,9 +831,10 @@ def run(self, workspace): try: subprocess.run(cmd, text=True) cellpose_output = numpy.load(os.path.join(temp_img_dir, unique_name + "_seg.npy"), allow_pickle=True).item() - y_data = cellpose_output["masks"] flows = cellpose_output["flows"] + if self.denoise.value: + img_restore = cellpose_output["restore"] #TODO don't think this is correct for retrieving image for plotting finally: # Delete the temporary files try: @@ -843,10 +849,11 @@ def run(self, workspace): y.parent_image = x.parent_image objects = workspace.object_set objects.add_objects(y, y_name) + object_count = y.count if self.denoise.value and self.show_window: # Need to remove unnecessary extra axes - denoised_image = numpy.squeeze(input_data) + denoised_image = numpy.squeeze(img_restore) if "upsample" in self.denoise_type.value: denoised_image = skimage.transform.resize( denoised_image, x_data.shape) @@ -888,20 +895,53 @@ def run(self, workspace): workspace.display_data.y_data = y_data workspace.display_data.dimensions = dimensions + workspace.display_data.primary_labels = y.segmented + + workspace.display_data.statistics = [] + statistics = workspace.display_data.statistics + statistics.append(["# of accepted objects", "%d" % object_count]) + + if object_count > 0: + areas = y.areas + areas.sort() + low_diameter = ( + math.sqrt(float(areas[object_count // 10]) / numpy.pi) * 2 + ) + median_diameter = ( + math.sqrt(float(areas[object_count // 2]) / numpy.pi) * 2 + ) + high_diameter = ( + math.sqrt(float(areas[object_count * 9 // 10]) / numpy.pi) * 2 + ) + statistics.append( + ["10th pctile diameter", "%.1f pixels" % low_diameter] + ) + statistics.append(["Median diameter", "%.1f pixels" % median_diameter]) + statistics.append( + ["90th pctile diameter", "%.1f pixels" % high_diameter] + ) + object_area = numpy.sum(areas) + total_area = numpy.product(y_data.shape[:2]) + statistics.append( + [ + "Area covered by objects", + "%.1f %%" % (100.0 * float(object_area) / float(total_area)), + ] + ) + def display(self, workspace, figure): - if self.save_probabilities.value: - layout = (2, 2) + if self.save_probabilities.value or self.denoise.value: + layout = (3, 2) else: - layout = (2, 1) - - figure.set_subplots( - dimensions=workspace.display_data.dimensions, subplots=layout - ) + layout = (2, 2) + figure.set_subplots(subplots=layout) + + title = "Input image, cycle #%d" % (workspace.measurements.image_number,) figure.subplot_imshow( colormap="gray", image=workspace.display_data.x_data, - title="Input Image", + title=title, x=0, y=0, ) @@ -913,13 +953,39 @@ def display(self, workspace, figure): x=1, y=0, ) + + cplabels = [ + dict(name=self.y_name.value, labels=[workspace.display_data.primary_labels]), + ] + + title = "%s outlines" % self.y_name.value + figure.subplot_imshow_grayscale( + 0, 1, workspace.display_data.x_data, title, cplabels=cplabels, sharexy=figure.subplot(0, 0), + ) + + figure.subplot_table( + 1, + 1, + [[x[1]] for x in workspace.display_data.statistics], + row_labels=[x[0] for x in workspace.display_data.statistics], + ) + if self.save_probabilities.value: figure.subplot_imshow( colormap="gray", image=workspace.display_data.probabilities, sharexy=figure.subplot(0, 0), title=self.probabilities_name.value, - x=0, + x=2, + y=0, + ) + if self.denoise.value: + figure.subplot_imshow( + colormap="gray", + image=workspace.display_data.recon, + sharexy=figure.subplot(0, 0), + title=self.denoise.value, + x=2, y=1, ) From dd3140ea8717e7d7c2709e27d48bcb1c3b6606a8 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:05:04 -0700 Subject: [PATCH 05/19] support cp4, explicitly support omnipose --- active_plugins/runcellpose.py | 173 +++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 65 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 14df45cf..0133b6d7 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -50,27 +50,18 @@ **RunCellpose** uses a pre-trained machine learning model (Cellpose) to detect cells or nuclei in an image. This module is useful for automating simple segmentation tasks in CellProfiler. -The module accepts greyscale input images and produces an object set. Probabilities can also be captured as an image. +The module accepts greyscale input images and produces an object set. +Probabilities can also be captured as an image. -Loading in a model will take slightly longer the first time you run it each session. When evaluating -performance you may want to consider the time taken to predict subsequent images. +Loading in a model will take slightly longer the first time you run it each session. +When evaluating performance you may want to consider the time taken to predict subsequent images. -This module now also supports Ominpose. Omnipose builds on Cellpose, for the purpose of **RunCellpose** it adds 2 additional -features: additional models; bact-omni and cyto2-omni which were trained using the Omnipose architechture, and bact -and the mask reconstruction algorithm for Omnipose that was created to solve over-segemnation of large cells; useful for bacterial cells, -but can be used for other arbitrary and anisotropic shapes. You can mix and match Omnipose models with Cellpose style masking or vice versa. +This module is compatible with Omnipose, Cellpose 2, Cellpose 3, and Cellpose-SAM (4). -The module is compatible with Cellpose 1.0.2 >= 2.3.2. From the old version of the module the 'cells' model corresponds to 'cyto2' model. +You can run this module using Cellpose installed to the same Python environment as CellProfiler. +See our documentation at https://plugins.cellprofiler.org/runcellpose.html for more information on installation. -You can run this module using Cellpose installed to the same Python environment as CellProfiler. Alternatively, you can -run this module using Cellpose in a Docker that the module will automatically download for you so you do not have to perform -any installation yourself. - -To install Cellpose in your Python environment: -You'll want to run `pip install cellpose==2.3.2` on your CellProfiler Python environment to setup Cellpose. If you have an older version of Cellpose -run 'python -m pip install --force-reinstall -v cellpose==2.3.2'. - -To use Omnipose models, and mask reconstruction method you'll want to install Omnipose 'pip install omnipose' and Cellpose version 1.0.2 'pip install cellpose==1.0.2'. +Alternatively, you can run this module using Cellpose in a Docker that the module will automatically download for you so you do not have to perform any installation yourself. On the first time loading into CellProfiler, Cellpose will need to download some model files from the internet. This may take some time. If you want to use a GPU to run the model, you'll need a compatible version of PyTorch and a @@ -102,7 +93,7 @@ DENOISER_NAMES = ['denoise_cyto3', 'deblur_cyto3', 'upsample_cyto3', 'denoise_nuclei', 'deblur_nuclei', 'upsample_nuclei'] -# Only these models support size scaling +# Only these models support size scaling for v2/v3 SIZED_MODELS = {"cyto3", "cyto2", "cyto", "nuclei"} def get_custom_model_vars(self): @@ -155,9 +146,9 @@ def create_settings(self): ) self.cellpose_version = Choice( text="Select Cellpose version", - choices=['v2', 'v3', 'v4'], + choices=['omnipose', 'v2', 'v3', 'v4'], value='v3', - doc="Select the version of Cellpose you want to use. Note that v2 is compatible with either v1 or v2.") + doc="Select the version of Cellpose you want to use.") self.docker_image_v2 = Choice( text="Select Cellpose docker image", @@ -578,6 +569,14 @@ def validate_module(self, pipeline): % model_path, self.model_file_name, ) + def cleanup(self): + from torch import cuda + # Try to clear some GPU memory for other worker processes. + try: + cuda.empty_cache() + except Exception as e: + print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + def run(self, workspace): x_name = self.x_name.value y_name = self.y_name.value @@ -586,15 +585,20 @@ def run(self, workspace): dimensions = x.dimensions x_data = x.pixel_data + if self.cellpose_version.value == 'omnipose': + self.mode = self.mode_v2 + self.denoise.value = False # Denoising only supported in v3 if self.cellpose_version.value == 'v2': self.mode = self.mode_v2 self.docker_image = self.docker_image_v2 + self.denoise.value = False # Denoising only supported in v3 elif self.cellpose_version.value == 'v3': self.mode = self.mode_v3 self.docker_image = self.docker_image_v3 elif self.cellpose_version.value == 'v4': self.mode = self.mode_v4 self.docker_image = self.docker_image_v4 + self.denoise.value = False # Denoising only supported in v3 if self.rescale.value: rescale_x = x_data.copy() @@ -639,41 +643,45 @@ def run(self, workspace): from torch import cuda cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) + if self.cellpose_version.value == 'omnipose': + assert int(self.cellpose_ver[0])<2, "Cellpose version selected in RunCellpose module doesn't match version in Python" + assert float(self.cellpose_ver[0:3]) >= 0.6, "Cellpose v1/omnipose requires Cellpose >= 0.6" + if self.mode.value != 'custom': + model = models.Cellpose(model_type= self.mode.value, + gpu=self.use_gpu.value) + else: + model_file, model_directory, model_path = get_custom_model_vars(self) + model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + try: + y_data, flows, *_ = model.eval( + x_data, + channels=channels, + diameter=diam, + net_avg=self.use_averaging.value, + do_3D=self.do_3D.value, + anisotropy=anisotropy, + flow_threshold=self.flow_threshold.value, + cellprob_threshold=self.cellprob_threshold.value, + stitch_threshold=self.stitch_threshold.value, # is ignored if do_3D=True + min_size=self.min_size.value, + omni=self.omni.value, + invert=self.invert.value, + ) + except Exception as a: + print(f"Unable to create masks. Check your module settings. {a}") + finally: + if self.use_gpu.value and model.torch: + cleanup() + if self.cellpose_version.value == 'v2': - assert int(self.cellpose_ver[0])<=2, "Cellpose version selected in RunCellpose module doesn't match version in Python" - if float(self.cellpose_ver[0:3]) >= 0.6 and int(self.cellpose_ver[0])<2: - if self.mode.value != 'custom': - model = models.Cellpose(model_type= self.mode.value, - gpu=self.use_gpu.value) - else: - model_file, model_directory, model_path = get_custom_model_vars(self) - model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) - + assert int(self.cellpose_ver[0])==2, "Cellpose version selected in RunCellpose module doesn't match version in Python" + if self.mode.value != 'custom': + model = models.CellposeModel(model_type= self.mode.value, + gpu=self.use_gpu.value) else: - if self.mode.value != 'custom': - model = models.CellposeModel(model_type= self.mode.value, - gpu=self.use_gpu.value) - else: - model_file, model_directory, model_path = get_custom_model_vars(self) - model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) - + model_file, model_directory, model_path = get_custom_model_vars(self) + model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) try: - if float(self.cellpose_ver[0:3]) >= 0.7 and int(self.cellpose_ver[0])<2: - y_data, flows, *_ = model.eval( - x_data, - channels=channels, - diameter=diam, - net_avg=self.use_averaging.value, - do_3D=self.do_3D.value, - anisotropy=anisotropy, - flow_threshold=self.flow_threshold.value, - cellprob_threshold=self.cellprob_threshold.value, - stitch_threshold=self.stitch_threshold.value, - min_size=self.min_size.value, - omni=self.omni.value, - invert=self.invert.value, - ) - else: y_data, flows, *_ = model.eval( x_data, channels=channels, @@ -683,7 +691,7 @@ def run(self, workspace): anisotropy=anisotropy, flow_threshold=self.flow_threshold.value, cellprob_threshold=self.cellprob_threshold.value, - stitch_threshold=self.stitch_threshold.value, + stitch_threshold=self.stitch_threshold.value, # is ignored if do_3D=True min_size=self.min_size.value, invert=self.invert.value, ) @@ -691,11 +699,7 @@ def run(self, workspace): print(f"Unable to create masks. Check your module settings. {a}") finally: if self.use_gpu.value and model.torch: - # Try to clear some GPU memory for other worker processes. - try: - cuda.empty_cache() - except Exception as e: - print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + cleanup() elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -749,7 +753,7 @@ def run(self, workspace): anisotropy=anisotropy, flow_threshold=self.flow_threshold.value, cellprob_threshold=self.cellprob_threshold.value, - stitch_threshold=self.stitch_threshold.value, + stitch_threshold=self.stitch_threshold.value, # is ignored if do_3D=True min_size=self.min_size.value, invert=self.invert.value, ) @@ -762,15 +766,54 @@ def run(self, workspace): print(f"Unable to create masks. Check your module settings. {a}") finally: if self.use_gpu.value and model.torch: - # Try to clear some GPU memory for other worker processes. - try: - cuda.empty_cache() - except Exception as e: - print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + cleanup() elif self.cellpose_version.value == 'v4': assert int(self.cellpose_ver[0])==4, "Cellpose version selected in RunCellpose module doesn't match version in Python" - # TODO + if self.mode.value == 'custom': + model_file, model_directory, model_path = get_custom_model_vars(self) + model_params = (self.mode.value, self.use_gpu.value) + LOGGER.info(f"Loading new model: {self.mode.value}") + self.current_model = models.CellposeModel(gpu=self.use_gpu.value) + self.current_model_params = model_params + + if self.specify_diameter.value: + try: + y_data, flows, *_ = self.current_model.eval( + x_data, + diameter=diam, + do_3D=self.do_3D.value, + anisotropy=anisotropy, + flow_threshold=self.flow_threshold.value, + cellprob_threshold=self.cellprob_threshold.value, + stitch_threshold=self.stitch_threshold.value, # is ignored if do_3D=True + min_size=self.min_size.value, + invert=self.invert.value, + ) + + except Exception as a: + print(f"Unable to create masks. Check your module settings. {a}") + finally: + if self.use_gpu.value and model.torch: + cleanup() + else: + try: + y_data, flows, *_ = self.current_model.eval( + x_data, + do_3D=self.do_3D.value, + anisotropy=anisotropy, + flow_threshold=self.flow_threshold.value, + cellprob_threshold=self.cellprob_threshold.value, + stitch_threshold=self.stitch_threshold.value, # is ignored if do_3D=True + min_size=self.min_size.value, + invert=self.invert.value, + ) + + except Exception as a: + print(f"Unable to create masks. Check your module settings. {a}") + finally: + if self.use_gpu.value and model.torch: + cleanup() if self.remove_edge_masks: y_data = utils.remove_edge_masks(y_data) From 5fbe37d49d26b8cb894f2b9b979f89af26dd059f Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:38:56 -0700 Subject: [PATCH 06/19] update docs --- .../CP-plugins-documentation/runcellpose.md | 64 +++++++++++++++++-- .../supported_plugins.md | 6 +- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/documentation/CP-plugins-documentation/runcellpose.md b/documentation/CP-plugins-documentation/runcellpose.md index 744e6673..5061edc6 100644 --- a/documentation/CP-plugins-documentation/runcellpose.md +++ b/documentation/CP-plugins-documentation/runcellpose.md @@ -1,12 +1,65 @@ # RunCellpose -RunCellpose is one of the modules that has additional dependencies that are not packaged with the built CellProfiler. -Therefore, you must additionally download RunCellpose's dependencies. -See [Using Plugins](using_plugins.md) for more information. +You can run RunCellpose using Cellpose in a Docker that the module will automatically download for you so you do not have to perform any installation yourself. +See [Using plugins - Using Docker](using_plugins.md/#using-docker-to-bypass-installation-requirements) for more information on using Docker with CellProfiler. + +You can also this module using Cellpose installed to the same Python environment as CellProfiler. +See [Using plugins - Installing dependencies](using_plugins.md/#installing-plugins-with-dependencies-using-cellprofiler-from-source) for more information on installing dependencies for CellProfiler plugins. + +## Installing Cellpose in the same Python environment as CellProfiler + +We provide some information below about installations that have worked for us. +If you are having challenges with installing Cellpose in your CellProfiler environment, please reach out on the [forum](https://forum.image.sc/). + +### Omnipose (Cellpose 1) + +In an environment that has Cellprofiler installed, run the following commands to install Omnipose and Cellpose 1: + +```bash +pip install omnipose +pip install cellpose==1.0.2 +``` + +### Cellpose 2 + +In an environment that has Cellprofiler installed, run the following commands to install Cellpose 2: + +```bash +pip install cellpose==2.3.2 +``` + +If you have an older version of Cellpose, run the following command to reinstall Cellpose 2: + +```bash +python -m pip install --force-reinstall -v cellpose==2.3.2 +``` + +### Cellpose 3 + +In a Python 3.9 environment that has Cellprofiler installed, run the following commands to install Cellpose 3: + +```bash +python3.9 -m pip install cellpose==3.1.1.2 +python3.9 -m pip install numpy==1.24.4 +``` + +### Cellpose-SAM (Cellpose 4) + +On Mac M1/M2, to create a new environment with CellProfiler and Cellpose 4, run the following commands: + +```bash +export LDFLAGS="-L/opt/homebrew/opt/mysql@8.0/lib" +export CPPFLAGS="-I/opt/homebrew/opt/mysql@8.0/include" +export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql@8.0/lib/pkgconfig" +conda create -y --force -n cellposeSAM_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk +conda activate cellposeSAM_cellprofiler +pip install cellpose==4.0.6 +pip install mysqlclient==1.4.6 cellprofiler +``` ## Using RunCellpose with a GPU -If you want to use a GPU to run the model (this is recommended for speed), you'll need a compatible version of PyTorch and a supported GPU. +If you want to use a GPU to run the model (this is recommended for speed), you'll need a compatible version of PyTorch and a supported GPU. General instructions are available at this [link](https://pytorch.org/get-started/locally/). 1. Your GPU should be visible in Device Manager under Display Adaptors. @@ -35,4 +88,5 @@ If your GPU isn't there, you likely need to install drivers. W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found 2022-05-26 20:24:21.906286: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine. ``` -If you don't have a GPU, this is not a problem. If you do, your configuration is incorrect and you need to try reinstalling drivers and the correct version of CUDA for your system. \ No newline at end of file +If you don't have a GPU, this is not a problem. +If you do, your configuration is incorrect and you need to try reinstalling drivers and the correct version of CUDA for your system. diff --git a/documentation/CP-plugins-documentation/supported_plugins.md b/documentation/CP-plugins-documentation/supported_plugins.md index e0db0e8f..3cdc6bc6 100644 --- a/documentation/CP-plugins-documentation/supported_plugins.md +++ b/documentation/CP-plugins-documentation/supported_plugins.md @@ -15,6 +15,7 @@ Those plugins that do have extra documentation contain links below. | CalculateMoments | CalculateMoments extracts moments statistics from a given distribution of pixel values. | No | | N/A | | CallBarcodes | CallBarcodes is used for assigning a barcode to an object based on the channel with the strongest intensity for a given number of cycles. It is used for optical sequencing by synthesis (SBS). | No | | N/A | | CompensateColors | CompensateColors determines how much signal in any given channel is because of bleed-through from another channel and removes the bleed-through. It can be performed across an image or masked to objects and provides a number of preprocessing and rescaling options to allow for troubleshooting if input image intensities are not well matched. | No | | N/A | +| ConvertObjectsToOutlines | ConvertObjectsToOutlines takes in black images with white object outlines and uses them to predict objects. See [Ask Erin Dear Beth video podcast](https://youtu.be/qWYyp_a0IHM) for a walkthrough of using the module. | No | | N/A | | DistanceTransform | DistanceTransform computes the distance transform of a binary image. The distance of each foreground pixel is computed to the nearest background pixel and the resulting image is then scaled so that the largest distance is 1. | No | | N/A | | EnforceObjectsOneToOne| EnforceObjectsOneToOne generates Primary and Secondary object relationships for any pair of objects in a similar manner to the relationships established by IdentifyPrimaryObjects and IdentifySecondaryObjects. It is particularly useful for relating objects identified using Deep Learning. | No | | N/A | | EnhancedMeasureTexture| EnhancedMeasureTexture measures the degree and nature of textures within an image or objects in a more comprehensive/tuneable manner than the MeasureTexture module native to CellProfiler. | No | | N/A | @@ -22,9 +23,10 @@ Those plugins that do have extra documentation contain links below. | HistogramEqualization | HistogramEqualization increases the global contrast of a low-contrast image or volume. Histogram equalization redistributes intensities to utilize the full range of intensities, such that the most common frequencies are more distinct. This module can perform either global or local histogram equalization. | No | | N/A | | HistogramMatching | HistogramMatching manipulates the pixel intensity values an input image and matches them to the histogram of a reference image. It can be used as a way to normalize intensities across different 2D or 3D images or different frames of the same 3D image. It allows you to choose which frame to use as the reference. | No | | N/A | | PixelShuffle | PixelShuffle takes the intensity of each pixel in an image and randomly shuffles its position. | No | | N/A | -| [RunCellpose](RunCellPose.md) | RunCellpose allows you to run Cellpose within CellProfiler. Cellpose is a generalist machine-learning algorithm for cellular segmentation and is a great starting point for segmenting non-round cells. You can use pre-trained Cellpose models or your custom model with this plugin. You can use a GPU with this module to dramatically increase your speed/efficiency. | Yes | `cellpose` | Yes | +| [RunCellpose](RunCellPose.md) | RunCellpose allows you to run Cellpose within CellProfiler. Cellpose is a generalist machine-learning algorithm for cellular segmentation and is a great starting point for segmenting non-round cells. You can use pre-trained Cellpose models or your custom model with this plugin. You can use a GPU with this module to dramatically increase your speed/efficiency. It currently supports Cellpose 1-4. | Yes | `cellpose` | Yes | | Runilastik | Runilasitk allows to run ilastik within CellProfiler. You can use pre-trained ilastik projects/models to predict the probability of your input images. The plugin supports two types of ilastik projects: Pixel Classification and Autocontext (2-stage).| Yes | | Yes | | RunImageJScript | RunImageJScript allows you to run any supported ImageJ script directly within CellProfiler. It is significantly more performant than RunImageJMacro, and is also less likely to leave behind temporary files. | Yes | `imagejscript` , though note that conda installation may be preferred, see [this link](https://py.imagej.net/en/latest/Install.html#installing-via-pip) for more information | No | -| RunOmnipose | RunOmnipose allows you to run Omnipose within CellProfiler. Omnipose is a general image segmentation tool that builds on Cellpose. | Yes | `omnipose` | No | +| RunOmnipose | RunOmnipose allows you to run Omnipose within CellProfiler. Omnipose is a general image segmentation tool that builds on Cellpose. Omnipose usage is also supported from within the RunCellpose module. | Yes | `omnipose` | No | | RunStarDist | RunStarDist allows you to run StarDist within CellProfiler. StarDist is a machine-learning algorithm for object detection with star-convex shapes making it best suited for nuclei or round-ish cells. You can use pre-trained StarDist models or your custom model with this plugin. You can use a GPU with this module to dramatically increase your speed/efficiency. RunStarDist is generally faster than RunCellpose. | Yes | `stardist` | No | +| RunVista2D | This module uses a pre-trained VISTA2D model to detect cell objects in an image. | No | | N/A | | VarianceTransform | This module allows you to calculate the variance of an image, using a determined window size. It also has the option to find the optimal window size from a predetermined range to obtain the maximum variance of an image. | No | | N/A | From d7597fa424dcb2fa4833faf67dc3c28049473c7c Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:40:20 -0700 Subject: [PATCH 07/19] support cellpose 1-4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bb3e97cf..506fe7a0 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ ] cellpose_deps = [ - "cellpose>=1.0.2,<3.0" + "cellpose>=1.0.2,<5.0" ] omnipose_deps = [ From f66764cfcd6b0e11757598033c01bd2bc0bb47ad Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:31:04 -0700 Subject: [PATCH 08/19] intuitive visible settings, finish saving denoise image --- active_plugins/runcellpose.py | 51 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 0133b6d7..6983c882 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -67,6 +67,9 @@ may take some time. If you want to use a GPU to run the model, you'll need a compatible version of PyTorch and a supported GPU. Instructions are avaiable at this link: {CUDA_LINK} +Note that RunCellpose supports the Cellpose 3 functionality of using image restoration models to improve the input images before segmentation for both Docker and Python methods. +However, it only supports saving out or visualizing the intermediate restored images when using the Python method. + Stringer, C., Wang, T., Michaelos, M. et al. Cellpose: a generalist algorithm for cellular segmentation. Nat Methods 18, 100–106 (2021). {Cellpose_link} Kevin J. Cutler, Carsen Stringer, Paul A. Wiggins, Joseph D. Mougous. Omnipose: a high-precision morphology-independent solution for bacterial cell segmentation. bioRxiv 2021.11.03.467199. {Omnipose_link} ============ ============ =============== @@ -435,6 +438,19 @@ def set_directory_fn(path): N.b. for upsampling it is essential that the "Expected diameter" setting is correct for the input images """, ) + self.denoise_image = Binary( + text="Save preprocessed image?", + value=False, + doc=""" + If enabled, the intermediate preprocessed image will be recorded as a new image. + This is only supported for Python mode of Cellpose 3. + """, + ) + self.denoise_name = ImageName( + "Name the preprocessed image", + "Preprocessed", + doc="Enter the name you want to call the preprocessed image produced by this module.", + ) def settings(self): return [ @@ -471,10 +487,18 @@ def settings(self): self.probability_rescale_setting, self.denoise, self.denoise_type, + self.denoise_image, + self.denoise_name, ] def visible_settings(self): - vis_settings = [self.rescale, self.docker_or_python, self.cellpose_version] + vis_settings = [self.rescale, self.cellpose_version] + + if self.cellpose_version.value == 'omnipose': # omnipose only supports Python, not Docker + self.docker_or_python.value = "Python" + vis_settings += [self.omni] + else: + vis_settings += [self.docker_or_python] if self.docker_or_python.value == "Docker": if self.cellpose_version.value == 'v2': @@ -496,9 +520,6 @@ def visible_settings(self): vis_settings += [self.x_name] - if self.docker_or_python.value == "Python": - vis_settings += [self.omni] - if self.mode.value != "nuclei": vis_settings += [self.supply_nuclei] if self.supply_nuclei.value: @@ -520,6 +541,11 @@ def visible_settings(self): self.y_name, self.save_probabilities, ] + if self.save_probabilities.value: + vis_settings += [self.probabilities_name] + if self.docker_or_python.value == 'Python': + vis_settings += [self.probability_rescale_setting] + if self.cellpose_version.value in ['v2','v3']: vis_settings += [self.invert] @@ -528,11 +554,6 @@ def visible_settings(self): if self.do_3D.value: vis_settings.remove(self.stitch_threshold) - if self.save_probabilities.value: - vis_settings += [self.probabilities_name] - if self.docker_or_python.value == 'Python': - vis_settings += [self.probability_rescale_setting] - vis_settings += [self.use_averaging, self.use_gpu] if self.docker_or_python.value == 'Python': @@ -542,7 +563,9 @@ def visible_settings(self): if self.cellpose_version.value == 'v3': vis_settings += [self.denoise] if self.denoise.value: - vis_settings += [self.denoise_type] + vis_settings += [self.denoise_type, self.denoise_image] + if self.denoise_image.value: + vis_settings += [self.denoise_name] return vis_settings @@ -876,8 +899,6 @@ def run(self, workspace): cellpose_output = numpy.load(os.path.join(temp_img_dir, unique_name + "_seg.npy"), allow_pickle=True).item() y_data = cellpose_output["masks"] flows = cellpose_output["flows"] - if self.denoise.value: - img_restore = cellpose_output["restore"] #TODO don't think this is correct for retrieving image for plotting finally: # Delete the temporary files try: @@ -896,11 +917,11 @@ def run(self, workspace): if self.denoise.value and self.show_window: # Need to remove unnecessary extra axes - denoised_image = numpy.squeeze(img_restore) + denoised_image = numpy.squeeze(input_data) if "upsample" in self.denoise_type.value: denoised_image = skimage.transform.resize( denoised_image, x_data.shape) - workspace.display_data.recon = denoised_image + workspace.display_data.denoised_image = denoised_image if self.save_probabilities.value: if self.docker_or_python.value == "Docker": @@ -1025,7 +1046,7 @@ def display(self, workspace, figure): if self.denoise.value: figure.subplot_imshow( colormap="gray", - image=workspace.display_data.recon, + image=workspace.display_data.denoised_image, sharexy=figure.subplot(0, 0), title=self.denoise.value, x=2, From 615733703471d2425d4fbc112885941c7bca617b Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 29 Jul 2025 12:27:13 -0700 Subject: [PATCH 09/19] fix denoise display, install instruction --- active_plugins/runcellpose.py | 2 +- documentation/CP-plugins-documentation/runcellpose.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 6983c882..6d94eced 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -1048,7 +1048,7 @@ def display(self, workspace, figure): colormap="gray", image=workspace.display_data.denoised_image, sharexy=figure.subplot(0, 0), - title=self.denoise.value, + title=self.denoise_name.value, x=2, y=1, ) diff --git a/documentation/CP-plugins-documentation/runcellpose.md b/documentation/CP-plugins-documentation/runcellpose.md index 5061edc6..ba9caa65 100644 --- a/documentation/CP-plugins-documentation/runcellpose.md +++ b/documentation/CP-plugins-documentation/runcellpose.md @@ -36,11 +36,16 @@ python -m pip install --force-reinstall -v cellpose==2.3.2 ### Cellpose 3 -In a Python 3.9 environment that has Cellprofiler installed, run the following commands to install Cellpose 3: +On Mac M1/M2, to create a new environment with CellProfiler and Cellpose 4, run the following commands: ```bash -python3.9 -m pip install cellpose==3.1.1.2 -python3.9 -m pip install numpy==1.24.4 +export LDFLAGS="-L/opt/homebrew/opt/mysql@8.0/lib" +export CPPFLAGS="-I/opt/homebrew/opt/mysql@8.0/include" +export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql@8.0/lib/pkgconfig" +conda create -y --force -n cellpose3_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk +conda activate cellpose3_cellprofiler +pip install cellpose==3.1.1.2 +pip install mysqlclient==1.4.6 cellprofiler ``` ### Cellpose-SAM (Cellpose 4) From dc49fe3ea70b9019bf08fce1c67fb9bd3be8b535 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:41:42 -0700 Subject: [PATCH 10/19] debug GPU --- active_plugins/runcellpose.py | 63 +++++++++++++------ .../CP-plugins-documentation/runcellpose.md | 4 +- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 6d94eced..7ddc5d49 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -105,6 +105,14 @@ def get_custom_model_vars(self): model_path = os.path.join(model_directory, model_file) return model_file, model_directory, model_path +def cleanup(self): + # Try to clear some GPU memory for other worker processes. + try: + from torch import cuda + cuda.empty_cache() + except Exception as e: + print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") + class RunCellpose(ImageSegmentation): category = "Object Processing" @@ -592,14 +600,6 @@ def validate_module(self, pipeline): % model_path, self.model_file_name, ) - def cleanup(self): - from torch import cuda - # Try to clear some GPU memory for other worker processes. - try: - cuda.empty_cache() - except Exception as e: - print(f"Unable to clear GPU memory. You may need to restart CellProfiler to change models. {e}") - def run(self, workspace): x_name = self.x_name.value y_name = self.y_name.value @@ -662,10 +662,6 @@ def run(self, workspace): from cellpose import models, io, core, utils self.cellpose_ver = importlib.metadata.version('cellpose') - if self.use_gpu.value and model.torch: - from torch import cuda - cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) - if self.cellpose_version.value == 'omnipose': assert int(self.cellpose_ver[0])<2, "Cellpose version selected in RunCellpose module doesn't match version in Python" assert float(self.cellpose_ver[0:3]) >= 0.6, "Cellpose v1/omnipose requires Cellpose >= 0.6" @@ -675,6 +671,11 @@ def run(self, workspace): else: model_file, model_directory, model_path = get_custom_model_vars(self) model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + + if self.use_gpu.value and model.torch: + from torch import cuda + cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) + try: y_data, flows, *_ = model.eval( x_data, @@ -694,7 +695,7 @@ def run(self, workspace): print(f"Unable to create masks. Check your module settings. {a}") finally: if self.use_gpu.value and model.torch: - cleanup() + cleanup(self) if self.cellpose_version.value == 'v2': assert int(self.cellpose_ver[0])==2, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -704,6 +705,11 @@ def run(self, workspace): else: model_file, model_directory, model_path = get_custom_model_vars(self) model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + + if self.use_gpu.value and model.torch: + from torch import cuda + cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) + try: y_data, flows, *_ = model.eval( x_data, @@ -722,7 +728,7 @@ def run(self, workspace): print(f"Unable to create masks. Check your module settings. {a}") finally: if self.use_gpu.value and model.torch: - cleanup() + cleanup(self) elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -738,6 +744,15 @@ def run(self, workspace): model_type=self.mode.value, gpu=self.use_gpu.value) self.current_model_params = model_params + if self.use_gpu.value: + try: + from torch import cuda + cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) + except: + print( + "Failed to set GPU memory share. Please check your PyTorch installation. Not setting per-process memory share." + ) + if self.denoise.value: from cellpose import denoise recon_params = ( @@ -788,8 +803,8 @@ def run(self, workspace): except Exception as a: print(f"Unable to create masks. Check your module settings. {a}") finally: - if self.use_gpu.value and model.torch: - cleanup() + if self.use_gpu.value: + cleanup(self) elif self.cellpose_version.value == 'v4': assert int(self.cellpose_ver[0])==4, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -800,6 +815,15 @@ def run(self, workspace): self.current_model = models.CellposeModel(gpu=self.use_gpu.value) self.current_model_params = model_params + if self.use_gpu.value: + try: + from torch import cuda + cuda.set_per_process_memory_fraction(self.manual_GPU_memory_share.value) + except: + print( + "Failed to set GPU memory share. Please check your PyTorch installation. Not setting per-process memory share." + ) + if self.specify_diameter.value: try: y_data, flows, *_ = self.current_model.eval( @@ -818,7 +842,7 @@ def run(self, workspace): print(f"Unable to create masks. Check your module settings. {a}") finally: if self.use_gpu.value and model.torch: - cleanup() + cleanup(self) else: try: y_data, flows, *_ = self.current_model.eval( @@ -835,8 +859,8 @@ def run(self, workspace): except Exception as a: print(f"Unable to create masks. Check your module settings. {a}") finally: - if self.use_gpu.value and model.torch: - cleanup() + if self.use_gpu.value: + cleanup(self) if self.remove_edge_masks: y_data = utils.remove_edge_masks(y_data) @@ -1055,6 +1079,7 @@ def display(self, workspace, figure): def do_check_gpu(self): import importlib.util + from cellpose import core torch_installed = importlib.util.find_spec('torch') is not None self.cellpose_ver = importlib.metadata.version('cellpose') #if the old version of cellpose <2.0, then use istorch kwarg diff --git a/documentation/CP-plugins-documentation/runcellpose.md b/documentation/CP-plugins-documentation/runcellpose.md index ba9caa65..30a95150 100644 --- a/documentation/CP-plugins-documentation/runcellpose.md +++ b/documentation/CP-plugins-documentation/runcellpose.md @@ -42,7 +42,7 @@ On Mac M1/M2, to create a new environment with CellProfiler and Cellpose 4, run export LDFLAGS="-L/opt/homebrew/opt/mysql@8.0/lib" export CPPFLAGS="-I/opt/homebrew/opt/mysql@8.0/include" export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql@8.0/lib/pkgconfig" -conda create -y --force -n cellpose3_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk +conda create -y --force -n cellpose3_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk --platform osx-arm64 conda activate cellpose3_cellprofiler pip install cellpose==3.1.1.2 pip install mysqlclient==1.4.6 cellprofiler @@ -56,7 +56,7 @@ On Mac M1/M2, to create a new environment with CellProfiler and Cellpose 4, run export LDFLAGS="-L/opt/homebrew/opt/mysql@8.0/lib" export CPPFLAGS="-I/opt/homebrew/opt/mysql@8.0/include" export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql@8.0/lib/pkgconfig" -conda create -y --force -n cellposeSAM_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk +conda create -y --force -n cellposeSAM_cellprofiler python=3.9 h5py=3.6.0 python.app scikit-learn==0.24.2 scikit-image==0.18.3 openjdk --platform osx-arm64 conda activate cellposeSAM_cellprofiler pip install cellpose==4.0.6 pip install mysqlclient==1.4.6 cellprofiler From 657015904414c20410d6ee09f88e4a84d934d896 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:49:24 -0700 Subject: [PATCH 11/19] add troubleshooting --- .../CP-plugins-documentation/runcellpose.md | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/documentation/CP-plugins-documentation/runcellpose.md b/documentation/CP-plugins-documentation/runcellpose.md index 30a95150..cbe442a7 100644 --- a/documentation/CP-plugins-documentation/runcellpose.md +++ b/documentation/CP-plugins-documentation/runcellpose.md @@ -6,6 +6,13 @@ See [Using plugins - Using Docker](using_plugins.md/#using-docker-to-bypass-inst You can also this module using Cellpose installed to the same Python environment as CellProfiler. See [Using plugins - Installing dependencies](using_plugins.md/#installing-plugins-with-dependencies-using-cellprofiler-from-source) for more information on installing dependencies for CellProfiler plugins. +## Troubleshooting + +If you are running RunCellpose in a docker and you get an error like this: +`No such file or directory: '/Users/eweisbar/Documents/CPoutput/.cellprofiler_temp/c564a507-17e5-4256-837d-6e1d59892524/img/c564a507-17e5-4256-837d-6e1d59892524_seg.npy` +It is likely an issue with running out of memory in the Docker. +See [this Github issue](https://github.com/CellProfiler/CellProfiler-plugins/issues/243) for more information. + ## Installing Cellpose in the same Python environment as CellProfiler We provide some information below about installations that have worked for us. @@ -67,31 +74,36 @@ pip install mysqlclient==1.4.6 cellprofiler If you want to use a GPU to run the model (this is recommended for speed), you'll need a compatible version of PyTorch and a supported GPU. General instructions are available at this [link](https://pytorch.org/get-started/locally/). -1. Your GPU should be visible in Device Manager under Display Adaptors. +1. Your GPU should be visible in Device Manager under Display Adaptors. If your GPU isn't there, you likely need to install drivers. [Here](https://www.nvidia.com/Download/Find.aspx) is where you can find NVIDIA GPU drivers if you need to install them. - 2. To test whether the GPU is configured correctly: - * Run `python` on the command line (i.e., in Command Prompt or Terminal) to start an interactive session - * Then run the following - ``` + +* Run `python` on the command line (i.e., in Command Prompt or Terminal) to start an interactive session +* Then run the following + + ```bash import torch torch.cuda.is_available() ``` - * If this returns `True`, you're all set - * If this returns `False`, you likely need to install/reinstall torch. See [here](https://pytorch.org/get-started/locally/) for your exact command. - * Exit the session with `exit()` then install torch if necessary - ``` + +* If this returns `True`, you're all set +* If this returns `False`, you likely need to install/reinstall torch. See [here](https://pytorch.org/get-started/locally/) for your exact command. +* Exit the session with `exit()` then install torch if necessary + + ```bash pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113 ``` - If you have a previous version of torch installed, make sure to run `pip uninstall torch` first. + If you have a previous version of torch installed, make sure to run `pip uninstall torch` first. **NOTE**: You might get a warning like this: -``` + +```bash W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found 2022-05-26 20:24:21.906286: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine. ``` + If you don't have a GPU, this is not a problem. If you do, your configuration is incorrect and you need to try reinstalling drivers and the correct version of CUDA for your system. From 976e50a20d1dba71433642326b9b87c75fed1a35 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 29 Jul 2025 15:02:22 -0700 Subject: [PATCH 12/19] variable revision number --- active_plugins/runcellpose.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 7ddc5d49..5d267511 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -191,7 +191,7 @@ def create_settings(self): self.specify_diameter = Binary( text="Specify expected object diameter?", - value=True, + value=False, doc="""\ Cellpose 4 was trained on images with ROI diameters from size 7.5 to 120, with a mean diameter of 30 pixels. Thus the model has a good amount of size-invariance, meaning that specifying the diameter is optional. @@ -1114,8 +1114,11 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name setting_values = setting_values + [False] variable_revision_number = 6 if variable_revision_number == 6: - #TODO - setting_values = setting_values[0:2] + new_setting_values = setting_values[0:2] + new_setting_values += ['v3', setting_values[2], CELLPOSE_DOCKERS['v3'][0], CELLPOSE_DOCKERS['v4'][0], setting_values[3]] + new_setting_values += [False, setting_values[4], MODEL_NAMES['v3'][0], MODEL_NAMES['v4'][0]] + new_setting_values += [setting_values[5:], False, DENOISER_NAMES[0], False, "Preprocessed"] + setting_values = new_setting_values variable_revision_number = 7 return setting_values, variable_revision_number From dfd7dbcb687caa8cfbfbf35fed8667e014720281 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Wed, 30 Jul 2025 09:15:13 -0700 Subject: [PATCH 13/19] only check for cellpose import if using python --- active_plugins/runcellpose.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 5d267511..4aa965ad 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -578,20 +578,20 @@ def visible_settings(self): return vis_settings def validate_module(self, pipeline): - """If using custom model, validate the model file opens and works""" - from cellpose import models - if self.mode.value == "custom": - model_file = self.model_file_name.value - model_directory = self.model_directory.get_absolute_path() - model_path = os.path.join(model_directory, model_file) - try: - open(model_path) - except: - raise ValidationError( - "Failed to load custom file: %s " % model_path, - self.model_file_name, - ) - if self.docker_or_python.value == "Python": + if self.docker_or_python.value == "Python": + """If using custom model, validate the model file opens and works""" + from cellpose import models + if self.mode.value == "custom": + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) + try: + open(model_path) + except: + raise ValidationError( + "Failed to load custom file: %s " % model_path, + self.model_file_name, + ) try: model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) except: From ecf0167b92b296d98b113ef1580de711fa9f32c4 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Wed, 30 Jul 2025 09:28:46 -0700 Subject: [PATCH 14/19] RunCellpose troubleshooting --- documentation/CP-plugins-documentation/runcellpose.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/documentation/CP-plugins-documentation/runcellpose.md b/documentation/CP-plugins-documentation/runcellpose.md index cbe442a7..65085a46 100644 --- a/documentation/CP-plugins-documentation/runcellpose.md +++ b/documentation/CP-plugins-documentation/runcellpose.md @@ -13,6 +13,11 @@ If you are running RunCellpose in a docker and you get an error like this: It is likely an issue with running out of memory in the Docker. See [this Github issue](https://github.com/CellProfiler/CellProfiler-plugins/issues/243) for more information. +If you open a saved pipeline that contains the RunCellpose module and your saved settings are not parsed properly, we recommend you add a new RunCellpose module to your pipeline and copy the settings from the old module to the new module. + +If you get an error message indicating the Docker selected in your pipeline is not available, this is likely because we updated the Docker image names to more explicitly indicate which version of Cellpose they are using. +Please select one of the updated Docker image names in your pipeline. + ## Installing Cellpose in the same Python environment as CellProfiler We provide some information below about installations that have worked for us. From 59bb39445065d7f3e22552c1737e6dbad0fada2a Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:07:58 -0700 Subject: [PATCH 15/19] support omnipose in docker --- active_plugins/runcellpose.py | 81 +++++++++++++------ .../Dockerfile | 0 .../download_cellpose_models.py | 0 dockerfiles/RunCellpose_Cellposev3/Dockerfile | 7 ++ .../download_cellpose_models.py | 8 ++ dockerfiles/RunCellpose_Cellposev4/Dockerfile | 7 ++ .../download_cellpose_models.py | 6 ++ 7 files changed, 84 insertions(+), 25 deletions(-) rename dockerfiles/{RunCellpose => RunCellpose_Cellposev2}/Dockerfile (100%) rename dockerfiles/{RunCellpose => RunCellpose_Cellposev2}/download_cellpose_models.py (100%) create mode 100644 dockerfiles/RunCellpose_Cellposev3/Dockerfile create mode 100644 dockerfiles/RunCellpose_Cellposev3/download_cellpose_models.py create mode 100644 dockerfiles/RunCellpose_Cellposev4/Dockerfile create mode 100644 dockerfiles/RunCellpose_Cellposev4/download_cellpose_models.py diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 4aa965ad..dcd9fb0a 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -81,14 +81,16 @@ """ "Select Cellpose Docker Image" -CELLPOSE_DOCKERS = {'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", +CELLPOSE_DOCKERS = {'omnipose': ["gnodar01/runcellpose_omnipose_no_pretrained:0.0.1"], # TODO make official + 'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.2"], - 'v3': ["erinweisbart/cellpose:3.1.1.2"], #TODO - 'v4': ["erinweisbart/cellpose:4.0.5"]} #TODO + 'v3': ["erinweisbart/cellpose:3.1.1.2"], # TODO make official + 'v4': ["erinweisbart/cellpose:4.0.5"]} # TODO make official "Detection mode" -MODEL_NAMES = {'v2':['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', +MODEL_NAMES = {'omnipose':['bact_phase_omni','bact_fluor_omni','cyto2_omni','worm_omni','plant_omni','bact_phase_affinity','cyto','nuclei','custom'], + 'v2':['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'], 'v3':[ "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", "yeast_BF_cp3", "bact_phase_cp3", "bact_fluor_cp3", "deepbacs_cp3", "cyto2", "cyto", "custom"], @@ -160,6 +162,16 @@ def create_settings(self): choices=['omnipose', 'v2', 'v3', 'v4'], value='v3', doc="Select the version of Cellpose you want to use.") + + self.docker_image_omnipose = Choice( + text="Select Cellpose docker image", + choices=CELLPOSE_DOCKERS['omnipose'], + value=CELLPOSE_DOCKERS['omnipose'][0], + doc="""\ +Select which Docker image to use for running Cellpose. +If you are not using a custom model, you should select a Docker image **with pretrained**. If you are using a custom model, +you can use any of the available Dockers, but those with pretrained models will be slightly larger (~500 MB). +""") self.docker_image_v2 = Choice( text="Select Cellpose docker image", @@ -211,7 +223,15 @@ def create_settings(self): Note that automatic diameter mode does not work when running on 3D images. """, ) - + self.mode_omnipose = Choice( + text="Detection mode", + choices=MODEL_NAMES['omnipose'], + value=MODEL_NAMES['omnipose'][0], + doc="""\ +CellPose comes with models for detecting nuclei or cells. Alternatively, you can supply a custom-trained model +generated using the command line or Cellpose GUI. Custom models can be useful if working with unusual cell types. +""", + ) self.mode_v2 = Choice( text="Detection mode", choices=MODEL_NAMES['v2'], @@ -244,7 +264,7 @@ def create_settings(self): text="Use Omnipose for mask reconstruction", value=False, doc="""\ -If enabled, use omnipose mask recontruction features will be used (Omnipose installation required and CellPose >= 1.0) """, +If enabled, use omnipose mask recontruction features will be used (Omnipose installation required and CellPose >= 1.0, <2) """, ) self.do_3D = Binary( @@ -466,11 +486,13 @@ def settings(self): self.rescale, self.docker_or_python, self.cellpose_version, + self.docker_image_omnipose, self.docker_image_v2, self.docker_image_v3, self.docker_image_v4, self.specify_diameter, self.expected_diameter, + self.mode_omnipose, self.mode_v2, self.mode_v3, self.mode_v4, @@ -500,23 +522,25 @@ def settings(self): ] def visible_settings(self): - vis_settings = [self.rescale, self.cellpose_version] + vis_settings = [self.rescale, self.cellpose_version,self.docker_or_python] - if self.cellpose_version.value == 'omnipose': # omnipose only supports Python, not Docker - self.docker_or_python.value = "Python" + if self.cellpose_version.value == 'omnipose': vis_settings += [self.omni] - else: - vis_settings += [self.docker_or_python] if self.docker_or_python.value == "Docker": - if self.cellpose_version.value == 'v2': + if self.cellpose_version.value == 'omnipose': + vis_settings += [self.docker_image_omnipose] + elif self.cellpose_version.value == 'v2': vis_settings += [self.docker_image_v2] elif self.cellpose_version.value == 'v3': vis_settings += [self.docker_image_v3] elif self.cellpose_version.value == 'v4': vis_settings += [self.docker_image_v4] - - if self.cellpose_version.value == 'v2': + + if self.cellpose_version.value == 'omnipose': + vis_settings += [self.mode_omnipose] + self.mode = self.mode_omnipose + elif self.cellpose_version.value == 'v2': vis_settings += [self.mode_v2] self.mode = self.mode_v2 elif self.cellpose_version.value == 'v3': @@ -542,19 +566,24 @@ def visible_settings(self): if self.specify_diameter.value: vis_settings += [self.expected_diameter] + if self.cellpose_version.value != 'omnipose': + vis_settings += [ + self.cellprob_threshold, + self.min_size, + ] + vis_settings += [ - self.cellprob_threshold, - self.min_size, self.flow_threshold, self.y_name, self.save_probabilities, ] + if self.save_probabilities.value: vis_settings += [self.probabilities_name] if self.docker_or_python.value == 'Python': vis_settings += [self.probability_rescale_setting] - if self.cellpose_version.value in ['v2','v3']: + if self.cellpose_version.value in ['omnipose','v2','v3']: vis_settings += [self.invert] vis_settings += [self.do_3D, self.stitch_threshold, self.remove_edge_masks] @@ -609,7 +638,8 @@ def run(self, workspace): x_data = x.pixel_data if self.cellpose_version.value == 'omnipose': - self.mode = self.mode_v2 + self.mode = self.mode_omnipose + self.docker_image = self.docker_image_omnipose self.denoise.value = False # Denoising only supported in v3 if self.cellpose_version.value == 'v2': self.mode = self.mode_v2 @@ -901,8 +931,8 @@ def run(self, workspace): if self.cellpose_version.value == 'v3': if self.denoise.value: cmd += ['--restore_type', self.denoise_type.value] - if self.cellpose_version.value in ['v2','v3']: - cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] + if self.cellpose_version.value in ['omnipose','v2','v3']: + cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(self.expected_diameter)] if self.cellpose_version.value in ['v4']: if self.specify_diameter.value: cmd += ['--diameter', str(diam)] @@ -910,9 +940,10 @@ def run(self, workspace): cmd += ['--net_avg'] if self.do_3D.value: cmd += ['--do_3D'] - cmd += ['--anisotropy', str(anisotropy), '--flow_threshold', str(self.flow_threshold.value), '--cellprob_threshold', - str(self.cellprob_threshold.value), '--stitch_threshold', str(self.stitch_threshold.value), '--min_size', str(self.min_size.value)] - if self.cellpose_version.value in ['v2','v3']: + if self.cellpose_version.value != 'omnipose': + cmd += ['--cellprob_threshold', str(self.cellprob_threshold.value), '--min_size', str(self.min_size.value)] + cmd += ['--anisotropy', str(anisotropy), '--flow_threshold', str(self.flow_threshold.value), '--stitch_threshold', str(self.stitch_threshold.value)] + if self.cellpose_version.value in ['omnipose','v2','v3']: if self.invert.value: cmd += ['--invert'] if self.remove_edge_masks.value: @@ -1115,8 +1146,8 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name variable_revision_number = 6 if variable_revision_number == 6: new_setting_values = setting_values[0:2] - new_setting_values += ['v3', setting_values[2], CELLPOSE_DOCKERS['v3'][0], CELLPOSE_DOCKERS['v4'][0], setting_values[3]] - new_setting_values += [False, setting_values[4], MODEL_NAMES['v3'][0], MODEL_NAMES['v4'][0]] + new_setting_values += ['v3', CELLPOSE_DOCKERS['omnipose'][0], setting_values[2], CELLPOSE_DOCKERS['v3'][0], CELLPOSE_DOCKERS['v4'][0], setting_values[3]] + new_setting_values += [False, MODEL_NAMES['omnipose'][0], setting_values[4], MODEL_NAMES['v3'][0], MODEL_NAMES['v4'][0]] new_setting_values += [setting_values[5:], False, DENOISER_NAMES[0], False, "Preprocessed"] setting_values = new_setting_values variable_revision_number = 7 diff --git a/dockerfiles/RunCellpose/Dockerfile b/dockerfiles/RunCellpose_Cellposev2/Dockerfile similarity index 100% rename from dockerfiles/RunCellpose/Dockerfile rename to dockerfiles/RunCellpose_Cellposev2/Dockerfile diff --git a/dockerfiles/RunCellpose/download_cellpose_models.py b/dockerfiles/RunCellpose_Cellposev2/download_cellpose_models.py similarity index 100% rename from dockerfiles/RunCellpose/download_cellpose_models.py rename to dockerfiles/RunCellpose_Cellposev2/download_cellpose_models.py diff --git a/dockerfiles/RunCellpose_Cellposev3/Dockerfile b/dockerfiles/RunCellpose_Cellposev3/Dockerfile new file mode 100644 index 00000000..ad709cd4 --- /dev/null +++ b/dockerfiles/RunCellpose_Cellposev3/Dockerfile @@ -0,0 +1,7 @@ +FROM pytorch/pytorch:1.13.0-cuda11.6-cudnn8-runtime + +RUN pip install numpy==1.26.4 cellpose==3.1.1.2 + +# Include if you wish the image to contain Cellpose pretrained models +COPY download_cellpose_models.py / +RUN python /download_cellpose_models.py diff --git a/dockerfiles/RunCellpose_Cellposev3/download_cellpose_models.py b/dockerfiles/RunCellpose_Cellposev3/download_cellpose_models.py new file mode 100644 index 00000000..249f5c81 --- /dev/null +++ b/dockerfiles/RunCellpose_Cellposev3/download_cellpose_models.py @@ -0,0 +1,8 @@ +import cellpose +from cellpose.models import MODEL_NAMES + +for model in MODEL_NAMES: + for model_index in range(4): + model_name = cellpose.models.model_path(model, model_index) + if model in ("cyto", "nuclei", "cyto2"): + size_model_name = cellpose.models.size_model_path(model) \ No newline at end of file diff --git a/dockerfiles/RunCellpose_Cellposev4/Dockerfile b/dockerfiles/RunCellpose_Cellposev4/Dockerfile new file mode 100644 index 00000000..0115ab9e --- /dev/null +++ b/dockerfiles/RunCellpose_Cellposev4/Dockerfile @@ -0,0 +1,7 @@ +FROM pytorch/pytorch:1.13.0-cuda11.6-cudnn8-runtime + +RUN pip install numpy==1.26.4 cellpose==4.0.6 + +# Include if you wish the image to contain Cellpose pretrained models +COPY download_cellpose_models.py / +RUN python /download_cellpose_models.py diff --git a/dockerfiles/RunCellpose_Cellposev4/download_cellpose_models.py b/dockerfiles/RunCellpose_Cellposev4/download_cellpose_models.py new file mode 100644 index 00000000..b00bf136 --- /dev/null +++ b/dockerfiles/RunCellpose_Cellposev4/download_cellpose_models.py @@ -0,0 +1,6 @@ +import cellpose +from cellpose.models import MODEL_NAMES + +for model in MODEL_NAMES: + for model_index in range(4): + model_name = cellpose.models.model_path(model, model_index) \ No newline at end of file From 80b69caf305d2178ae4096d72219c9edd5a12c84 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:56:42 -0700 Subject: [PATCH 16/19] clarify diam --- active_plugins/runcellpose.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index dcd9fb0a..3cc8d692 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -563,7 +563,9 @@ def visible_settings(self): ] if self.cellpose_version.value == 'v4': vis_settings += [self.specify_diameter] - if self.specify_diameter.value: + if self.specify_diameter.value: + vis_settings += [self.expected_diameter] + else: vis_settings += [self.expected_diameter] if self.cellpose_version.value != 'omnipose': @@ -663,7 +665,10 @@ def run(self, workspace): if self.do_3D.value: anisotropy = x.spacing[0] / x.spacing[1] - if self.specify_diameter.value: + if self.cellpose_version.value == 'v4': + if self.specify_diameter.value: + diam = self.expected_diameter.value + else: diam = self.expected_diameter.value if self.expected_diameter.value > 0 else None if x.multichannel: @@ -932,7 +937,7 @@ def run(self, workspace): if self.denoise.value: cmd += ['--restore_type', self.denoise_type.value] if self.cellpose_version.value in ['omnipose','v2','v3']: - cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(self.expected_diameter)] + cmd += ['--chan', str(channels[0]), '--chan2', str(channels[1]), '--diameter', str(diam)] if self.cellpose_version.value in ['v4']: if self.specify_diameter.value: cmd += ['--diameter', str(diam)] From d4af760a3e3a4a31336ec343d9a6905324da4a89 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:56:10 -0700 Subject: [PATCH 17/19] add clear cache --- active_plugins/runcellpose.py | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 3cc8d692..561f9c6c 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -480,6 +480,17 @@ def set_directory_fn(path): doc="Enter the name you want to call the preprocessed image produced by this module.", ) + self.cache_model = Binary( + text="Cache model between image sets", + value=False, + doc=f"""\ + If True, the AI model will be maintained between image sets. If False, + memory will be released. Caching can improve performance by avoiding + the need to load the model for each image set. You may need to disable + this setting if running multiple AI models in your pipeline, + particularly if using a GPU with limited memory. You likely want this off while testing your pipeline. + """) + def settings(self): return [ self.x_name, @@ -519,6 +530,7 @@ def settings(self): self.denoise_type, self.denoise_image, self.denoise_name, + self.cache_model ] def visible_settings(self): @@ -605,6 +617,8 @@ def visible_settings(self): vis_settings += [self.denoise_type, self.denoise_image] if self.denoise_image.value: vis_settings += [self.denoise_name] + + vis_settings += [self.cache_model] return vis_settings @@ -731,6 +745,10 @@ def run(self, workspace): finally: if self.use_gpu.value and model.torch: cleanup(self) + if not self.cache_model.value: + # Release the model's memory + self.current_model = None + self.current_model_params = None if self.cellpose_version.value == 'v2': assert int(self.cellpose_ver[0])==2, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -764,6 +782,10 @@ def run(self, workspace): finally: if self.use_gpu.value and model.torch: cleanup(self) + if not self.cache_model.value: + # Release the model's memory + self.current_model = None + self.current_model_params = None elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -840,6 +862,10 @@ def run(self, workspace): finally: if self.use_gpu.value: cleanup(self) + if not self.cache_model.value: + # Release the model's memory + self.current_model = None + self.current_model_params = None elif self.cellpose_version.value == 'v4': assert int(self.cellpose_ver[0])==4, "Cellpose version selected in RunCellpose module doesn't match version in Python" @@ -878,6 +904,10 @@ def run(self, workspace): finally: if self.use_gpu.value and model.torch: cleanup(self) + if not self.cache_model.value: + # Release the model's memory + self.current_model = None + self.current_model_params = None else: try: y_data, flows, *_ = self.current_model.eval( @@ -896,6 +926,10 @@ def run(self, workspace): finally: if self.use_gpu.value: cleanup(self) + if not self.cache_model.value: + # Release the model's memory + self.current_model = None + self.current_model_params = None if self.remove_edge_masks: y_data = utils.remove_edge_masks(y_data) @@ -1153,7 +1187,7 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name new_setting_values = setting_values[0:2] new_setting_values += ['v3', CELLPOSE_DOCKERS['omnipose'][0], setting_values[2], CELLPOSE_DOCKERS['v3'][0], CELLPOSE_DOCKERS['v4'][0], setting_values[3]] new_setting_values += [False, MODEL_NAMES['omnipose'][0], setting_values[4], MODEL_NAMES['v3'][0], MODEL_NAMES['v4'][0]] - new_setting_values += [setting_values[5:], False, DENOISER_NAMES[0], False, "Preprocessed"] + new_setting_values += [setting_values[5:], False, DENOISER_NAMES[0], False, "Preprocessed", False] setting_values = new_setting_values variable_revision_number = 7 return setting_values, variable_revision_number From f332d29cd2f39dabd11df237aeb0d4a31b0623bd Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:51:37 -0700 Subject: [PATCH 18/19] fix custom model use v3 --- active_plugins/runcellpose.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 561f9c6c..3433391b 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -789,17 +789,19 @@ def run(self, workspace): elif self.cellpose_version.value == 'v3': assert int(self.cellpose_ver[0])==3, "Cellpose version selected in RunCellpose module doesn't match version in Python" + LOGGER.info(f"Loading new model: {self.mode.value}") if self.mode.value == 'custom': model_file, model_directory, model_path = get_custom_model_vars(self) - model_params = (self.mode.value, self.use_gpu.value) - LOGGER.info(f"Loading new model: {self.mode.value}") - if self.mode.value in SIZED_MODELS: - self.current_model = models.Cellpose( - model_type=self.mode.value, gpu=self.use_gpu.value) - else: self.current_model = models.CellposeModel( - model_type=self.mode.value, gpu=self.use_gpu.value) - self.current_model_params = model_params + pretrained_model=model_path, gpu=self.use_gpu.value) + else: + if self.mode.value in SIZED_MODELS: + self.current_model = models.Cellpose( + model_type=self.mode.value, gpu=self.use_gpu.value) + else: + self.current_model = models.CellposeModel( + model_type=self.mode.value, gpu=self.use_gpu.value) + self.current_model_params = (self.mode.value, self.use_gpu.value) if self.use_gpu.value: try: From 24116c7691d9513fd80c43ba6e5b625c89b24e59 Mon Sep 17 00:00:00 2001 From: ErinWeisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:14:57 -0700 Subject: [PATCH 19/19] cleanup --- active_plugins/runcellpose.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 3433391b..29d76c9d 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -81,15 +81,15 @@ """ "Select Cellpose Docker Image" -CELLPOSE_DOCKERS = {'omnipose': ["gnodar01/runcellpose_omnipose_no_pretrained:0.0.1"], # TODO make official +CELLPOSE_DOCKERS = {'omnipose': ["cellprofiler/runcellpose_omnipose_no_pretrained:1.0.2"], 'v2': ["cellprofiler/runcellpose_no_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.3.2", "cellprofiler/runcellpose_with_pretrained:2.2"], - 'v3': ["erinweisbart/cellpose:3.1.1.2"], # TODO make official - 'v4': ["erinweisbart/cellpose:4.0.5"]} # TODO make official + 'v3': ["cellprofiler/cellpose:3.1.1.2"], + 'v4': ["cellprofiler/cellpose:4.0.5"]} "Detection mode" -MODEL_NAMES = {'omnipose':['bact_phase_omni','bact_fluor_omni','cyto2_omni','worm_omni','plant_omni','bact_phase_affinity','cyto','nuclei','custom'], +MODEL_NAMES = {'omnipose':['cyto','nuclei','cyto2','custom'], 'v2':['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'], 'v3':[ "cyto3", "nuclei", "cyto2_cp3", "tissuenet_cp3", "livecell_cp3", "yeast_PhC_cp3", @@ -950,8 +950,9 @@ def run(self, workspace): temp_img_path = os.path.join(temp_img_dir, unique_name+".tiff") if self.mode.value == "custom": - model_file, model_directory, model_path = get_custom_model_vars(self) - model = models.CellposeModel(pretrained_model=model_path, gpu=self.use_gpu.value) + model_file = self.model_file_name.value + model_directory = self.model_directory.get_absolute_path() + model_path = os.path.join(model_directory, model_file) temp_model_dir = os.path.join(temp_dir, "model") os.makedirs(temp_model_dir, exist_ok=True)