Search in sources :

Example 6 with PathCellObject

use of qupath.lib.objects.PathCellObject in project qupath by qupath.

the class IntensityFeaturesPlugin method processObject.

static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageData<BufferedImage> imageData) throws IOException {
    // Determine amount to downsample
    var server = imageData.getServer();
    var stains = imageData.getColorDeconvolutionStains();
    PixelCalibration cal = server.getPixelCalibration();
    double downsample = calculateDownsample(cal, params);
    if (downsample <= 0) {
        logger.warn("Effective downsample must be > 0 (requested value {})", downsample);
    }
    // Determine region shape
    RegionType regionType = (RegionType) params.getChoiceParameterValue("region");
    // Try to get ROI
    boolean useROI = regionType == RegionType.ROI || regionType == RegionType.NUCLEUS;
    ROI roi = null;
    if (regionType == RegionType.NUCLEUS) {
        if (pathObject instanceof PathCellObject)
            roi = ((PathCellObject) pathObject).getNucleusROI();
    } else
        roi = pathObject.getROI();
    // pathROI = ((PathCellObject)pathObject).getNucleusROI();
    if (roi == null)
        return false;
    // Create a map - this is useful for occasions when tiling is needed
    Map<FeatureColorTransform, List<FeatureComputer>> map = new LinkedHashMap<>();
    if (server.isRGB()) {
        for (FeatureColorTransform transform : FeatureColorTransformEnum.values()) {
            List<FeatureComputer> list = new ArrayList<>();
            map.put(transform, list);
            for (FeatureComputerBuilder builder : builders) {
                list.add(builder.build());
            }
        }
    } else {
        for (FeatureColorTransform transform : getBasicChannelTransforms(server.nChannels())) {
            List<FeatureComputer> list = new ArrayList<>();
            map.put(transform, list);
            for (FeatureComputerBuilder builder : builders) {
                list.add(builder.build());
            }
        }
    }
    String prefix = getDiameterString(server, params);
    // Create tiled ROIs, if required
    ImmutableDimension sizePreferred = ImmutableDimension.getInstance((int) (2000 * downsample), (int) (2000 * downsample));
    // ImmutableDimension sizePreferred = new ImmutableDimension((int)(200*downsample), (int)(200*downsample));
    Collection<? extends ROI> rois = RoiTools.computeTiledROIs(roi, sizePreferred, sizePreferred, false, 0);
    if (rois.size() > 1)
        logger.info("Splitting {} into {} tiles for intensity measurements", roi, rois.size());
    for (ROI pathROI : rois) {
        if (Thread.currentThread().isInterrupted()) {
            logger.warn("Measurement skipped - thread interrupted!");
            return false;
        }
        // Get bounds
        RegionRequest region;
        if (useROI) {
            region = RegionRequest.createInstance(server.getPath(), downsample, pathROI);
        } else {
            ImmutableDimension size = getPreferredTileSizePixels(server, params);
            // RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
            // Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
            int xStart = (int) (Math.round(pathROI.getCentroidX() / downsample) * downsample) - size.width / 2;
            int yStart = (int) (Math.round(pathROI.getCentroidY() / downsample) * downsample) - size.height / 2;
            int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
            int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
            region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
        }
        // // Check image large enough to do *anything* of value
        // if (region.getWidth() / downsample < 1 || region.getHeight() / downsample < 1) {
        // logger.trace("Requested region is too small! {}", region);
        // return false;
        // }
        // System.out.println(bounds);
        // System.out.println("Size: " + size);
        BufferedImage img = server.readBufferedImage(region);
        if (img == null) {
            logger.error("Could not read image - unable to compute intensity features for {}", pathObject);
            return false;
        }
        // Create mask ROI if necessary
        // If we just have 1 pixel, we want to use it so that the mean/min/max measurements are valid (even if nothing else is)
        byte[] maskBytes = null;
        if (useROI && img.getWidth() * img.getHeight() > 1) {
            BufferedImage imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), pathROI, region);
            maskBytes = ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData();
        }
        boolean isRGB = server.isRGB();
        List<FeatureColorTransform> transforms;
        if (isRGB)
            transforms = Arrays.asList(FeatureColorTransformEnum.values());
        else
            transforms = getBasicChannelTransforms(server.nChannels());
        int w = img.getWidth();
        int h = img.getHeight();
        int[] rgbBuffer = isRGB ? img.getRGB(0, 0, w, h, null, 0, w) : null;
        float[] pixels = null;
        for (FeatureColorTransform transform : transforms) {
            // Check if the color transform is requested
            if (params.containsKey(transform.getKey()) && Boolean.TRUE.equals(params.getBooleanParameterValue(transform.getKey()))) {
                // Transform the pixels
                pixels = transform.getTransformedPixels(img, rgbBuffer, stains, pixels);
                // Create the simple image
                SimpleModifiableImage pixelImage = SimpleImages.createFloatImage(pixels, w, h);
                // Apply any arbitrary mask
                if (maskBytes != null) {
                    for (int i = 0; i < pixels.length; i++) {
                        if (maskBytes[i] == (byte) 0)
                            pixelImage.setValue(i % w, i / w, Float.NaN);
                    }
                } else if (regionType == RegionType.CIRCLE) {
                    // Apply circular tile mask
                    double cx = (w - 1) / 2;
                    double cy = (h - 1) / 2;
                    double radius = Math.max(w, h) * .5;
                    double distThreshold = radius * radius;
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                            if ((cx - x) * (cx - x) + (cy - y) * (cy - y) > distThreshold)
                                pixelImage.setValue(x, y, Float.NaN);
                        }
                    }
                }
                // Do the computations
                for (FeatureComputer computer : map.get(transform)) {
                    computer.updateFeatures(pixelImage, transform, params);
                }
            }
        }
    }
    // Add measurements to the parent object
    for (Entry<FeatureColorTransform, List<FeatureComputer>> entry : map.entrySet()) {
        String name = prefix + ": " + entry.getKey().getName(imageData, false) + ":";
        for (FeatureComputer computer : entry.getValue()) computer.addMeasurements(pathObject, name, params);
    }
    pathObject.getMeasurementList().close();
    // Lock any measurements that require it
    if (pathObject instanceof PathAnnotationObject)
        ((PathAnnotationObject) pathObject).setLocked(true);
    else if (pathObject instanceof TMACoreObject)
        ((TMACoreObject) pathObject).setLocked(true);
    return true;
}
Also used : ArrayList(java.util.ArrayList) BufferedImage(java.awt.image.BufferedImage) LinkedHashMap(java.util.LinkedHashMap) HaralickFeatureComputer(qupath.lib.analysis.features.HaralickFeatureComputer) SimpleModifiableImage(qupath.lib.analysis.images.SimpleModifiableImage) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) ParameterList(qupath.lib.plugins.parameters.ParameterList) List(java.util.List) TMACoreObject(qupath.lib.objects.TMACoreObject) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) ImmutableDimension(qupath.lib.geom.ImmutableDimension) RegionRequest(qupath.lib.regions.RegionRequest) PathCellObject(qupath.lib.objects.PathCellObject)

Example 7 with PathCellObject

use of qupath.lib.objects.PathCellObject in project qupath by qupath.

the class HaralickFeaturesPlugin method processObject.

static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageServer<BufferedImage> server, final ColorDeconvolutionStains stains) throws IOException {
    String stainsName = (String) params.getChoiceParameterValue("stainChoice");
    double mag = params.getDoubleParameterValue("magnification");
    int d = params.getIntParameterValue("haralickDistance");
    int nBins = params.getIntParameterValue("haralickBins");
    boolean includeStats = params.getBooleanParameterValue("includeStats");
    boolean doCircular = params.getBooleanParameterValue("doCircular");
    double downsample;
    boolean hasMagnification = !Double.isNaN(server.getMetadata().getMagnification());
    PixelCalibration cal = server.getPixelCalibration();
    if (hasMagnification)
        downsample = server.getMetadata().getMagnification() / mag;
    else if (cal.hasPixelSizeMicrons()) {
        downsample = params.getDoubleParameterValue("pixelSizeMicrons") / cal.getAveragedPixelSizeMicrons();
    } else
        downsample = params.getDoubleParameterValue("downsample");
    // double downsample = server.getMagnification() / mag;
    // Try to get ROI
    ROI pathROI = null;
    if (pathObject instanceof PathCellObject && Boolean.TRUE.equals(params.getBooleanParameterValue("useNucleusROIs")))
        pathROI = ((PathCellObject) pathObject).getNucleusROI();
    else
        pathROI = pathObject.getROI();
    if (pathROI == null)
        return false;
    // Get bounds
    ImmutableDimension size = getPreferredTileSizePixels(server, params);
    RegionRequest region;
    boolean createMaskROI = false;
    if (size.getWidth() <= 0 || size.getHeight() <= 0) {
        region = RegionRequest.createInstance(server.getPath(), downsample, pathObject.getROI());
        createMaskROI = true;
        doCircular = false;
    } else if (size.getWidth() / downsample < 1 || size.getHeight() / downsample < 1)
        // Positive size, but insufficient to make measurements
        return false;
    else {
        // RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
        // Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
        int xStart = (int) ((int) (pathROI.getCentroidX() / downsample + .5) * downsample) - size.width / 2;
        int yStart = (int) ((int) (pathROI.getCentroidY() / downsample + .5) * downsample) - size.height / 2;
        int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
        int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
        region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
    }
    // Check image large enough to do *anything* of value
    if (region.getWidth() / downsample < 3 || region.getHeight() / downsample < 3)
        return false;
    // System.out.println(bounds);
    // System.out.println("Size: " + size);
    BufferedImage img = server.readBufferedImage(region);
    if (img == null) {
        logger.error("Could not read image - unable to compute Haralick features for {}", pathObject);
        return false;
    }
    // Create mask ROI if necessary
    byte[] maskBytes = null;
    if (createMaskROI) {
        ROI roi = pathObject.getROI();
        // if (pathObject instanceof PathCellObject && ((PathCellObject)pathObject).getNucleusROI() != null)
        // roi = ((PathCellObject)pathObject).getNucleusROI();
        BufferedImage imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), roi, region);
        maskBytes = ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData();
    }
    double minValue = Double.NaN;
    double maxValue = Double.NaN;
    // Get a buffer containing the image pixels
    int w = img.getWidth();
    int h = img.getHeight();
    int[] buf = img.getRGB(0, 0, w, h, null, 0, w);
    // Create a color transformer to get the images we need
    float[] pixels = new float[buf.length];
    SimpleModifiableImage pxImg = SimpleImages.createFloatImage(pixels, w, h);
    MeasurementList measurementList = pathObject.getMeasurementList();
    String postfix = maskBytes == null ? " (" + getDiameterString(server, params) + ")" : "";
    if (stainsName.equals("H-DAB")) {
        minValue = 0;
        maxValue = 2.0;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "DAB" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("H&E")) {
        minValue = 0;
        maxValue = 2;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("H-DAB (8-bit)")) {
        minValue = 0;
        maxValue = 255;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "DAB 8-bit" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("H&E (8-bit)")) {
        minValue = 0;
        maxValue = 255;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("Optical density")) {
        minValue = 0;
        maxValue = 2.5;
        processTransformedImage(pxImg, buf, pixels, measurementList, "OD sum" + postfix, ColorTransformer.ColorTransformMethod.Optical_density_sum, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("RGB")) {
        minValue = 0;
        maxValue = 255;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Red" + postfix, ColorTransformer.ColorTransformMethod.Red, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Green" + postfix, ColorTransformer.ColorTransformMethod.Green, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Blue" + postfix, ColorTransformer.ColorTransformMethod.Blue, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("RGB OD")) {
        minValue = 0;
        // Actual possible max is around 2.4 for 8-bit input... but this gives a lot of bins for (almost) saturated pixels
        maxValue = 1.5;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Red OD" + postfix, ColorTransformer.ColorTransformMethod.Red_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Green OD" + postfix, ColorTransformer.ColorTransformMethod.Green_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(pxImg, buf, pixels, measurementList, "Blue OD" + postfix, ColorTransformer.ColorTransformMethod.Blue_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("Grayscale")) {
        minValue = 0;
        maxValue = 255;
        processTransformedImage(pxImg, buf, pixels, measurementList, "Grayscale" + postfix, ColorTransformer.ColorTransformMethod.RGB_mean, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    } else if (stainsName.equals("HSB")) {
        minValue = 0;
        maxValue = 1;
        float[] hsb = null;
        double sinX = 0;
        double cosX = 0;
        float[] pixelsBrightness = new float[pixels.length];
        float[] pixelsSaturation = new float[pixels.length];
        for (int i = 0; i < buf.length; i++) {
            if (maskBytes != null && maskBytes[i] == (byte) 0)
                continue;
            int val = buf[i];
            hsb = Color.RGBtoHSB(ColorTools.red(val), ColorTools.green(val), ColorTools.blue(val), hsb);
            pixelsSaturation[i] = hsb[1];
            pixelsBrightness[i] = hsb[2];
            double alpha = hsb[0] * 2 * Math.PI;
            sinX += Math.sin(alpha);
            cosX += Math.cos(alpha);
        }
        measurementList.putMeasurement("Mean hue", Math.atan2(sinX, cosX) / (2 * Math.PI) + 0.5);
        // measurementList.putMeasurement("Mean saturation", hsb[1]);
        // measurementList.putMeasurement("Mean brightness", hsb[2]);
        processTransformedImage(SimpleImages.createFloatImage(pixelsSaturation, w, h), buf, pixelsSaturation, measurementList, "Saturation" + postfix, null, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
        processTransformedImage(SimpleImages.createFloatImage(pixelsBrightness, w, h), buf, pixelsBrightness, measurementList, "Brightness" + postfix, null, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
    }
    measurementList.close();
    return true;
}
Also used : MeasurementList(qupath.lib.measurements.MeasurementList) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) BufferedImage(java.awt.image.BufferedImage) SimpleModifiableImage(qupath.lib.analysis.images.SimpleModifiableImage) ImmutableDimension(qupath.lib.geom.ImmutableDimension) RegionRequest(qupath.lib.regions.RegionRequest) PathCellObject(qupath.lib.objects.PathCellObject)

Example 8 with PathCellObject

use of qupath.lib.objects.PathCellObject in project qupath by qupath.

the class ShapeFeaturesPlugin method addRunnableTasks.

@Override
protected void addRunnableTasks(final ImageData<T> imageData, final PathObject parentObject, List<Runnable> tasks) {
    PixelCalibration cal = imageData == null ? null : imageData.getServer().getPixelCalibration();
    boolean useMicrons = params.getBooleanParameterValue("useMicrons") && cal != null && cal.hasPixelSizeMicrons();
    double pixelWidth = useMicrons ? cal.getPixelWidthMicrons() : 1;
    double pixelHeight = useMicrons ? cal.getPixelHeightMicrons() : 1;
    String unit = useMicrons ? GeneralTools.micrometerSymbol() : "px";
    boolean doArea = params.getBooleanParameterValue("area");
    boolean doPerimeter = params.getBooleanParameterValue("perimeter");
    boolean doCircularity = params.getBooleanParameterValue("circularity");
    ROI roi = (parentObject.hasROI() && parentObject.getROI().isArea()) ? parentObject.getROI() : null;
    if (roi != null) {
        tasks.add(new Runnable() {

            @Override
            public void run() {
                try {
                    MeasurementList measurementList = parentObject.getMeasurementList();
                    ROI roi;
                    if (parentObject instanceof PathCellObject) {
                        roi = ((PathCellObject) parentObject).getNucleusROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "Nucleus Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                        roi = parentObject.getROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "Cell Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                    } else {
                        roi = parentObject.getROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "ROI Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                    }
                    measurementList.close();
                } catch (Exception e) {
                    e.printStackTrace();
                    throw (e);
                }
            }
        });
    }
}
Also used : MeasurementList(qupath.lib.measurements.MeasurementList) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) PathCellObject(qupath.lib.objects.PathCellObject)

Aggregations

PathCellObject (qupath.lib.objects.PathCellObject)8 ROI (qupath.lib.roi.interfaces.ROI)7 MeasurementList (qupath.lib.measurements.MeasurementList)5 PixelCalibration (qupath.lib.images.servers.PixelCalibration)4 PathObject (qupath.lib.objects.PathObject)4 BufferedImage (java.awt.image.BufferedImage)3 LinkedHashMap (java.util.LinkedHashMap)3 RegionRequest (qupath.lib.regions.RegionRequest)3 ByteProcessor (ij.process.ByteProcessor)2 ColorProcessor (ij.process.ColorProcessor)2 ImageProcessor (ij.process.ImageProcessor)2 Color (java.awt.Color)2 ArrayList (java.util.ArrayList)2 PixelImageIJ (qupath.imagej.tools.PixelImageIJ)2 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)2 PathDetectionObject (qupath.lib.objects.PathDetectionObject)2 TMACoreObject (qupath.lib.objects.TMACoreObject)2 EllipseROI (qupath.lib.roi.EllipseROI)2 Overlay (ij.gui.Overlay)1 Roi (ij.gui.Roi)1