Search in sources :

Example 6 with ColorDeconvolutionStains

use of qupath.lib.color.ColorDeconvolutionStains in project qupath by qupath.

the class ImageData method setColorDeconvolutionStains.

/**
 * Set the color deconvolution stain vectors for the current image type.
 * <p>
 * If the type is not brightfield, an IllegalArgumentException is thrown.
 *
 * @param stains
 */
public void setColorDeconvolutionStains(ColorDeconvolutionStains stains) {
    if (!isBrightfield())
        throw new IllegalArgumentException("Cannot set color deconvolution stains for image type " + type);
    logger.trace("Setting stains to {}", stains);
    ColorDeconvolutionStains stainsOld = stainMap.put(type, stains);
    pcs.firePropertyChange("stains", stainsOld, stains);
    addColorDeconvolutionStainsToWorkflow(this);
    // logger.error("WARNING: Setting color deconvolution stains is not yet scriptable!!!!");
    changes = true;
}
Also used : DefaultColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains.DefaultColorDeconvolutionStains) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Example 7 with ColorDeconvolutionStains

use of qupath.lib.color.ColorDeconvolutionStains in project qupath by qupath.

the class ImageDetailsPane method editStainVector.

void editStainVector(Object value) {
    if (!(value instanceof StainVector || value instanceof double[]))
        return;
    // JOptionPane.showMessageDialog(null, "Modifying stain vectors not yet implemented...");
    ImageData<BufferedImage> imageData = qupath.getImageData();
    if (imageData == null)
        return;
    ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
    // Default to background values
    int num = -1;
    String name = null;
    String message = null;
    if (value instanceof StainVector) {
        StainVector stainVector = (StainVector) value;
        if (stainVector.isResidual() && imageData.getImageType() != ImageType.BRIGHTFIELD_OTHER) {
            logger.warn("Cannot set residual stain vector - this is computed from the known vectors");
            return;
        }
        num = stains.getStainNumber(stainVector);
        if (num <= 0) {
            logger.error("Could not identify stain vector " + stainVector + " inside " + stains);
            return;
        }
        name = stainVector.getName();
        message = "Set stain vector from ROI?";
    } else
        message = "Set color deconvolution background values from ROI?";
    ROI pathROI = imageData.getHierarchy().getSelectionModel().getSelectedROI();
    boolean wasChanged = false;
    String warningMessage = null;
    boolean editableName = imageData.getImageType() == ImageType.BRIGHTFIELD_OTHER;
    if (pathROI != null) {
        if ((pathROI instanceof RectangleROI) && !pathROI.isEmpty() && ((RectangleROI) pathROI).getArea() < 500 * 500) {
            if (Dialogs.showYesNoDialog("Color deconvolution stains", message)) {
                ImageServer<BufferedImage> server = imageData.getServer();
                BufferedImage img = null;
                try {
                    img = server.readBufferedImage(RegionRequest.createInstance(server.getPath(), 1, pathROI));
                } catch (IOException e) {
                    Dialogs.showErrorMessage("Set stain vector", "Unable to read image region");
                    logger.error("Unable to read region", e);
                }
                int rgb = ColorDeconvolutionHelper.getMedianRGB(img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()));
                if (num >= 0) {
                    StainVector vectorValue = ColorDeconvolutionHelper.generateMedianStainVectorFromPixels(name, img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()), stains.getMaxRed(), stains.getMaxGreen(), stains.getMaxBlue());
                    if (!Double.isFinite(vectorValue.getRed() + vectorValue.getGreen() + vectorValue.getBlue())) {
                        Dialogs.showErrorMessage("Set stain vector", "Cannot set stains for the current ROI!\n" + "It might be too close to the background color.");
                        return;
                    }
                    value = vectorValue;
                } else {
                    // Update the background
                    value = new double[] { ColorTools.red(rgb), ColorTools.green(rgb), ColorTools.blue(rgb) };
                }
                wasChanged = true;
            }
        } else {
            warningMessage = "Note: To set stain values from an image region, draw a small, rectangular ROI first";
        }
    }
    // Prompt to set the name / verify stains
    ParameterList params = new ParameterList();
    String title;
    String nameBefore = null;
    String valuesBefore = null;
    String collectiveNameBefore = stains.getName();
    String suggestedName;
    if (collectiveNameBefore.endsWith("default"))
        suggestedName = collectiveNameBefore.substring(0, collectiveNameBefore.lastIndexOf("default")) + "modified";
    else
        suggestedName = collectiveNameBefore;
    params.addStringParameter("collectiveName", "Collective name", suggestedName, "Enter collective name for all 3 stains (e.g. H-DAB Scanner A, H&E Scanner B)");
    if (value instanceof StainVector) {
        nameBefore = ((StainVector) value).getName();
        valuesBefore = ((StainVector) value).arrayAsString(Locale.getDefault(Category.FORMAT));
        params.addStringParameter("name", "Name", nameBefore, "Enter stain name").addStringParameter("values", "Values", valuesBefore, "Enter 3 values (red, green, blue) defining color deconvolution stain vector, separated by spaces");
        title = "Set stain vector";
    } else {
        nameBefore = "Background";
        valuesBefore = GeneralTools.arrayToString(Locale.getDefault(Category.FORMAT), (double[]) value, 2);
        params.addStringParameter("name", "Stain name", nameBefore);
        params.addStringParameter("values", "Stain values", valuesBefore, "Enter 3 values (red, green, blue) defining background, separated by spaces");
        params.setHiddenParameters(true, "name");
        title = "Set background";
    }
    if (warningMessage != null)
        params.addEmptyParameter(warningMessage);
    // Disable editing the name if it should be fixed
    ParameterPanelFX parameterPanel = new ParameterPanelFX(params);
    parameterPanel.setParameterEnabled("name", editableName);
    ;
    if (!Dialogs.showConfirmDialog(title, parameterPanel.getPane()))
        return;
    // Check if anything changed
    String collectiveName = params.getStringParameterValue("collectiveName");
    String nameAfter = params.getStringParameterValue("name");
    String valuesAfter = params.getStringParameterValue("values");
    if (collectiveName.equals(collectiveNameBefore) && nameAfter.equals(nameBefore) && valuesAfter.equals(valuesBefore) && !wasChanged)
        return;
    double[] valuesParsed = ColorDeconvolutionStains.parseStainValues(Locale.getDefault(Category.FORMAT), valuesAfter);
    if (valuesParsed == null) {
        logger.error("Input for setting color deconvolution information invalid! Cannot parse 3 numbers from {}", valuesAfter);
        return;
    }
    if (num >= 0) {
        try {
            stains = stains.changeStain(StainVector.createStainVector(nameAfter, valuesParsed[0], valuesParsed[1], valuesParsed[2]), num);
        } catch (Exception e) {
            logger.error("Error setting stain vectors", e);
            Dialogs.showErrorMessage("Set stain vectors", "Requested stain vectors are not valid!\nAre two stains equal?");
        }
    } else {
        // Update the background
        stains = stains.changeMaxValues(valuesParsed[0], valuesParsed[1], valuesParsed[2]);
    }
    // Set the collective name
    stains = stains.changeName(collectiveName);
    imageData.setColorDeconvolutionStains(stains);
    qupath.getViewer().repaintEntireImage();
}
Also used : StainVector(qupath.lib.color.StainVector) IOException(java.io.IOException) RectangleROI(qupath.lib.roi.RectangleROI) ROI(qupath.lib.roi.interfaces.ROI) BufferedImage(java.awt.image.BufferedImage) ParameterPanelFX(qupath.lib.gui.dialogs.ParameterPanelFX) IOException(java.io.IOException) RectangleROI(qupath.lib.roi.RectangleROI) ParameterList(qupath.lib.plugins.parameters.ParameterList) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Example 8 with ColorDeconvolutionStains

use of qupath.lib.color.ColorDeconvolutionStains in project qupath by qupath.

the class PositivePixelCounterIJ method getDefaultParameterList.

@Override
public ParameterList getDefaultParameterList(final ImageData<BufferedImage> imageData) {
    ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
    String stain1Name = stains == null ? "Hematoxylin" : stains.getStain(1).getName();
    String stain2Name = stains == null ? "DAB" : stains.getStain(2).getName();
    ParameterList params = new ParameterList().addIntParameter("downsampleFactor", "Downsample factor", 4, "", 1, 32, "Amount to downsample image prior to detection - higher values lead to smaller images (and faster but less accurate processing)").addDoubleParameter("gaussianSigmaMicrons", "Gaussian sigma", 2, GeneralTools.micrometerSymbol(), "Gaussian filter size - higher values give a smoother (less-detailed) result").addDoubleParameter("thresholdStain1", stain1Name + " threshold ('Negative')", 0.1, "OD units", "Threshold to use for 'Negative' detection").addDoubleParameter("thresholdStain2", stain2Name + " threshold ('Positive')", 0.3, "OD units", "Threshold to use for 'Positive' stain detection").addBooleanParameter("addSummaryMeasurements", "Add summary measurements to parent", true, "Add summary measurements to parent objects").addBooleanParameter("clearParentMeasurements", "Clear existing parent measurements", true, "Remove any existing measurements from parent objects").addBooleanParameter("appendDetectionParameters", "Add parameters to measurement names", false, "Append the detection parameters to any measurement names").addBooleanParameter("legacyMeasurements0.1.2", "Use legacy measurements (v0.1.2)", false, "Generate measurements compatible with QuPath v0.1.2");
    return params;
}
Also used : ParameterList(qupath.lib.plugins.parameters.ParameterList) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Example 9 with ColorDeconvolutionStains

use of qupath.lib.color.ColorDeconvolutionStains in project qupath by qupath.

the class EstimateStainVectors method estimateStains.

/**
 * Check colors only currently applies to H&amp;E.
 *
 * @param rgbPacked
 * @param redOD
 * @param greenOD
 * @param blueOD
 * @param stainsOriginal
 * @param minStain
 * @param maxStain
 * @param ignorePercentage
 * @param checkColors
 * @return
 */
public static ColorDeconvolutionStains estimateStains(final int[] rgbPacked, final float[] redOD, final float[] greenOD, final float[] blueOD, final ColorDeconvolutionStains stainsOriginal, final double minStain, final double maxStain, final double ignorePercentage, final boolean checkColors) {
    double alpha = ignorePercentage / 100;
    int n = rgbPacked.length;
    if (redOD.length != n || greenOD.length != n || blueOD.length != n)
        throw new IllegalArgumentException("All pixel arrays must be the same length!");
    int[] rgb = Arrays.copyOf(rgbPacked, n);
    float[] red = Arrays.copyOf(redOD, n);
    float[] green = Arrays.copyOf(greenOD, n);
    float[] blue = Arrays.copyOf(blueOD, n);
    // Check if we do color sanity test
    boolean doColorTestForHE = checkColors && stainsOriginal.isH_E();
    // boolean doColorTestForHDAB = checkColors && stainsOriginal.isH_DAB();
    boolean doGrayTest = checkColors && (stainsOriginal.isH_E() || stainsOriginal.isH_DAB());
    double sqrt3 = 1 / Math.sqrt(3);
    double grayThreshold = Math.cos(0.15);
    // Loop through and discard pixels that are too faintly or densely stained
    int keepCount = 0;
    double maxStainSq = maxStain * maxStain;
    for (int i = 0; i < rgb.length; i++) {
        float r = red[i];
        float g = green[i];
        float b = blue[i];
        double magSquared = r * r + g * g + b * b;
        if (magSquared > maxStainSq || r < minStain || g < minStain || b < minStain || magSquared <= 0)
            continue;
        // Check for consistency with H&E staining, if required (i.e. only keep red/pink/purple/blue pixels and the like)
        if (doColorTestForHE && (r > g || b > g)) {
            continue;
        }
        // Exclude very 'gray' pixels
        if (doGrayTest && (r * sqrt3 + g * sqrt3 + b * sqrt3) / Math.sqrt(magSquared) >= grayThreshold) {
            continue;
        }
        // Update the arrays
        red[keepCount] = r;
        green[keepCount] = g;
        blue[keepCount] = b;
        rgb[keepCount] = rgb[i];
        keepCount++;
    }
    if (keepCount <= 1)
        throw new IllegalArgumentException("Not enough pixels remain after applying stain thresholds!");
    // Trim the arrays
    if (keepCount < rgb.length) {
        red = Arrays.copyOf(red, keepCount);
        green = Arrays.copyOf(green, keepCount);
        blue = Arrays.copyOf(blue, keepCount);
        rgb = Arrays.copyOf(rgb, keepCount);
    }
    double[][] cov = new double[3][3];
    cov[0][0] = covariance(red, red);
    cov[1][1] = covariance(green, green);
    cov[2][2] = covariance(blue, blue);
    cov[0][1] = covariance(red, green);
    cov[0][2] = covariance(red, blue);
    cov[1][2] = covariance(green, blue);
    cov[2][1] = cov[1][2];
    cov[2][0] = cov[0][2];
    cov[1][0] = cov[0][1];
    RealMatrix mat = MatrixUtils.createRealMatrix(cov);
    logger.debug("Covariance matrix:\n {}", getMatrixAsString(mat.getData()));
    EigenDecomposition eigen = new EigenDecomposition(mat);
    double[] eigenValues = eigen.getRealEigenvalues();
    int[] eigenOrder = rank(eigenValues);
    double[] eigen1 = eigen.getEigenvector(eigenOrder[2]).toArray();
    double[] eigen2 = eigen.getEigenvector(eigenOrder[1]).toArray();
    logger.debug("First eigenvector: " + getVectorAsString(eigen1));
    logger.debug("Second eigenvector: " + getVectorAsString(eigen2));
    double[] phi = new double[keepCount];
    for (int i = 0; i < keepCount; i++) {
        double r = red[i];
        double g = green[i];
        double b = blue[i];
        phi[i] = Math.atan2(r * eigen1[0] + g * eigen1[1] + b * eigen1[2], r * eigen2[0] + g * eigen2[1] + b * eigen2[2]);
    }
    /*
		 * Rather than projecting onto the plane (which might be a bit wrong),
		 * select the vectors directly from the data.
		 * This is effectively like a region selection, but where the region has
		 * been chosen automatically.
		 */
    int[] inds = rank(phi);
    int ind1 = inds[Math.max(0, (int) (alpha * keepCount + .5))];
    int ind2 = inds[Math.min(inds.length - 1, (int) ((1 - alpha) * keepCount + .5))];
    // Create new stain vectors
    StainVector s1 = StainVector.createStainVector(stainsOriginal.getStain(1).getName(), red[ind1], green[ind1], blue[ind1]);
    StainVector s2 = StainVector.createStainVector(stainsOriginal.getStain(2).getName(), red[ind2], green[ind2], blue[ind2]);
    // If working with H&E, we can use the simple heuristic of comparing the red values
    if (stainsOriginal.isH_E()) {
        // Need to check within the stain vectors (*not* original indexed values) because normalisation is important (I think... there were errors before)
        if (s1.getRed() < s2.getRed()) {
            s1 = StainVector.createStainVector(stainsOriginal.getStain(1).getName(), red[ind2], green[ind2], blue[ind2]);
            s2 = StainVector.createStainVector(stainsOriginal.getStain(2).getName(), red[ind1], green[ind1], blue[ind1]);
        }
    } else {
        // Check we've got the closest match - if not, switch the order
        double angle11 = StainVector.computeAngle(s1, stainsOriginal.getStain(1));
        double angle12 = StainVector.computeAngle(s1, stainsOriginal.getStain(2));
        double angle21 = StainVector.computeAngle(s2, stainsOriginal.getStain(1));
        double angle22 = StainVector.computeAngle(s2, stainsOriginal.getStain(2));
        if (Math.min(angle12, angle21) < Math.min(angle11, angle22)) {
            s1 = StainVector.createStainVector(stainsOriginal.getStain(1).getName(), red[ind2], green[ind2], blue[ind2]);
            s2 = StainVector.createStainVector(stainsOriginal.getStain(2).getName(), red[ind1], green[ind1], blue[ind1]);
        }
    }
    ColorDeconvolutionStains stains = new ColorDeconvolutionStains(stainsOriginal.getName(), s1, s2, stainsOriginal.getMaxRed(), stainsOriginal.getMaxGreen(), stainsOriginal.getMaxBlue());
    return stains;
}
Also used : StainVector(qupath.lib.color.StainVector) EigenDecomposition(org.apache.commons.math3.linear.EigenDecomposition) RealMatrix(org.apache.commons.math3.linear.RealMatrix) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Example 10 with ColorDeconvolutionStains

use of qupath.lib.color.ColorDeconvolutionStains in project qupath by qupath.

the class ImageData method addColorDeconvolutionStainsToWorkflow.

// TODO: REINTRODUCE LOGGING!
private static void addColorDeconvolutionStainsToWorkflow(ImageData<?> imageData) {
    // logger.warn("Color deconvolution stain logging not currently enabled!");
    ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
    if (stains == null) {
        return;
    }
    String arg = ColorDeconvolutionStains.getColorDeconvolutionStainsAsString(imageData.getColorDeconvolutionStains(), 5);
    Map<String, String> map = GeneralTools.parseArgStringValues(arg);
    WorkflowStep lastStep = imageData.getHistoryWorkflow().getLastStep();
    String commandName = "Set color deconvolution stains";
    WorkflowStep newStep = new DefaultScriptableWorkflowStep(commandName, map, "setColorDeconvolutionStains(\'" + arg + "');");
    // else
    if (!Objects.equals(newStep, lastStep))
        imageData.getHistoryWorkflow().addStep(newStep);
// ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
// if (stains == null)
// return;
// 
// String arg = ColorDeconvolutionStains.getColorDeconvolutionStainsAsString(imageData.getColorDeconvolutionStains(), 5);
// Map<String, String> map = GeneralTools.parseArgStringValues(arg);
// WorkflowStep lastStep = imageData.getWorkflow().getLastStep();
// String commandName = "Set color deconvolution stains";
// WorkflowStep newStep = new DefaultScriptableWorkflowStep(commandName,
// map,
// QP.class.getSimpleName() + ".setColorDeconvolutionStains(\'" + arg + "');");
// 
// if (lastStep != null && commandName.equals(lastStep.getName()))
// imageData.getWorkflow().replaceLastStep(newStep);
// else
// imageData.getWorkflow().addStep(newStep);
}
Also used : DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) WorkflowStep(qupath.lib.plugins.workflow.WorkflowStep) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) DefaultColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains.DefaultColorDeconvolutionStains) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Aggregations

ColorDeconvolutionStains (qupath.lib.color.ColorDeconvolutionStains)12 BufferedImage (java.awt.image.BufferedImage)4 IOException (java.io.IOException)4 DefaultColorDeconvolutionStains (qupath.lib.color.ColorDeconvolutionStains.DefaultColorDeconvolutionStains)4 StainVector (qupath.lib.color.StainVector)3 ParameterList (qupath.lib.plugins.parameters.ParameterList)3 DialogButton (qupath.lib.gui.dialogs.Dialogs.DialogButton)2 ParameterPanelFX (qupath.lib.gui.dialogs.ParameterPanelFX)2 PathObject (qupath.lib.objects.PathObject)2 ROI (qupath.lib.roi.interfaces.ROI)2 BufferedInputStream (java.io.BufferedInputStream)1 EOFException (java.io.EOFException)1 FileNotFoundException (java.io.FileNotFoundException)1 ObjectInputStream (java.io.ObjectInputStream)1 URI (java.net.URI)1 HashMap (java.util.HashMap)1 Locale (java.util.Locale)1 Map (java.util.Map)1 SimpleStringProperty (javafx.beans.property.SimpleStringProperty)1 Insets (javafx.geometry.Insets)1