From fd9f3cf19a550c69ddad7bc86579ded4f78c6556 Mon Sep 17 00:00:00 2001 From: Erin Weisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Fri, 16 Feb 2024 09:47:20 -0800 Subject: [PATCH 1/9] Create CITATION.cff --- CITATION.cff | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..c862e8a --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,48 @@ +cff-version: 1.2.0 +message: "If using CellProfiler Plugins in a pipeline used in a publication, please cite our CellProfiler-Plugins paper. Additionally, some plugins require citation themselves if used. You can determine which plugins require citation by reading the Help information for each plugin, accessible through CellProfiler, or by using the Citation generator built into CellProfiler (since CellProfiler 5)." +type: software +authors: + - name: 'Imaging Platform, Broad Institute of Harvard and MIT' + city: Cambridge + country: US +repository: https://github.com/CellProfiler/CellProfiler-plugins +title: "CellProfiler plugins" +doi: https://doi.org/10.1111/jmi.13223 +date-released: 2023 +preferred-citation: + type: article + authors: + - family-names: "Weisbart" + given-names: "Erin" + orcid: "https://orcid.org/0000-0002-6437-2458" + - family-names: "Tromans-Coia" + given-names: "Callum" + orcid: "https://orcid.org/0000-0002-5518-8915" + - family-names: "Diaz-Rohrer" + given-names: "Barbara" + orcid: "https://orcid.org/0000-0002-4748-7077" + - family-names: "Stirling" + given-names: "David R." + orcid: "https://orcid.org/0000-0001-6802-4103" + - family-names: "Garcia-Fossa" + given-names: "Fernanda" + orcid: "https://orcid.org/0000-0003-2308-0149" + - family-names: "Senft" + given-names: "Rebecca A." + orcid: "https://orcid.org/0000-0003-0081-4170" + - family-names: "Hiner" + given-names: "Mark C." + orcid: "https://orcid.org/0000-0001-9404-7579" + - family-names: "de Jesus" + given-names: "Marcelo B." + orcid: "https://orcid.org/0000-0003-0812-1491" + - family-names: "Eliceiri" + given-names: "Kevin W." + orcid: "https://orcid.org/0000-0001-8678-670X" + - family-names: "Cimini" + given-names: "Beth A." + orcid: "https://orcid.org/0000-0001-9640-9318" + doi: "https://doi.org/10.1111/jmi.13223" + journal: "Journal of Microscopy" + title: "CellProfiler plugins – An easy image analysis platform integration for containers and Python tools." + year: 2023 From b928c0bc980d953d74e1f4a1f39641495f6fdf57 Mon Sep 17 00:00:00 2001 From: Suganya Sivagurunathan Date: Thu, 29 Feb 2024 20:21:09 -0500 Subject: [PATCH 2/9] Cellpose version below 3 (#235) * Update setup.py * Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4508d1f..bb3e97c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ ] cellpose_deps = [ - "cellpose>=1.0.2" + "cellpose>=1.0.2,<3.0" ] omnipose_deps = [ From f5139bf4a0094ec4bac648bf35c10fa910652d42 Mon Sep 17 00:00:00 2001 From: Rebecca Senft Date: Mon, 25 Mar 2024 13:00:47 -0400 Subject: [PATCH 3/9] Update callbarcodes.py docs (#240) * Update callbarcodes.py Updated documentation with more details on channel naming and outputs. * delete duplicated text --------- Co-authored-by: Erin Weisbart <54687786+ErinWeisbart@users.noreply.github.com> --- active_plugins/callbarcodes.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/active_plugins/callbarcodes.py b/active_plugins/callbarcodes.py index 427eea4..9c63b2f 100644 --- a/active_plugins/callbarcodes.py +++ b/active_plugins/callbarcodes.py @@ -48,21 +48,25 @@ One column contains the corresponding gene/transcript names. All other columns in the .csv will be ignored. -Before running this module in your pipeline, you need to identify the objects in which you will be calling your barcodes and you will need to have measured the intensities of each object in four channels corresponding to nucleotides A,C,T, and G. +Before running this module in your pipeline, you need to identify the objects in which you will be calling your barcodes and you will need to have measured the intensities of each object in four channels corresponding to nucleotides A,C,T, and G. + +Your images for each nucleotide channel per cycle must be named in a particular way for this module to work as intended. For example, 'Cycle01_A_BackSub' is a valid name. Begin with 'Cycle' then the cycle number '01, 02...', then the nucleotide and any other information can follow. + If the background intensities of your four channels are not very well matched, you might want to run the **CompensateColors** module before measuring the object intensities. What do I get as output? ^^^^^^^^^^^^^^^^^^^^^^^^ -To be added +Image- and object-level measurements about barcode calling and score. Optionally, images of objects color-coded by barcode call or matching score. Measurements made by this module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Within the InputObject.csv, this module outputs the following measurements: -- BarcodeCalled is the n-cycle string of the barcode sequence that was read by the module -- MatchedTo_Barcode is the known barcode that the module best matched to the called barcode -- MatchedTo_ID is an ID number assigned to each known barcode -- MatchedTo_GeneCode is the known gene/transcript name that corresponds to the known barcode -- MatchedTo_Score is the quality of the called barcode to known barcode match, reported as (matching nucleotides)/(total nucleotides) where 1 is a perfect match + +- **BarcodeCalled** is the n-cycle string of the barcode sequence that was read by the module +- **MatchedTo_Barcode** is the known barcode that the module best matched to the called barcode +- **MatchedTo_ID** is an ID number assigned to each known barcode +- **MatchedTo_GeneCode** is the known gene/transcript name that corresponds to the known barcode +- **MatchedTo_Score** is the quality of the called barcode to known barcode match, reported as (matching nucleotides)/(total nucleotides) where 1 is a perfect match Note that CellProfiler cannot create a per-parent mean measurement of a string. @@ -137,22 +141,22 @@ def set_directory_fn(path): self.input_object_name.get_value, "AreaShape_Area", doc="""\ -This measurement should be """, +This measurement should be an intensity measure that is measured for every cycle and is of a category that distinguishes different nucleotides. If named correctly (see main module input help), this module will gather the appropriate measurement for all nucleotides. For example, choosing the maximum intensity in the channel for nucleotide A will also use max intensity for C, T, G.""", ) self.metadata_field_barcode = cellprofiler_core.setting.choice.Choice( - "Select the column of barcodes to match against", + "Select the column containing barcodes to match against", ["No CSV file"], choices_fn=self.get_choices, - doc="""\Select the column of barcodes to match against. + doc="""\Select the column containing barcodes to match against. """, ) self.metadata_field_tag = cellprofiler_core.setting.choice.Choice( - "Select the column with gene/transcript barcode names", + "Select the column containing gene/transcript barcode names", ["No CSV file"], choices_fn=self.get_choices, - doc="""\Select the column with gene/transcript barcode names. + doc="""\Select the column containing gene/transcript barcode names. """, ) From 11b46ee7f6eb78f97f784a731c5a1931b66c90d4 Mon Sep 17 00:00:00 2001 From: Beth Cimini Date: Sun, 7 Apr 2024 09:46:18 -0400 Subject: [PATCH 4/9] Runilastik module that can use local OR Docker installations (#226) * Create Runilastik_test.py * Update Runilastik_test.py * testversion * TestVersion * rename * UpdatedVersion * working Mac version * Remove addition of extra shape dimension in grayscale * Warning message and documentation * Update supported_plugins.md * More Documentation * Update runilastik.py * Included a dictionary * Added more settings in the Runilastik plugin * Added some documentation in the Runilastik.py file * Added more docker containers * Updated Runilastik plugin * Moved Predict.py to unmaintained plugins * Updated the documentation and the code (else statements) * Updated documentation --------- Co-authored-by: sugan89 --- active_plugins/predict.py | 177 ---------- active_plugins/runilastik.py | 314 ++++++++++++++++++ .../supported_plugins.md | 2 +- .../unsupported_plugins.md | 1 + .../CP-plugins-documentation/using_plugins.md | 4 +- .../CellProfiler4_autoconverted/predict.py | 124 +++---- 6 files changed, 369 insertions(+), 253 deletions(-) delete mode 100755 active_plugins/predict.py create mode 100644 active_plugins/runilastik.py diff --git a/active_plugins/predict.py b/active_plugins/predict.py deleted file mode 100755 index 0329733..0000000 --- a/active_plugins/predict.py +++ /dev/null @@ -1,177 +0,0 @@ -################################# -# -# Imports from useful Python libraries -# -################################# - -import os -import subprocess -import tempfile -import h5py # HDF5 is ilastik's preferred file format -import logging -import skimage - -################################# -# -# Imports from CellProfiler -# -################################## - -from cellprofiler_core.image import Image -from cellprofiler_core.module import Module -import cellprofiler_core.setting -from cellprofiler_core.setting.choice import Choice -from cellprofiler_core.setting.text import Pathname - -__doc__ = """\ -Predict -======= - -**Predict** uses an ilastik pixel classifier to generate a probability image. Each -channel represents the probability of the pixels in the image belong to -a particular class. Use **ColorToGray** to separate channels for further -processing. For example, use **IdentifyPrimaryObjects** on a -(single-channel) probability map to generate a segmentation. The order -of the channels in **ColorToGray** is the same as the order of the -labels within the ilastik project. - -Additionally, please ensure CellProfiler is configured to load images in -the same format as ilastik. For example, if your ilastik classifier is -trained on RGB images, use **NamesAndTypes** to load images as RGB by -selecting "*Color image*" from the *Select the image type* dropdown. If -your classifier expects grayscale images, use **NamesAndTypes** to load -images as "*Grayscale image*". - -| - -============ ============ =============== -Supports 2D? Supports 3D? Respects masks? -============ ============ =============== -YES NO NO -============ ============ =============== -""" - -logger = logging.getLogger(__name__) - - -class Predict(cellprofiler_core.module.ImageProcessing): - module_name = "Predict" - - variable_revision_number = 2 - - def create_settings(self): - super(Predict, self).create_settings() - - self.executable = Pathname( - "Executable", - doc="ilastik command line executable name, or location if it is not on your path.", - ) - - self.project_file = Pathname( - "Project file", doc="Path to the project file (\*.ilp)." - ) - - self.project_type = Choice( - "Select the project type", - ["Pixel Classification", "Autocontext (2-stage)"], - "Pixel Classification", - doc="""\ -Select the project type which matches the project file specified by -*Project file*. CellProfiler supports two types of ilastik projects: - -- *Pixel Classification*: Classify the pixels of an image given user - annotations. `Read more`_. - -- *Autocontext (2-stage)*: Perform pixel classification in multiple - stages, sharing predictions between stages to improve results. `Read - more `__. - -.. _Read more: http://ilastik.org/documentation/pixelclassification/pixelclassification -""", - ) - - def settings(self): - settings = super(Predict, self).settings() - - settings += [self.executable, self.project_file, self.project_type] - - return settings - - def visible_settings(self): - visible_settings = super(Predict, self).visible_settings() - - visible_settings += [self.executable, self.project_file, self.project_type] - - return visible_settings - - def run(self, workspace): - image = workspace.image_set.get_image(self.x_name.value) - - x_data = image.pixel_data - x_data = x_data*image.scale - - fin = tempfile.NamedTemporaryFile(suffix=".h5", delete=False) - - fout = tempfile.NamedTemporaryFile(suffix=".h5", delete=False) - - if self.executable.value[-4:] == ".app": - executable = os.path.join(self.executable.value, "Contents/MacOS/ilastik") - else: - executable = self.executable.value - - cmd = [ - executable, - "--headless", - "--project", - self.project_file.value, - "--output_format", - "hdf5", - ] - - if self.project_type.value in ["Pixel Classification"]: - cmd += ["--export_source", "Probabilities"] - elif self.project_type.value in ["Autocontext (2-stage)"]: - - cmd += ["--export_source", "probabilities stage 2"] - # cmd += ["--export_source", "probabilities all stages"] - - cmd += ["--output_filename_format", fout.name, fin.name] - - try: - with h5py.File(fin.name, "w") as f: - shape = x_data.shape - - f.create_dataset("data", shape, data=x_data) - - fin.close() - - fout.close() - - subprocess.check_call(cmd) - - with h5py.File(fout.name, "r") as f: - y_data = f["exported_data"][()] - - y = Image(y_data) - - workspace.image_set.add(self.y_name.value, y) - - if self.show_window: - workspace.display_data.x_data = x_data - - workspace.display_data.y_data = y_data - - workspace.display_data.dimensions = image.dimensions - except subprocess.CalledProcessError as cpe: - logger.error( - "Command {} exited with status {}".format(cpe.output, cpe.returncode), - cpe, - ) - - raise cpe - except IOError as ioe: - raise ioe - finally: - os.unlink(fin.name) - - os.unlink(fout.name) diff --git a/active_plugins/runilastik.py b/active_plugins/runilastik.py new file mode 100644 index 0000000..8f5499d --- /dev/null +++ b/active_plugins/runilastik.py @@ -0,0 +1,314 @@ +################################# +# +# Imports from useful Python libraries +# +################################# + +import os +import subprocess +import shutil +import uuid +import logging +import sys +import h5py +import tempfile + + +################################# +# +# Imports from CellProfiler +# +################################## + +from cellprofiler_core.image import Image +from cellprofiler_core.module import ImageProcessing +from cellprofiler_core.setting.choice import Choice +from cellprofiler_core.preferences import get_default_output_directory +from cellprofiler_core.setting import ValidationError +from cellprofiler_core.setting.text import ( + Directory, + Filename, + Pathname, + Text, +) + +ilastik_link = "https://doi.org/10.1038/s41592-019-0582-9" +LOGGER = logging.getLogger(__name__) + + +__doc__ = """\ +Runilastik +======= + +Use an ilastik pixel classifier to generate a probability image. Each +channel represents the probability of the pixels in the image belong to +a particular class. Use **ColorToGray** to separate channels for further +processing. For example, use **IdentifyPrimaryObjects** on a +(single-channel) probability map to generate a segmentation. The order +of the channels in **ColorToGray** is the same as the order of the +labels within the ilastik project. + +Additionally, please ensure CellProfiler is configured to load images in +the same format as ilastik. For example, if your ilastik classifier is +trained on RGB images, use **NamesAndTypes** to load images as RGB by +selecting "*Color image*" from the *Select the image type* dropdown. If +your classifier expects grayscale images, use **NamesAndTypes** to load +images as "*Grayscale image*". + +Runilastik module will not run analysis mode on local installation of ilastik on a Windows system. Please use Docker instead. + +A note to the mac users - this module takes a longer time to run using the Docker. + +Links to the Docker containers, + biocontainers/ilastik:1.4.0_cv2 - https://hub.docker.com/layers/biocontainers/ilastik/1.4.0_cv2/images/sha256-0ccbca62d9efc63918d9de3b9b2bb5b1265a084f8b6410fd8c34e62869549791?context=explore + ilastik/ilastik-from-binary:1.4.0b13 - https://hub.docker.com/layers/ilastik/ilastik-from-binary/1.4.0b13/images/sha256-e3a4044a5ac6f2086f4bf006c8a95e2bd6a6fbfb68831bb4ba47baf2fafba988?context=explore +""" + +#ILASTIK_DOCKER is a dictionary where the keys are the names of the different docker containers and the values are the commands that are needed to run the respective docker container. +ILASTIK_DOCKER = {"biocontainers/ilastik:1.4.0_cv2":'/opt/ilastik-1.4.0-Linux/run_ilastik.sh','ilastik/ilastik-from-binary:1.4.0b13':'./run_ilastik.sh', "select your own":''} +#Docker container that did not work - {'ilastik/ilastik-from-source:0.0.1a1':'. ~/.bashrc && python ilastik.py'} + + +class Runilastik(ImageProcessing): + module_name = "Runilastik" + + variable_revision_number = 1 # the number of variations made to this module + + doi = { + "Please cite the following when using Runilastik:": "https://doi.org/10.1038/s41592-019-0582-9", # doi ias added such that it is easier for citations + } + + def create_settings(self): + super(Runilastik, self).create_settings() + + self.docker_or_local = Choice( + text="Run ilastik in docker or local environment", + choices=["Docker", "Local"], + value="Docker", + doc="""\ +If Docker is selected, ensure that Docker Desktop is open and running on your +computer. On first run of the Runilastik plugin, the Docker container will be +downloaded. However, this slow downloading process will only have to happen +once. + +If Local is selected, the local install of ilastik will be used. +""", + ) + + + self.docker_choice = Choice( + text="Choose the docker", + choices = list(ILASTIK_DOCKER.keys()), + doc=""" +Choose the docker that you would like to use for running ilastik +""" + ) + + self.custom_docker_name = Text( + "Enter the docker name ", + value="", + doc=""" +Please give your docker name +""" + ) + + self.docker_executable = Text( + "Enter the executable command to run the docker", + value="", + doc=""" +Please provide the executable command that is needed to run the docker command. You can find this in the github page of the docker. +""" + ) + + self.executable = Pathname( + "Executable", + doc="ilastik command line executable name, or location if it is not on your path." + ) + + self.project_file = Pathname( + "Project file", + doc="Path to the project file (\*.ilp)." + ) + + self.project_type = Choice( + "Select the project type", + [ + "Pixel Classification", + "Autocontext (2-stage)" + ], + "Pixel Classification", + doc="""\ +Select the project type which matches the project file specified by +*Project file*. CellProfiler supports two types of ilastik projects: + +- *Pixel Classification*: Classify the pixels of an image given user + annotations. `Read more`_. + +- *Autocontext (2-stage)*: Perform pixel classification in multiple + stages, sharing predictions between stages to improve results. `Read + more `__. + +.. _Read more: http://ilastik.org/documentation/pixelclassification/pixelclassification +""" + ) + + def settings(self): + return [ + self.x_name, + self.y_name, + self.docker_or_local, + self.executable, + self.project_file, + self.project_type, + ] + # A function to define what settings should be displayed if an user chooses specific setting + def visible_settings(self): + + vis_settings = [self.docker_or_local] + if self.docker_or_local.value == "Docker": + vis_settings += [self.docker_choice] + + if self.docker_choice == "select your own": + vis_settings += [self.custom_docker_name, self.docker_executable] + else: + vis_settings += [self.executable] + + vis_settings += [self.x_name, self.y_name, self.project_file, self.project_type] + + return vis_settings + + # Give a warning if the user chooses "analysis mode" + def validate_module_warnings(self, docker_or_local): + """Warn user re: Analysis mode""" + if self.docker_or_local.value == "Docker": + if not sys.platform.lower().startswith("win"): + raise ValidationError( + "Analysis mode will take a long time to run using Docker", + self.docker_or_local, + ) + else: + if self.executable.value[-4:] == ".exe": + raise ValidationError( + "Sorry, analysis will not run on Windows with the local installation of the ilastik. Please try Docker instead.", + self.docker_or_local, + ) + + def run(self, workspace): + image = workspace.image_set.get_image(self.x_name.value) + + x_data = image.pixel_data + x_data = x_data*image.scale #rescale + + # preparing the data + # Create a UUID for this run + unique_name = str(uuid.uuid4()) + + # Directory that will be used to pass images to the docker container + temp_dir = os.path.join(get_default_output_directory(), ".cellprofiler_temp", unique_name) + + os.makedirs(temp_dir, exist_ok=True) + + #The input image files are converted into h5 format and saved in the temporary directory + fin = tempfile.NamedTemporaryFile(suffix=".h5", dir=temp_dir, delete=False) + + fout = tempfile.NamedTemporaryFile(suffix=".h5", dir=temp_dir, delete=False) + + + with h5py.File(fin.name, "w") as f: + shape = x_data.shape + # Previously, code lived here that added an explicit channel dimension in grayscale + # It now seems to harm rather than help, but may need to be resurrected in some corner case not thoroughly tested + + f.create_dataset("data", shape, data=x_data) + + fin.close() + + fout.close() + + if self.docker_or_local.value == "Docker": + # Define how to call docker + docker_path = "docker" if sys.platform.lower().startswith("win") else "/usr/local/bin/docker" + # The project file is stored in a directory which can be pointed to the docker + model_file = self.project_file.value + model_directory = os.path.dirname(os.path.abspath(model_file)) + + fout_name = f"/data/{os.path.basename(fout.name)}" + fin_name = f"/data/{os.path.basename(fin.name)}" + + + if self.docker_choice.value == "select your own": + ILASTIK_DOCKER_choice = self.custom_docker_name.value + ILASTIK_command = self.docker_executable.value + + else: + ILASTIK_DOCKER_choice = self.docker_choice.value + ILASTIK_command = ILASTIK_DOCKER[ILASTIK_DOCKER_choice] + + cmd = [f"{docker_path}", "run", "--rm", "-v", f"{temp_dir}:/data", + "-v", f"{model_directory}:/model", + f"{ILASTIK_DOCKER_choice}", f"{ILASTIK_command}", "--headless", + "--project", f"/model/{os.path.basename(model_file)}" + ] + + if self.docker_or_local.value == "Local": + + if self.executable.value[-4:] == ".app": + executable = os.path.join(self.executable.value, "Contents/MacOS/ilastik") + else: + executable = self.executable.value + + fout_name = fout.name + fin_name = fin.name + + cmd = [ + executable, + "--headless", + "--project", self.project_file.value] + + cmd += ["--output_format", "hdf5"] + + + if self.project_type.value in ["Pixel Classification"]: + cmd += ["--export_source", "Probabilities"] + elif self.project_type.value in ["Autocontext (2-stage)"]: + cmd += ["--export_source", "probabilities stage 2"] + #cmd += ["--export_source", "probabilities all stages"] + + cmd += ["--output_filename_format", fout_name, fin_name] + + try: + subprocess.check_call(cmd) + + + with h5py.File(fout.name, "r") as f: + y_data = f["exported_data"][()] + + y = Image(y_data) + + workspace.image_set.add(self.y_name.value, y) + + if self.show_window: + workspace.display_data.x_data = x_data + + workspace.display_data.y_data = y_data + + workspace.display_data.dimensions = image.dimensions + except subprocess.CalledProcessError as cpe: + LOGGER.error("Command {} exited with status {}".format(cpe.output, cpe.returncode), cpe) + + raise cpe + except IOError as ioe: + raise ioe + finally: + os.unlink(fin.name) + + os.unlink(fout.name) + + # Delete the temporary files + try: + shutil.rmtree(temp_dir) + except: + LOGGER.error("Unable to delete temporary directory, files may be in use by another program.") + LOGGER.error("Temp folder is subfolder {tempdir} in your Default Output Folder.\nYou may need to remove it manually.") + + diff --git a/documentation/CP-plugins-documentation/supported_plugins.md b/documentation/CP-plugins-documentation/supported_plugins.md index f013663..ed80c60 100644 --- a/documentation/CP-plugins-documentation/supported_plugins.md +++ b/documentation/CP-plugins-documentation/supported_plugins.md @@ -20,8 +20,8 @@ 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 | -| Predict | Predict allows you to use an ilastik pixel classifier to generate a probability image. CellProfiler supports two types of ilastik projects: Pixel Classification and Autocontext (2-stage). | 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 | +| 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 | | 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 | diff --git a/documentation/CP-plugins-documentation/unsupported_plugins.md b/documentation/CP-plugins-documentation/unsupported_plugins.md index 5e277b6..3198774 100644 --- a/documentation/CP-plugins-documentation/unsupported_plugins.md +++ b/documentation/CP-plugins-documentation/unsupported_plugins.md @@ -21,3 +21,4 @@ Information about select plugins is as follows: **ClassifyPixelsUNET**: ClassifyPixelsUNET is a pixel classifier for background/object edge/object body. As far as we are aware, other deep learning based plugins that we do currently support (such as RunCellpose) work better. **DeclumpObjects**: DeclumpObjects will split objects based on a seeded watershed method. Functionality from this module was [added into CellProfiler](https://github.com/CellProfiler/CellProfiler/pull/4397) in the Watershed module as of CellProfiler 4.2.0. +**Predict**: Predict module is not supported anymore and one can use **Runilastik** module to run ilastik pixel classifier in Cellprofiler. diff --git a/documentation/CP-plugins-documentation/using_plugins.md b/documentation/CP-plugins-documentation/using_plugins.md index d823ec5..8b3902b 100644 --- a/documentation/CP-plugins-documentation/using_plugins.md +++ b/documentation/CP-plugins-documentation/using_plugins.md @@ -21,7 +21,7 @@ See [Installing plugins with dependencies, using CellProfiler from source](#inst See [Installing plugins with dependencies, using pre-built CellProfiler](#installing-plugins-with-dependencies-using-pre-built-cellprofiler). - The third option uses Docker to bypass installation requirements. It is the simplest option that only requires download of Docker Desktop; the module that has dependencies will automatically download a Docker that has all of the dependencies upon run and access that Docker while running the plugin. -It is currently only supported for the RunCellpose plugin but will be available in other plugins soon. +It is currently supported for the RunCellpose and Runilastik plugins. Please have a look at this [table](https://github.com/CellProfiler/CellProfiler-plugins/blob/master/documentation/CP-plugins-documentation/supported_plugins.md) to know about the availability of docker versions for plugins. See [Using Docker to Bypass Installation Requirements](#using-docker-to-bypass-installation-requirements). ### Installing plugins without dependencies @@ -173,7 +173,7 @@ Download Docker Desktop from [Docker.com](https://www.docker.com/products/docker 2. **Run Docker Desktop** Open Docker Desktop. -Docker Desktop will need to be open every time you use a plugin with Docker. +Docker Desktop will need to be open every time you use a plugin with Docker. Please have a look at this [table](https://github.com/CellProfiler/CellProfiler-plugins/blob/master/documentation/CP-plugins-documentation/supported_plugins.md) to know if a docker version is available for a plugin. 3. **Select "Run with Docker"** In your plugin, select `Docker` for "Run module in docker or local python environment" setting. diff --git a/unmaintained_plugins/CellProfiler4_autoconverted/predict.py b/unmaintained_plugins/CellProfiler4_autoconverted/predict.py index 084dccc..0329733 100644 --- a/unmaintained_plugins/CellProfiler4_autoconverted/predict.py +++ b/unmaintained_plugins/CellProfiler4_autoconverted/predict.py @@ -1,24 +1,33 @@ +################################# +# +# Imports from useful Python libraries +# +################################# + import os import subprocess import tempfile - import h5py # HDF5 is ilastik's preferred file format import logging import skimage -import cellprofiler_core.image -import cellprofiler_core.module +################################# +# +# Imports from CellProfiler +# +################################## + +from cellprofiler_core.image import Image +from cellprofiler_core.module import Module import cellprofiler_core.setting from cellprofiler_core.setting.choice import Choice from cellprofiler_core.setting.text import Pathname -logger = logging.getLogger(__name__) - __doc__ = """\ Predict ======= -Use an ilastik pixel classifier to generate a probability image. Each +**Predict** uses an ilastik pixel classifier to generate a probability image. Each channel represents the probability of the pixels in the image belong to a particular class. Use **ColorToGray** to separate channels for further processing. For example, use **IdentifyPrimaryObjects** on a @@ -26,70 +35,45 @@ of the channels in **ColorToGray** is the same as the order of the labels within the ilastik project. -CellProfiler automatically scales grayscale and color images to the -[0.0, 1.0] range on load. Your ilastik classifier should be trained on -images with the same scale as the prediction images. You can ensure -consistent scales by: - -- using **ImageMath** to convert the images loaded by CellProfiler back - to their original scale. Use these settings to rescale an image: - - - **Operation**: *None* - - **Multiply the first image by**: *RESCALE_VALUE* - - **Set values greater than 1 equal to 1?**: *No* - - where *RESCALE_VALUE* is determined by your image data and the value - of *Set intensity range from* in **NamesAndTypes**. For example, the - *RESCALE_VALUE* for 32-bit images rescaled by "*Image bit-depth*" is - 65535 (the maximum value allowed by this data type). Please refer to - the help for the setting *Set intensity range from* in - **NamesAndTypes** for more information. - - This option is best when your training and prediction images do not - require any preprocessing by CellProfiler. - -- preprocessing any training images with CellProfiler (e.g., - **RescaleIntensity**) and applying the same pre-processing steps to - your analysis pipeline. You can use **SaveImages** to export training - images as 32-bit TIFFs. - - This option requires two CellProfiler pipelines, but is effective - when your training and prediction images require preprocessing by - CellProfiler. - Additionally, please ensure CellProfiler is configured to load images in the same format as ilastik. For example, if your ilastik classifier is trained on RGB images, use **NamesAndTypes** to load images as RGB by selecting "*Color image*" from the *Select the image type* dropdown. If your classifier expects grayscale images, use **NamesAndTypes** to load images as "*Grayscale image*". + +| + +============ ============ =============== +Supports 2D? Supports 3D? Respects masks? +============ ============ =============== +YES NO NO +============ ============ =============== """ +logger = logging.getLogger(__name__) + class Predict(cellprofiler_core.module.ImageProcessing): module_name = "Predict" - variable_revision_number = 1 + variable_revision_number = 2 def create_settings(self): super(Predict, self).create_settings() self.executable = Pathname( "Executable", - doc="ilastik command line executable name, or location if it is not on your path." + doc="ilastik command line executable name, or location if it is not on your path.", ) self.project_file = Pathname( - "Project file", - doc="Path to the project file (\*.ilp)." + "Project file", doc="Path to the project file (\*.ilp)." ) self.project_type = Choice( "Select the project type", - [ - "Pixel Classification", - "Autocontext (2-stage)" - ], + ["Pixel Classification", "Autocontext (2-stage)"], "Pixel Classification", doc="""\ Select the project type which matches the project file specified by @@ -103,28 +87,20 @@ def create_settings(self): more `__. .. _Read more: http://ilastik.org/documentation/pixelclassification/pixelclassification -""" +""", ) def settings(self): settings = super(Predict, self).settings() - settings += [ - self.executable, - self.project_file, - self.project_type - ] + settings += [self.executable, self.project_file, self.project_type] return settings def visible_settings(self): visible_settings = super(Predict, self).visible_settings() - visible_settings += [ - self.executable, - self.project_file, - self.project_type - ] + visible_settings += [self.executable, self.project_file, self.project_type] return visible_settings @@ -132,40 +108,39 @@ def run(self, workspace): image = workspace.image_set.get_image(self.x_name.value) x_data = image.pixel_data + x_data = x_data*image.scale fin = tempfile.NamedTemporaryFile(suffix=".h5", delete=False) fout = tempfile.NamedTemporaryFile(suffix=".h5", delete=False) + if self.executable.value[-4:] == ".app": + executable = os.path.join(self.executable.value, "Contents/MacOS/ilastik") + else: + executable = self.executable.value + cmd = [ - self.executable.value, + executable, "--headless", - "--project", self.project_file.value, - "--output_format", "hdf5" + "--project", + self.project_file.value, + "--output_format", + "hdf5", ] if self.project_type.value in ["Pixel Classification"]: cmd += ["--export_source", "Probabilities"] elif self.project_type.value in ["Autocontext (2-stage)"]: - x_data = skimage.img_as_ubyte(x_data) # ilastik requires UINT8. Might be relaxed in future. cmd += ["--export_source", "probabilities stage 2"] - #cmd += ["--export_source", "probabilities all stages"] + # cmd += ["--export_source", "probabilities all stages"] - cmd += [ - "--output_filename_format", fout.name, - fin.name - ] + cmd += ["--output_filename_format", fout.name, fin.name] try: with h5py.File(fin.name, "w") as f: shape = x_data.shape - if x_data.ndim == 2: - # ilastik appears to add a channel dimension - # even if the image is grayscale - shape += (1,) - f.create_dataset("data", shape, data=x_data) fin.close() @@ -175,9 +150,9 @@ def run(self, workspace): subprocess.check_call(cmd) with h5py.File(fout.name, "r") as f: - y_data = f["exported_data"].value + y_data = f["exported_data"][()] - y = cellprofiler_core.image.Image(y_data) + y = Image(y_data) workspace.image_set.add(self.y_name.value, y) @@ -188,7 +163,10 @@ def run(self, workspace): workspace.display_data.dimensions = image.dimensions except subprocess.CalledProcessError as cpe: - logger.error("Command {} exited with status {}".format(cpe.output, cpe.returncode), cpe) + logger.error( + "Command {} exited with status {}".format(cpe.output, cpe.returncode), + cpe, + ) raise cpe except IOError as ioe: From f0e4db193c1fc82cc5bd81844cc2ce307e307442 Mon Sep 17 00:00:00 2001 From: Shatavisha Dasgupta <139376717+ShataDg@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:46:26 -0400 Subject: [PATCH 5/9] Add normalization settings to RunCellpose * updated runcellpose.py * update runcellpose --- active_plugins/runcellpose.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 91afae7..2fd99cd 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -83,9 +83,11 @@ """ +"Select Cellpose Docker Image" CELLPOSE_DOCKER_NO_PRETRAINED = "cellprofiler/runcellpose_no_pretrained:0.1" CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED = "cellprofiler/runcellpose_with_pretrained:0.1" +"Detection mode" MODEL_NAMES = ['cyto','nuclei','tissuenet','livecell', 'cyto2', 'general', 'CP', 'CPx', 'TN1', 'TN2', 'TN3', 'LC1', 'LC2', 'LC3', 'LC4', 'custom'] @@ -95,7 +97,7 @@ class RunCellpose(ImageSegmentation): module_name = "RunCellpose" - variable_revision_number = 4 + variable_revision_number = 5 doi = { "Please cite the following when using RunCellPose:": "https://doi.org/10.1038/s41592-020-01018-x", @@ -105,6 +107,16 @@ class RunCellpose(ImageSegmentation): def create_settings(self): super(RunCellpose, self).create_settings() + self.rescale = Binary( + text="Rescale images before running Cellpose", + value=True, + doc="""\ +Reminds the user that the normalization step will be performed to ensure suimilar segmentation behaviour inthe RunCellpose +modul and the Cellpose app. +""" + ) + + self.docker_or_python = Choice( text="Run CellPose in docker or local python environment", choices=["Docker", "Python"], @@ -340,6 +352,7 @@ def set_directory_fn(path): def settings(self): return [ self.x_name, + self.rescale, self.docker_or_python, self.docker_image, self.expected_diameter, @@ -365,7 +378,8 @@ def settings(self): ] def visible_settings(self): - vis_settings = [self.docker_or_python] + vis_settings = [self.rescale, self.docker_or_python] + if self.docker_or_python.value == "Docker": vis_settings += [self.docker_image] @@ -440,6 +454,13 @@ def run(self, workspace): x = images.get_image(x_name) dimensions = x.dimensions x_data = x.pixel_data + + if self.rescale.value: + rescale_x = x_data.copy() + x01 = numpy.percentile(rescale_x, 1) + x99 = numpy.percentile(rescale_x, 99) + x_data = numpy.clip((rescale_x - x01) / (x99 - x01), a_min=0, a_max=1) + anisotropy = 0.0 if self.do_3D.value: anisotropy = x.spacing[0] / x.spacing[1] @@ -699,4 +720,9 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name if variable_revision_number == 3: setting_values = [setting_values[0]] + ["Python",CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED] + setting_values[1:] variable_revision_number = 4 + if variable_revision_number == 4: + setting_values = [setting_values[0]] + ['No'] + [setting_values[1:]] + variable_revision_number = 5 return setting_values, variable_revision_number + + From 662bb00410aaa27359df0dddb7b16bc3a532fa8b Mon Sep 17 00:00:00 2001 From: Erin Weisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:31:45 -0700 Subject: [PATCH 6/9] make addnoise plugin (#246) * make addnoise plugin * add AddNoise to docs --- active_plugins/addnoise.py | 291 ++++++++++++++++++ .../supported_plugins.md | 6 +- 2 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 active_plugins/addnoise.py diff --git a/active_plugins/addnoise.py b/active_plugins/addnoise.py new file mode 100644 index 0000000..6b795cc --- /dev/null +++ b/active_plugins/addnoise.py @@ -0,0 +1,291 @@ +""" +AddNoise +======================== + +**AddNoise** adds noise to an image. + +| + +============ ============ =============== +Supports 2D? Supports 3D? Respects masks? +============ ============ =============== +YES YES NO +============ ============ =============== + +""" + +import numpy +from cellprofiler_core.image import Image +from cellprofiler_core.module import Module +from cellprofiler_core.setting import Divider, Binary +from cellprofiler_core.setting import SettingsGroup +from cellprofiler_core.setting.choice import Choice +from cellprofiler_core.setting.do_something import DoSomething +from cellprofiler_core.setting.do_something import RemoveSettingButton +from cellprofiler_core.setting.subscriber import ImageSubscriber +from cellprofiler_core.setting.text import ImageName +from cellprofiler_core.setting.text import Float + +SETTINGS_PER_IMAGE = 2 + +A_GAUSSIAN = "Gaussian" +A_POISSON = "Poisson" +A_SANDP = "Salt and Pepper" + +G_MU = "mu" +G_SIGMA = "sigma" + +I_PERCENT = "Percent image with noise" + +class AddNoise(Module): + category = "Image Processing" + variable_revision_number = 1 + module_name = "AddNoise" + + def create_settings(self): + """Make settings here (and set the module name)""" + self.images = [] + self.add_image(can_delete=False) + self.add_image_button = DoSomething("", "Add another image", self.add_image) + self.truncate_low = Binary( + "Set output image values less than 0 equal to 0?", + True, + doc="""\ +Values outside the range 0 to 1 might not be handled well by other +modules. Select *"Yes"* to set negative values to 0, which was previously +done automatically without ability to override. +""" ) + + self.truncate_high = Binary( + "Set output image values greater than 1 equal to 1?", + True, + doc="""\ +Values outside the range 0 to 1 might not be handled well by other +modules. Select *"Yes"* to set values greater than 1 to a maximum +value of 1. +""") + self.method = Choice( + "Select the operation", + [A_GAUSSIAN, A_POISSON, A_SANDP], + doc="""\ +Select what kind of noise you want to add. + +- *{A_GAUSSIAN}:* Gaussian noise has a normally distributed probability density function. +It is independent of the original intensities in the image. +{G_MU} is the mean and {G_SIGMA} is the standard deviation. +- *{A_POISSON}:* Poisson noise is correlated with the intensity of each pixel. Also called Shot Noise. +- *{A_SANDP}:* Salt and Pepper is a type of impulse noise where there is a sparse occurance of maximum and minimum pixel values in an image. +You can set the {I_PERCENT}. +""".format( + **{"A_GAUSSIAN": A_GAUSSIAN, "A_POISSON": A_POISSON, "A_SANDP": A_SANDP, + "G_MU": G_MU, "G_SIGMA": G_SIGMA, "I_PERCENT": I_PERCENT}, + ), + ) + + self.mu = Float( + "mu (mean)", + value = 0, + doc="""\ +*(Used only if “{A_GAUSSIAN}” is selected)* +Enter the mean of the Gaussian noise +""".format( + **{ + "A_GAUSSIAN": A_GAUSSIAN + } + ), + ) + + self.sigma = Float( + "sigma (standard deviation)", + value = .1, + doc="""\ +*(Used only if “{A_GAUSSIAN}” is selected)* +Enter the standard deviation of the Gaussian noise +""".format( + **{ + "A_GAUSSIAN": A_GAUSSIAN + } + ), + ) + + self.percent = Float( + "percent of image to salt and pepper", + value = .1, + doc="""\ +*(Used only if “{A_SANDP}” is selected)* +Enter the percentage of the image to salt and pepper +""".format( + **{ + "A_SANDP": A_SANDP + } + ), + ) + + + def add_image(self, can_delete=True): + """Add an image and its settings to the list of images""" + image_name = ImageSubscriber( + "Select the input image", "None", doc="Select the image to add noise to." + ) + + noised_image_name = ImageName( + "Name the output image", + "NoisedBlue", + doc="Enter a name for the noisy image.", + ) + + image_settings = SettingsGroup() + image_settings.append("image_name", image_name) + image_settings.append("noised_image_name", noised_image_name) + + if can_delete: + image_settings.append( + "remover", + RemoveSettingButton( + "", "Remove this image", self.images, image_settings + ), + ) + image_settings.append("divider", Divider()) + self.images.append(image_settings) + + def settings(self): + """Return the settings to be loaded or saved to/from the pipeline + + These are the settings (from cellprofiler_core.settings) that are + either read from the strings in the pipeline or written out + to the pipeline. The settings should appear in a consistent + order so they can be matched to the strings in the pipeline. + """ + result = [self.method,self.mu,self.sigma] + for image in self.images: + result += [ + image.image_name, + image.noised_image_name, + ] + result += [ + self.truncate_low, + self.truncate_high, + ] + return result + + def visible_settings(self): + """Return the list of displayed settings + """ + result = [self.method] + for image in self.images: + result += [ + image.image_name, + image.noised_image_name, + ] + # + # Get the "remover" button if there is one + # + remover = getattr(image, "remover", None) + if remover is not None: + result.append(remover) + result.append(image.divider) + result.append(self.add_image_button) + result.append(self.truncate_low) + result.append(self.truncate_high) + if self.method == A_GAUSSIAN: + result.append(self.mu) + result.append(self.sigma) + if self.method == A_SANDP: + result.append(self.percent) + return result + + def run(self, workspace): + """Run the module + + workspace - The workspace contains + pipeline - instance of cpp for this run + image_set - the images in the image set being processed + object_set - the objects (labeled masks) in this image set + measurements - the measurements for this run + frame - the parent frame to whatever frame is created. None means don't draw. + """ + for image in self.images: + self.run_image(image, workspace) + + def run_image(self, image, workspace): + # + # Get the image names from the settings + # + image_name = image.image_name.value + noised_image_name = image.noised_image_name.value + # + # Get images from the image set + # + orig_image = workspace.image_set.get_image(image_name, must_be_grayscale=True) + + if self.method.value == A_GAUSSIAN: + output_pixels = self.add_gaussian(orig_image, self.mu.value, self.sigma.value) + if self.method.value == A_POISSON: + output_pixels = self.add_poisson(orig_image) + if self.method.value == A_SANDP: + output_pixels = self.add_impulse(orig_image, self.percent.value) + # + # Optionally, clip high and low values + # + if self.truncate_low.value: + output_pixels = numpy.where(output_pixels < 0, 0, output_pixels) + if self.truncate_high.value: + output_pixels = numpy.where(output_pixels > 1, 1, output_pixels) + + y = Image(dimensions=orig_image.dimensions, image=output_pixels, parent_image=orig_image, convert=False) + workspace.image_set.add(noised_image_name, y) + # + # Save images for display + # + if self.show_window: + if not hasattr(workspace.display_data, "images"): + workspace.display_data.images = {} + workspace.display_data.images[image_name] = orig_image.pixel_data + workspace.display_data.images[noised_image_name] = output_pixels + + def add_gaussian(self, orig_image, mu, sigma): + noise_mask = numpy.random.normal(mu, sigma, orig_image.pixel_data.shape) + noisy_pixels = orig_image.pixel_data + noise_mask + return noisy_pixels + def add_poisson(self, orig_image): + noise_mask = numpy.random.poisson(orig_image.pixel_data) + noisy_pixels = orig_image.pixel_data + noise_mask + return noisy_pixels + def add_impulse(self, orig_image, percent): + random_indices = numpy.random.choice(orig_image.pixel_data.size, round(orig_image.pixel_data.size*percent)) + noise = numpy.random.choice([orig_image.pixel_data.min(), orig_image.pixel_data.max()], round(orig_image.pixel_data.size*percent)) + noisy_pixels = orig_image.pixel_data.copy() + noisy_pixels.flat[random_indices] = noise + return noisy_pixels + + def display(self, workspace, figure): + """ Display one row of orig / noised per image setting group""" + figure.set_subplots((2, len(self.images))) + for j, image in enumerate(self.images): + image_name = image.image_name.value + noised_image_name = image.noised_image_name.value + orig_image = workspace.display_data.images[image_name] + noised_image = workspace.display_data.images[noised_image_name] + + def imshow(x, y, image, *args, **kwargs): + if image.ndim == 2: + f = figure.subplot_imshow_grayscale + else: + f = figure.subplot_imshow_color + return f(x, y, image, *args, **kwargs) + + imshow( + 0, + j, + orig_image, + "Original image: %s" % image_name, + sharexy=figure.subplot(0, 0), + ) + imshow( + 1, + j, + noised_image, + "Final image: %s" % noised_image_name, + sharexy=figure.subplot(0, 0), + ) + diff --git a/documentation/CP-plugins-documentation/supported_plugins.md b/documentation/CP-plugins-documentation/supported_plugins.md index ed80c60..5d47a4b 100644 --- a/documentation/CP-plugins-documentation/supported_plugins.md +++ b/documentation/CP-plugins-documentation/supported_plugins.md @@ -3,15 +3,15 @@ Below is a brief overview of our currently supported plugins. For details about using any particular plugin, please read the module documentation inside the plugin in CellProfiler. -Most plugins will run without any special installation of either CellProfiler or the plugins. +Most plugins will run without any special installation of either CellProfiler or the plugins. See [using plugins](using_plugins.md) for how to set up CellProfiler for plugin use as well as for installation information for those plugins that do require installation of dependencies. Most plugin documentation can be found within the plugin itself and can be accessed through CellProfiler help. Those plugins that do have extra documentation contain links below. - | Plugin | Description | Requires installation of dependencies? | Install flag | Docker version currently available? | |--------|-------------|----------------------------------------|--------------|-------------------------------------| +| AddNoise | AddNoise adds Gaussian, Poisson, or Salt and Pepper noise to images. Of particular use for data augmentation in deep learning. | No | | N/A | | 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 | @@ -21,7 +21,7 @@ Those plugins that do have extra documentation contain links below. | 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 | -| 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 | +| 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 | | 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 | From 23fabd84ca68116036b72d3bc39fed3f6469e198 Mon Sep 17 00:00:00 2001 From: Beth Cimini Date: Wed, 1 May 2024 12:15:05 -0400 Subject: [PATCH 7/9] Update runcellpose.py --- active_plugins/runcellpose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index 2fd99cd..c7423e4 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -721,7 +721,7 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name setting_values = [setting_values[0]] + ["Python",CELLPOSE_DOCKER_IMAGE_WITH_PRETRAINED] + setting_values[1:] variable_revision_number = 4 if variable_revision_number == 4: - setting_values = [setting_values[0]] + ['No'] + [setting_values[1:]] + setting_values = [setting_values[0]] + ['No'] + setting_values[1:] variable_revision_number = 5 return setting_values, variable_revision_number From b68e0d00bf64936d2d2c1d17948ca4c25247e95a Mon Sep 17 00:00:00 2001 From: emiglietta <58238709+emiglietta@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:29:23 -0400 Subject: [PATCH 8/9] Enable getting probability map when running from Docker (#250) * Get probabilities in RunCellpose from docker include a conditional statement to get the right flow for probabilities when running RunCellpose from docker image * add probability map rescaling option when running from Python --- active_plugins/runcellpose.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/active_plugins/runcellpose.py b/active_plugins/runcellpose.py index c7423e4..9493cb8 100644 --- a/active_plugins/runcellpose.py +++ b/active_plugins/runcellpose.py @@ -97,7 +97,7 @@ class RunCellpose(ImageSegmentation): module_name = "RunCellpose" - variable_revision_number = 5 + variable_revision_number = 6 doi = { "Please cite the following when using RunCellPose:": "https://doi.org/10.1038/s41592-020-01018-x", @@ -111,8 +111,8 @@ def create_settings(self): text="Rescale images before running Cellpose", value=True, doc="""\ -Reminds the user that the normalization step will be performed to ensure suimilar segmentation behaviour inthe RunCellpose -modul and the Cellpose app. +Reminds the user that the normalization step will be performed to ensure suimilar segmentation behaviour in the RunCellpose +module and the Cellpose app. """ ) @@ -346,6 +346,14 @@ def set_directory_fn(path): doc=""" If you do not want to include any object masks that are not in full view in the image, you can have the masks that have pixels touching the the edges removed. The default is set to "Yes". +""", + ) + + self.probability_rescale_setting = Binary( + text="Rescale probability map?", + value=True, + doc=""" +Activate to rescale probability map to 0-255 (which matches the scale used when running this module from Docker) """, ) @@ -375,6 +383,7 @@ def settings(self): self.omni, self.invert, self.remove_edge_masks, + self.probability_rescale_setting, ] def visible_settings(self): @@ -416,6 +425,8 @@ def visible_settings(self): 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] @@ -630,9 +641,18 @@ def run(self, workspace): objects.add_objects(y, y_name) if self.save_probabilities.value: + if self.docker_or_python.value == "Docker": + # get rid of extra dimension + prob_map = numpy.squeeze(flows[1], axis=0) # ranges 0-255 + else: + prob_map = flows[2] + rescale_prob_map = prob_map.copy() + prob_map01 = numpy.percentile(rescale_prob_map, 1) + prob_map99 = numpy.percentile(rescale_prob_map, 99) + prob_map = numpy.clip((rescale_prob_map - prob_map01) / (prob_map99 - prob_map01), a_min=0, a_max=1) # Flows come out sized relative to CellPose's inbuilt model size. # We need to slightly resize to match the original image. - size_corrected = skimage.transform.resize(flows[2], y_data.shape) + size_corrected = skimage.transform.resize(prob_map, y_data.shape) prob_image = Image( size_corrected, parent_image=x.parent_image, @@ -723,6 +743,9 @@ def upgrade_settings(self, setting_values, variable_revision_number, module_name if variable_revision_number == 4: setting_values = [setting_values[0]] + ['No'] + setting_values[1:] variable_revision_number = 5 + if variable_revision_number == 5: + setting_values = setting_values + [False] + variable_revision_number = 6 return setting_values, variable_revision_number From 1f847d9890ae0ee5a7f37c9b62930850cb984816 Mon Sep 17 00:00:00 2001 From: Erin Weisbart <54687786+ErinWeisbart@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:22:54 -0700 Subject: [PATCH 9/9] add FilterObjects_StringMatch (#254) Uses FilterObjects as template for filtering objects that match a given string. --- active_plugins/filterobjects_stringmatch.py | 179 ++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 active_plugins/filterobjects_stringmatch.py diff --git a/active_plugins/filterobjects_stringmatch.py b/active_plugins/filterobjects_stringmatch.py new file mode 100644 index 0000000..8714e77 --- /dev/null +++ b/active_plugins/filterobjects_stringmatch.py @@ -0,0 +1,179 @@ +from cellprofiler_core.module.image_segmentation import ObjectProcessing +from cellprofiler_core.setting import ( + Divider, +) +from cellprofiler_core.setting.text import Alphanumeric + +__doc__ = "" + +import logging +import os + +import numpy +import scipy +import scipy.ndimage +import scipy.sparse + +import cellprofiler_core.object +from cellprofiler.utilities.rules import Rules + +LOGGER = logging.getLogger(__name__) + + +class FilterObjects_StringMatch(ObjectProcessing): + module_name = "FilterObjects_StringMatch" + + variable_revision_number = 1 + + def __init__(self): + self.rules = Rules() + + super(FilterObjects_StringMatch, self).__init__() + + def create_settings(self): + super(FilterObjects_StringMatch, self).create_settings() + + self.x_name.text = """Select the objects to filter""" + + self.x_name.doc = "" + + self.y_name.text = """Name the output objects""" + + self.y_name.doc = "Enter a name for the collection of objects that are retained after applying the filter(s)." + + self.spacer_1 = Divider(line=False) + + self.filter_out = Alphanumeric( + "What string to filter out", + "AAAA", + doc="""Enter a name for the measurement calculated by this module.""", + ) + + self.rules.create_settings() + + def settings(self): + settings = super(FilterObjects_StringMatch, self).settings() + settings += [self.filter_out] + return settings + + def visible_settings(self): + visible_settings = super(FilterObjects_StringMatch, self).visible_settings() + visible_settings += [ + self.filter_out + ] + return visible_settings + + def run(self, workspace): + """Filter objects for this image set, display results""" + src_objects = workspace.get_objects(self.x_name.value) + + indexes = self.keep_by_string(workspace, src_objects) + + # + # Create an array that maps label indexes to their new values + # All labels to be deleted have a value in this array of zero + # + new_object_count = len(indexes) + max_label = numpy.max(src_objects.segmented) + label_indexes = numpy.zeros((max_label + 1,), int) + label_indexes[indexes] = numpy.arange(1, new_object_count + 1) + # + # Loop over both the primary and additional objects + # + object_list = [(self.x_name.value, self.y_name.value)] + m = workspace.measurements + first_set = True + for src_name, target_name in object_list: + src_objects = workspace.get_objects(src_name) + target_labels = src_objects.segmented.copy() + # + # Reindex the labels of the old source image + # + target_labels[target_labels > max_label] = 0 + target_labels = label_indexes[target_labels] + # + # Make a new set of objects - retain the old set's unedited + # segmentation for the new and generally try to copy stuff + # from the old to the new. + # + target_objects = cellprofiler_core.object.Objects() + target_objects.segmented = target_labels + target_objects.unedited_segmented = src_objects.unedited_segmented + # + # Remove the filtered objects from the small_removed_segmented + # if present. "small_removed_segmented" should really be + # "filtered_removed_segmented". + # + small_removed = src_objects.small_removed_segmented.copy() + small_removed[(target_labels == 0) & (src_objects.segmented != 0)] = 0 + target_objects.small_removed_segmented = small_removed + if src_objects.has_parent_image: + target_objects.parent_image = src_objects.parent_image + workspace.object_set.add_objects(target_objects, target_name) + + self.add_measurements(workspace, src_name, target_name) + if self.show_window and first_set: + workspace.display_data.src_objects_segmented = src_objects.segmented + workspace.display_data.target_objects_segmented = target_objects.segmented + workspace.display_data.dimensions = src_objects.dimensions + first_set = False + + def display(self, workspace, figure): + """Display what was filtered""" + src_name = self.x_name.value + src_objects_segmented = workspace.display_data.src_objects_segmented + target_objects_segmented = workspace.display_data.target_objects_segmented + dimensions = workspace.display_data.dimensions + + target_name = self.y_name.value + + figure.set_subplots((2, 2), dimensions=dimensions) + + figure.subplot_imshow_labels( + 0, 0, src_objects_segmented, title="Original: %s" % src_name + ) + + figure.subplot_imshow_labels( + 1, + 0, + target_objects_segmented, + title="Filtered: %s" % target_name, + sharexy=figure.subplot(0, 0), + ) + + pre = numpy.max(src_objects_segmented) + post = numpy.max(target_objects_segmented) + + statistics = [[pre], [post], [pre - post]] + + figure.subplot_table( + 0, + 1, + statistics, + row_labels=( + "Number of objects pre-filtering", + "Number of objects post-filtering", + "Number of objects removed", + ), + ) + + def keep_by_string(self, workspace, src_objects): + """ + workspace - workspace passed into Run + src_objects - the Objects instance to be filtered + """ + src_name = self.x_name.value + m = workspace.measurements + values = m.get_current_measurement(src_name, "Barcode_BarcodeCalled") + # Is this structure still necessary or is it an artifact? + # Could be just values == self.filter_out.value + # Make an array of True + hits = numpy.ones(len(values), bool) + # Fill with False for those where we want to filter out + hits[values == self.filter_out.value] = False + # Get object numbers for things that are True + indexes = numpy.argwhere(hits)[:, 0] + # Objects are 1 counted, Python is 0 counted + indexes = indexes + 1 + + return indexes