All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mianalysis.mia.module.images.process.ColourDeconvolution Maven / Gradle / Ivy

Go to download

ModularImageAnalysis (MIA) is an ImageJ plugin which provides a modular framework for assembling image and object analysis workflows. Detected objects can be transformed, filtered, measured and related. Analysis workflows are batch-enabled by default, allowing easy processing of high-content datasets.

There is a newer version: 1.6.12
Show newest version
package io.github.mianalysis.mia.module.images.process;

import java.util.LinkedHashMap;

import org.scijava.Priority;
import org.scijava.plugin.Plugin;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.plugin.RGBStackConverter;
import ij.plugin.SubHyperstackMaker;
import ij.process.ImageProcessor;
import io.github.mianalysis.mia.module.Categories;
import io.github.mianalysis.mia.module.Category;
import io.github.mianalysis.mia.module.Module;
import io.github.mianalysis.mia.module.Modules;
import io.github.mianalysis.mia.object.Workspace;
import io.github.mianalysis.mia.object.image.Image;
import io.github.mianalysis.mia.object.image.ImageFactory;
import io.github.mianalysis.mia.object.parameters.BooleanP;
import io.github.mianalysis.mia.object.parameters.ChoiceP;
import io.github.mianalysis.mia.object.parameters.InputImageP;
import io.github.mianalysis.mia.object.parameters.OutputImageP;
import io.github.mianalysis.mia.object.parameters.Parameters;
import io.github.mianalysis.mia.object.parameters.SeparatorP;
import io.github.mianalysis.mia.object.parameters.text.DoubleP;
import io.github.mianalysis.mia.object.refs.collections.ImageMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.MetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ParentChildRefs;
import io.github.mianalysis.mia.object.refs.collections.PartnerRefs;
import io.github.mianalysis.mia.object.system.Status;
import sc.fiji.colourDeconvolution.StainMatrix;


/**
* Applies the Colour Deconvolution plugin to unmix an RGB image (stored in the workspace) into up to three separate channels, which are output as separate images.  The input image can be stored as either an RGB or composite image.  This process is only applicable to images created via subtractive mixing (e.g. histological staining), not to additive methods (e.g. fluorescence).
*/
@Plugin(type = Module.class, priority=Priority.LOW, visible=true)
public class ColourDeconvolution extends Module {

	/**
	* 
	*/
    public static final String INPUT_SEPARATOR = "Image input";

	/**
	* Image from the workspace to apply unmixing to.  This can be stored as either an RGB or composite image.
	*/
    public static final String INPUT_IMAGE = "Input image";


	/**
	* 
	*/
    public static final String OUTPUT_SEPARATOR = "Image output";

	/**
	* When selected, the first stain in the stain matrix will be output to the workspace with the name specified by "Output image 1 name"
	*/
    public static final String ENABLE_IM1_OUTPUT = "Output image 1";

	/**
	* Name to assign to first stain image, if output to the workspace.
	*/
    public static final String OUTPUT_IMAGE_1 = "Output image 1 name";

	/**
	* When selected, the second stain in the stain matrix will be output to the workspace with the name specified by "Output image 2 name"
	*/
    public static final String ENABLE_IM2_OUTPUT = "Output image 2";

	/**
	* Name to assign to second stain image, if output to the workspace.
	*/
    public static final String OUTPUT_IMAGE_2 = "Output image 2 name";

	/**
	* When selected, the third stain in the stain matrix will be output to the workspace with the name specified by "Output image 3 name"
	*/
    public static final String ENABLE_IM3_OUTPUT = "Output image 3";

	/**
	* Name to assign to third stain image, if output to the workspace.
	*/
    public static final String OUTPUT_IMAGE_3 = "Output image 3 name";


	/**
	* 
	*/
    public static final String DECONVOLUTION_SEPARATOR = "Deconvolution controls";

	/**
	* Stain models to apply to input image.  If set to "Custom (user values)" the individual RGB components for each channel can be specified.  Model choices are: Alcian blue and H,Azan-Mallory,Brilliant_Blue,CMY,FastRed FastBlue DAB,Feulgen Light Green,Giemsa,H AEC,HandE,HandE 2,HandE DAB,H DAB,H PAS,Masson Trichrome,Methyl Green DAB,RGB,Custom (user values)
	*/
    public static final String STAIN_MODEL = "Stain model";
    public static final String R1 = "Stain 1 (red)";
    public static final String G1 = "Stain 1 (green)";
    public static final String B1 = "Stain 1 (blue)";
    public static final String R2 = "Stain 2 (red)";
    public static final String G2 = "Stain 2 (green)";
    public static final String B2 = "Stain 2 (blue)";
    public static final String R3 = "Stain 3 (red)";
    public static final String G3 = "Stain 3 (green)";
    public static final String B3 = "Stain 3 (blue)";

    public ColourDeconvolution(Modules modules) {
        super("Colour deconvolution", modules);
    }

    public interface StainModels {
        String ALCIAN_BLUE_H = "Alcian blue & H";
        String AZAN_MALLORY = "Azan-Mallory";
        String BRILLIANT_BLUE = "Brilliant_Blue";
        String CMY = "CMY";
        String FLG = "Feulgen Light Green";
        String FR_FB_DAB = "FastRed FastBlue DAB";
        String GIEMSA = "Giemsa";
        String H_AEC = "H AEC";
        String H_AND_E = "H&E";
        String H_AND_E_2 = "H&E 2";
        String H_AND_E_DAB = "H&E DAB";
        String H_DAB = "H DAB";
        String H_PAS = "H PAS";
        String MASSON_TRICHROME = "Masson Trichrome";
        String METHYL_GREEN_DAB = "Methyl Green DAB";
        String RGB = "RGB";
        String CUSTOM = "Custom (user values)";

        String[] ALL = new String[] { ALCIAN_BLUE_H, AZAN_MALLORY, BRILLIANT_BLUE, CMY, FR_FB_DAB, FLG, GIEMSA, H_AEC,
                H_AND_E, H_AND_E_2, H_AND_E_DAB, H_DAB, H_PAS, MASSON_TRICHROME, METHYL_GREEN_DAB, RGB, CUSTOM };

    }

    public static StainMatrix getCustomStainMatrix(double r1, double g1, double b1, double r2, double g2, double b2,
            double r3, double g3, double b3) {
        StainMatrix sm = new StainMatrix();
        sm.setMODx(new double[] { r1, r2, r3 });
        sm.setMODy(new double[] { g1, g2, g3 });
        sm.setMODz(new double[] { b1, b2, b3 });

        return sm;

    }

    public static ImagePlus[] process(ImagePlus ipl, String[] outputImageNames, StainMatrix stainMatrix) {
        String moduleName = new ColourDeconvolution(null).getName();

        int width = ipl.getWidth();
        int height = ipl.getHeight();
        int nChannels = ipl.getNChannels();
        int nSlices = ipl.getNSlices();
        int nFrames = ipl.getNFrames();

        // Creating the output image
        ImagePlus deconvolved1 = IJ.createHyperStack(outputImageNames[0], width, height, 1, nSlices, nFrames, 8);
        deconvolved1.setCalibration(ipl.getCalibration());

        ImagePlus deconvolved2 = IJ.createHyperStack(outputImageNames[1], width, height, 1, nSlices, nFrames, 8);
        deconvolved2.setCalibration(ipl.getCalibration());

        ImagePlus deconvolved3 = IJ.createHyperStack(outputImageNames[2], width, height, 1, nSlices, nFrames, 8);
        deconvolved3.setCalibration(ipl.getCalibration());

        // Iterating over all timepoints
        int count = 0;
        for (int t = 1; t <= nFrames; t++) {
            ImagePlus iplSingle = SubHyperstackMaker.makeSubhyperstack(ipl, "1-" + nChannels, "1-" + nSlices,
                    t + "-" + t);

            // If not already an RGB image, convert to one
            if (iplSingle.getBitDepth() != 24)
                RGBStackConverter.convertToRGB(iplSingle);

            ImageStack[] iplOut = stainMatrix.compute(false, true, iplSingle);

            for (int z = 1; z <= nSlices; z++) {
                deconvolved1.setPosition(1, z, t);
                deconvolved2.setPosition(1, z, t);
                deconvolved3.setPosition(1, z, t);

                ImageProcessor iprDeconvolved1 = deconvolved1.getProcessor();
                ImageProcessor iprDeconvolved2 = deconvolved2.getProcessor();
                ImageProcessor iprDeconvolved3 = deconvolved3.getProcessor();

                ImageProcessor iprOut1 = iplOut[0].getProcessor(z);
                ImageProcessor iprOut2 = iplOut[1].getProcessor(z);
                ImageProcessor iprOut3 = iplOut[2].getProcessor(z);

                for (int x = 0; x < width; x++) {
                    for (int y = 0; y < height; y++) {
                        iprDeconvolved1.setf(x, y, iprOut1.getf(x, y));
                        iprDeconvolved2.setf(x, y, iprOut2.getf(x, y));
                        iprDeconvolved3.setf(x, y, iprOut3.getf(x, y));
                    }
                }
            }

            writeProgressStatus(++count, nFrames, "stacks", moduleName);

        }

        deconvolved1.setPosition(1, 1, 1);
        deconvolved2.setPosition(1, 1, 1);
        deconvolved3.setPosition(1, 1, 1);

        deconvolved1.updateChannelAndDraw();
        deconvolved2.updateChannelAndDraw();
        deconvolved3.updateChannelAndDraw();

        return new ImagePlus[] { deconvolved1, deconvolved2, deconvolved3 };

    }

    @Override
    public Category getCategory() {
        return Categories.IMAGES_PROCESS;
    }

    @Override
    public String getVersionNumber() {
        return "1.0.0";
    }

    @Override
    public String getDescription() {
        return "Applies the Colour Deconvolution plugin to unmix an RGB image (stored in the workspace) into up to three separate channels, which are output as separate images.  The input image can be stored as either an RGB or composite image.  This process is only applicable to images created via subtractive mixing (e.g. histological staining), not to additive methods (e.g. fluorescence).";
    }

    @Override
    public Status process(Workspace workspace) {
        // Getting input image
        String inputImageName = parameters.getValue(INPUT_IMAGE,workspace);
        Image inputImage = workspace.getImages().get(inputImageName);
        ImagePlus inputImagePlus = inputImage.getImagePlus();

        // Getting parameters
        boolean outputImage1 = parameters.getValue(ENABLE_IM1_OUTPUT,workspace);
        String outputImageName1 = parameters.getValue(OUTPUT_IMAGE_1,workspace);
        boolean outputImage2 = parameters.getValue(ENABLE_IM2_OUTPUT,workspace);
        String outputImageName2 = parameters.getValue(OUTPUT_IMAGE_2,workspace);
        boolean outputImage3 = parameters.getValue(ENABLE_IM3_OUTPUT,workspace);
        String outputImageName3 = parameters.getValue(OUTPUT_IMAGE_3,workspace);
        String stainModel = parameters.getValue(STAIN_MODEL,workspace);
        double r1 = parameters.getValue(R1,workspace);
        double g1 = parameters.getValue(G1,workspace);
        double b1 = parameters.getValue(B1,workspace);
        double r2 = parameters.getValue(R2,workspace);
        double g2 = parameters.getValue(G2,workspace);
        double b2 = parameters.getValue(B2,workspace);
        double r3 = parameters.getValue(R3,workspace);
        double g3 = parameters.getValue(G3,workspace);
        double b3 = parameters.getValue(B3,workspace);

        // Running the deconvolution
        StainMatrix stainMatrix;
        if (stainModel.equals(StainModels.CUSTOM))
            stainMatrix = getCustomStainMatrix(r1, g1, b1, r2, g2, b2, r3, g3, b3);
        else
            stainMatrix = getStainMatrices().get(stainModel);

        String[] outputImageNames = new String[] { outputImageName1, outputImageName2, outputImageName3 };
        ImagePlus[] outputImagePluses = process(inputImagePlus, outputImageNames, stainMatrix);

        // If selected, displaying the image
        if (outputImage1) {
            Image outImage1 = ImageFactory.createImage(outputImageName1, outputImagePluses[0]);
            workspace.addImage(outImage1);
            if (showOutput)
                outImage1.show();
        }

        if (outputImage2) {
            Image outImage2 = ImageFactory.createImage(outputImageName2, outputImagePluses[1]);
            workspace.addImage(outImage2);
            if (showOutput)
                outImage2.show();
        }

        if (outputImage3) {
            Image outImage3 = ImageFactory.createImage(outputImageName3, outputImagePluses[2]);
            workspace.addImage(outImage3);
            if (showOutput)
                outImage3.show();
        }

        return Status.PASS;

    }

    @Override
    protected void initialiseParameters() {
        parameters.add(new SeparatorP(INPUT_SEPARATOR, this));
        parameters.add(new InputImageP(INPUT_IMAGE, this));

        parameters.add(new SeparatorP(OUTPUT_SEPARATOR, this));
        parameters.add(new BooleanP(ENABLE_IM1_OUTPUT, this, true));
        parameters.add(new OutputImageP(OUTPUT_IMAGE_1, this));
        parameters.add(new BooleanP(ENABLE_IM2_OUTPUT, this, true));
        parameters.add(new OutputImageP(OUTPUT_IMAGE_2, this));
        parameters.add(new BooleanP(ENABLE_IM3_OUTPUT, this, true));
        parameters.add(new OutputImageP(OUTPUT_IMAGE_3, this));

        parameters.add(new SeparatorP(DECONVOLUTION_SEPARATOR, this));
        parameters.add(new ChoiceP(STAIN_MODEL, this, StainModels.H_AND_E, StainModels.ALL));
        parameters.add(new DoubleP(R1, this, 0d));
        parameters.add(new DoubleP(G1, this, 0d));
        parameters.add(new DoubleP(B1, this, 0d));
        parameters.add(new DoubleP(R2, this, 0d));
        parameters.add(new DoubleP(G2, this, 0d));
        parameters.add(new DoubleP(B2, this, 0d));
        parameters.add(new DoubleP(R3, this, 0d));
        parameters.add(new DoubleP(G3, this, 0d));
        parameters.add(new DoubleP(B3, this, 0d));

        addParameterDescriptions();

    }

    @Override
    public Parameters updateAndGetParameters() {
Workspace workspace = null;
        Parameters returnedParameters = new Parameters();

        returnedParameters.add(parameters.get(INPUT_SEPARATOR));
        returnedParameters.add(parameters.getParameter(INPUT_IMAGE));

        returnedParameters.add(parameters.get(OUTPUT_SEPARATOR));
        returnedParameters.add(parameters.getParameter(ENABLE_IM1_OUTPUT));
        if ((boolean) parameters.getValue(ENABLE_IM1_OUTPUT,workspace))
            returnedParameters.add(parameters.getParameter(OUTPUT_IMAGE_1));

        returnedParameters.add(parameters.getParameter(ENABLE_IM2_OUTPUT));
        if ((boolean) parameters.getValue(ENABLE_IM2_OUTPUT,workspace))
            returnedParameters.add(parameters.getParameter(OUTPUT_IMAGE_2));

        returnedParameters.add(parameters.getParameter(ENABLE_IM3_OUTPUT));
        if ((boolean) parameters.getValue(ENABLE_IM3_OUTPUT,workspace))
            returnedParameters.add(parameters.getParameter(OUTPUT_IMAGE_3));

        returnedParameters.add(parameters.get(DECONVOLUTION_SEPARATOR));
        returnedParameters.add(parameters.getParameter(STAIN_MODEL));

        switch ((String) parameters.getValue(STAIN_MODEL,workspace)) {
            case StainModels.CUSTOM:
                returnedParameters.add(parameters.getParameter(R1));
                returnedParameters.add(parameters.getParameter(G1));
                returnedParameters.add(parameters.getParameter(B1));
                returnedParameters.add(parameters.getParameter(R2));
                returnedParameters.add(parameters.getParameter(G2));
                returnedParameters.add(parameters.getParameter(B2));
                returnedParameters.add(parameters.getParameter(R3));
                returnedParameters.add(parameters.getParameter(G3));
                returnedParameters.add(parameters.getParameter(B3));

                break;
        }

        return returnedParameters;

    }

    @Override
    public ImageMeasurementRefs updateAndGetImageMeasurementRefs() {
return null;
    }

    @Override
public ObjMeasurementRefs updateAndGetObjectMeasurementRefs() {
return null;
    }

    @Override
    public ObjMetadataRefs updateAndGetObjectMetadataRefs() {  
	return null; 
    }

    @Override
    public MetadataRefs updateAndGetMetadataReferences() {
return null;
    }

    @Override
    public ParentChildRefs updateAndGetParentChildRefs() {
return null;
    }

    @Override
    public PartnerRefs updateAndGetPartnerRefs() {
return null;
    }

    void addParameterDescriptions() {
        parameters.get(INPUT_IMAGE).setDescription(
                "Image from the workspace to apply unmixing to.  This can be stored as either an RGB or composite image.");

        parameters.get(ENABLE_IM1_OUTPUT).setDescription(
                "When selected, the first stain in the stain matrix will be output to the workspace with the name specified by \""
                        + OUTPUT_IMAGE_1 + "\"");

        parameters.get(OUTPUT_IMAGE_1)
                .setDescription("Name to assign to first stain image, if output to the workspace.");

        parameters.get(ENABLE_IM2_OUTPUT).setDescription(
                "When selected, the second stain in the stain matrix will be output to the workspace with the name specified by \""
                        + OUTPUT_IMAGE_2 + "\"");

        parameters.get(OUTPUT_IMAGE_2)
                .setDescription("Name to assign to second stain image, if output to the workspace.");

        parameters.get(ENABLE_IM3_OUTPUT).setDescription(
                "When selected, the third stain in the stain matrix will be output to the workspace with the name specified by \""
                        + OUTPUT_IMAGE_3 + "\"");

        parameters.get(OUTPUT_IMAGE_3)
                .setDescription("Name to assign to third stain image, if output to the workspace.");

        parameters.get(STAIN_MODEL)
                .setDescription("Stain models to apply to input image.  If set to \"" + StainModels.CUSTOM
                        + "\" the individual RGB components for each channel can be specified.  Model choices are: "
                        + String.join(",", StainModels.ALL));

        parameters.get(R1).setDescription("Red component of stain 1.  Value specified in range 0-1.");

        parameters.get(G1).setDescription("Green component of stain 1.  Value specified in range 0-1.");

        parameters.get(B1).setDescription("Blue component of stain 1.  Value specified in range 0-1.");

        parameters.get(R2).setDescription("Red component of stain 2.  Value specified in range 0-1.");

        parameters.get(G2).setDescription("Green component of stain 2.  Value specified in range 0-1.");

        parameters.get(B2).setDescription("Blue component of stain 2.  Value specified in range 0-1.");

        parameters.get(R3).setDescription("Red component of stain 3.  Value specified in range 0-1.");

        parameters.get(G3).setDescription("Green component of stain 3.  Value specified in range 0-1.");

        parameters.get(B3).setDescription("Blue component of stain 3.  Value specified in range 0-1.");

    }

    public static LinkedHashMap getStainMatrices() {
        LinkedHashMap matrices = new LinkedHashMap<>();

        StainMatrix sm = new StainMatrix();
        sm.setMODx(new double[] { 0.644211, 0.092789, 0 });
        sm.setMODy(new double[] { 0.716556, 0.954111, 0 });
        sm.setMODz(new double[] { 0.266844, 0.283111, 0 });
        matrices.put(StainModels.H_AND_E, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.49015734, 0.04615336, 0 });
        sm.setMODy(new double[] { 0.76897085, 0.8420684, 0 });
        sm.setMODz(new double[] { 0.41040173, 0.5373925, 0 });
        matrices.put(StainModels.H_AND_E_2, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.65, 0.268, 0 });
        sm.setMODy(new double[] { 0.704, 0.57, 0 });
        sm.setMODz(new double[] { 0.286, 0.776, 0 });
        matrices.put(StainModels.H_DAB, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.4642092, 0.94705542, 0 });
        sm.setMODy(new double[] { 0.83008335, 0.25373821, 0 });
        sm.setMODz(new double[] { 0.30827187, 0.19650764, 0 });
        matrices.put(StainModels.FLG, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.834750233, 0.092789, 0 });
        sm.setMODy(new double[] { 0.513556283, 0.954111, 0 });
        sm.setMODz(new double[] { 0.196330403, 0.283111, 0 });
        matrices.put(StainModels.GIEMSA, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.21393921, 0.74890292, 0.268 });
        sm.setMODy(new double[] { 0.85112669, 0.60624161, 0.57 });
        sm.setMODz(new double[] { 0.47794022, 0.26731082, 0.776 });
        matrices.put(StainModels.FR_FB_DAB, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.98, 0.268, 0 });
        sm.setMODy(new double[] { 0.144316, 0.57, 0 });
        sm.setMODz(new double[] { 0.133146, 0.776, 0 });
        matrices.put(StainModels.METHYL_GREEN_DAB, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.65, 0.072, 0.268 });
        sm.setMODy(new double[] { 0.704, 0.99, 0.57 });
        sm.setMODz(new double[] { 0.286, 0.105, 0.776 });
        matrices.put(StainModels.H_AND_E_DAB, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.65, 0.2743, 0 });
        sm.setMODy(new double[] { 0.704, 0.6796, 0 });
        sm.setMODz(new double[] { 0.286, 0.6803, 0 });
        matrices.put(StainModels.H_AEC, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.853033, 0.09289875, 0.10732849 });
        sm.setMODy(new double[] { 0.508733, 0.8662008, 0.36765403 });
        sm.setMODz(new double[] { 0.112656, 0.49098468, 0.9237484 });
        matrices.put(StainModels.AZAN_MALLORY, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.7995107, 0.09997159, 0 });
        sm.setMODy(new double[] { 0.5913521, 0.73738605, 0 });
        sm.setMODz(new double[] { 0.10528667, 0.6680326, 0 });
        matrices.put(StainModels.MASSON_TRICHROME, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.874622, 0.552556, 0 });
        sm.setMODy(new double[] { 0.457711, 0.7544, 0 });
        sm.setMODz(new double[] { 0.158256, 0.353744, 0 });
        matrices.put(StainModels.ALCIAN_BLUE_H, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.644211, 0.175411, 0 });
        sm.setMODy(new double[] { 0.716556, 0.972178, 0 });
        sm.setMODz(new double[] { 0.266844, 0.154589, 0 });
        matrices.put(StainModels.H_PAS, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0.31465548, 0.383573, 0.7433543 });
        sm.setMODy(new double[] { 0.6602395, 0.5271141, 0.51731443 });
        sm.setMODz(new double[] { 0.68196464, 0.7583024, 0.4240403 });
        matrices.put(StainModels.BRILLIANT_BLUE, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 0, 1, 1 });
        sm.setMODy(new double[] { 1, 0, 1 });
        sm.setMODz(new double[] { 1, 1, 0 });
        matrices.put(StainModels.RGB, sm);

        sm = new StainMatrix();
        sm.setMODx(new double[] { 1, 0, 0 });
        sm.setMODy(new double[] { 0, 1, 0 });
        sm.setMODz(new double[] { 0, 0, 1 });
        matrices.put(StainModels.CMY, sm);

        return matrices;

    }

    @Override
    public boolean verify() {
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy