Search in sources :

Example 1 with PathDetectionObject

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

the class PathHierarchyPaintingHelper method paintObject.

/**
 * Paint an object (or, more precisely, its ROI), optionally along with the ROIs of any child objects.
 *
 * This is subject to the OverlayOptions, and therefore may not actually end up painting anything
 * (if the settings are such that objects of the class provided are not to be displayed)
 *
 * @param pathObject
 * @param paintChildren
 * @param g
 * @param boundsDisplayed
 * @param overlayOptions
 * @param selectionModel
 * @param downsample
 * @return true if anything was painted, false otherwise
 */
public static boolean paintObject(PathObject pathObject, boolean paintChildren, Graphics2D g, Rectangle boundsDisplayed, OverlayOptions overlayOptions, PathObjectSelectionModel selectionModel, double downsample) {
    if (pathObject == null)
        return false;
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    // Always paint the selected object
    // Note: this makes the assumption that child ROIs are completely contained within their parents;
    // this probably should be the case, but isn't guaranteed
    boolean isSelected = (selectionModel != null && selectionModel.isSelected(pathObject)) && (PathPrefs.useSelectedColorProperty().get() || !PathObjectTools.hasPointROI(pathObject));
    boolean isDetectedObject = pathObject.isDetection() || (pathObject.isTile() && pathObject.hasMeasurements());
    // Check if the PathClass isn't being shown
    PathClass pathClass = pathObject.getPathClass();
    if (!isSelected && overlayOptions != null && overlayOptions.isPathClassHidden(pathClass))
        return false;
    boolean painted = false;
    // See if we need to check the children
    ROI pathROI = pathObject.getROI();
    if (pathROI != null) {
        double roiBoundsX = pathROI.getBoundsX();
        double roiBoundsY = pathROI.getBoundsY();
        double roiBoundsWidth = pathROI.getBoundsWidth();
        double roiBoundsHeight = pathROI.getBoundsHeight();
        if (PathObjectTools.hasPointROI(pathObject) || boundsDisplayed == null || pathROI instanceof LineROI || boundsDisplayed.intersects(roiBoundsX, roiBoundsY, Math.max(roiBoundsWidth, 1), Math.max(roiBoundsHeight, 1))) {
            // Paint the ROI, if necessary
            if (isSelected || (overlayOptions.getShowDetections() && isDetectedObject) || (overlayOptions.getShowAnnotations() && pathObject.isAnnotation()) || (overlayOptions.getShowTMAGrid() && pathObject.isTMACore())) {
                boolean doFill = overlayOptions.getFillDetections() || pathObject instanceof ParallelTileObject;
                boolean doOutline = true;
                Color color = null;
                boolean useMapper = false;
                double fillOpacity = .75;
                if (isSelected && PathPrefs.useSelectedColorProperty().get() && PathPrefs.colorSelectedObjectProperty().getValue() != null)
                    color = ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get());
                else {
                    MeasurementMapper mapper = overlayOptions.getMeasurementMapper();
                    useMapper = mapper != null && mapper.isValid() && pathObject.isDetection();
                    if (useMapper) {
                        if (pathObject.hasMeasurements()) {
                            Integer rgb = mapper.getColorForObject(pathObject);
                            // If the mapper returns null, the object shouldn't be painted
                            if (rgb == null)
                                return false;
                            // , mapper.getColorMapper().hasAlpha());
                            color = ColorToolsAwt.getCachedColor(rgb);
                        } else
                            color = null;
                        // System.out.println(color + " - " + pathObject.getMeasurementList().getMeasurementValue(mapper.));
                        fillOpacity = 1.0;
                        // Outlines are not so helpful with the measurement mapper
                        if (doFill)
                            doOutline = doOutline && !pathObject.isTile();
                    } else {
                        Integer rgb = ColorToolsFX.getDisplayedColorARGB(pathObject);
                        color = ColorToolsAwt.getCachedColor(rgb);
                    }
                // color = PathObjectHelpers.getDisplayedColor(pathObject);
                }
                // Check if we have only one or two pixels to draw - if so, we can be done quickly
                if (isDetectedObject && downsample > 4 && roiBoundsWidth / downsample < 3 && roiBoundsHeight / downsample < 3) {
                    int x = (int) roiBoundsX;
                    int y = (int) roiBoundsY;
                    // Prefer rounding up, lest we lose a lot of regions unnecessarily
                    int w = (int) (roiBoundsWidth + .9);
                    int h = (int) (roiBoundsHeight + .9);
                    if (w > 0 && h > 0) {
                        g.setColor(color);
                        // g.setColor(DisplayHelpers.getMoreTranslucentColor(color));
                        // g.setStroke(getCachedStroke(overlayOptions.strokeThinThicknessProperty().get()));
                        g.fillRect(x, y, w, h);
                    }
                    painted = true;
                } else {
                    Stroke stroke = null;
                    // Decide whether to fill or not
                    Color colorFill = doFill && (isDetectedObject || PathObjectTools.hasPointROI(pathObject)) ? color : null;
                    if (colorFill != null && fillOpacity != 1) {
                        if (pathObject instanceof ParallelTileObject)
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        else if (pathObject instanceof PathCellObject && overlayOptions.getShowCellBoundaries() && overlayOptions.getShowCellNuclei()) {
                            // if (isSelected)
                            // colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                            // else
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        } else if (pathObject.getParent() instanceof PathDetectionObject) {
                            colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                        } else if (pathObject instanceof PathTileObject && pathClass == null && color != null && color.getRGB() == PathPrefs.colorTileProperty().get()) {
                            // Don't fill in empty, unclassified tiles
                            // DisplayHelpers.getMoreTranslucentColor(colorFill);
                            colorFill = null;
                        }
                    }
                    // Color colorStroke = doOutline ? (colorFill == null ? color : (downsample > overlayOptions.strokeThinThicknessProperty().get() ? null : DisplayHelpers.darkenColor(color))) : null;
                    Color colorStroke = doOutline ? (colorFill == null ? color : ColorToolsAwt.darkenColor(color)) : null;
                    // For thick lines, antialiasing is very noticeable... less so for thin lines (of which there may be a huge number)
                    if (isDetectedObject) {
                        // Detections inside detections get half the line width
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get() / 2.0);
                        else
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get());
                    } else {
                        double thicknessScale = downsample * (isSelected && !PathPrefs.useSelectedColorProperty().get() ? 1.6 : 1);
                        float thickness = (float) (PathPrefs.annotationStrokeThicknessProperty().get() * thicknessScale);
                        if (isSelected && pathObject.getParent() == null && PathPrefs.selectionModeProperty().get()) {
                            stroke = getCachedStrokeDashed(thickness);
                        } else {
                            stroke = getCachedStroke(thickness);
                        }
                    }
                    g.setStroke(stroke);
                    boolean paintSymbols = overlayOptions.getDetectionDisplayMode() == DetectionDisplayMode.CENTROIDS && pathObject.isDetection() && !pathObject.isTile();
                    if (paintSymbols) {
                        pathROI = PathObjectTools.getROI(pathObject, true);
                        double x = pathROI.getCentroidX();
                        double y = pathROI.getCentroidY();
                        double radius = PathPrefs.detectionStrokeThicknessProperty().get() * 2.0;
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            radius /= 2.0;
                        Shape shape;
                        int nSubclasses = 0;
                        if (pathClass != null) {
                            nSubclasses = PathClassTools.splitNames(pathClass).size();
                        }
                        switch(nSubclasses) {
                            case 0:
                                var ellipse = localEllipse2D.get();
                                ellipse.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = ellipse;
                                break;
                            case 1:
                                var rect = localRect2D.get();
                                rect.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = rect;
                                break;
                            case 2:
                                var triangle = localPath2D.get();
                                double sqrt3 = Math.sqrt(3.0);
                                triangle.reset();
                                triangle.moveTo(x, y - radius * 2.0 / sqrt3);
                                triangle.lineTo(x - radius, y + radius / sqrt3);
                                triangle.lineTo(x + radius, y + radius / sqrt3);
                                triangle.closePath();
                                shape = triangle;
                                break;
                            case 3:
                                var plus = localPath2D.get();
                                plus.reset();
                                plus.moveTo(x, y - radius);
                                plus.lineTo(x, y + radius);
                                plus.moveTo(x - radius, y);
                                plus.lineTo(x + radius, y);
                                shape = plus;
                                break;
                            default:
                                var cross = localPath2D.get();
                                cross.reset();
                                radius /= Math.sqrt(2);
                                cross.moveTo(x - radius, y - radius);
                                cross.lineTo(x + radius, y + radius);
                                cross.moveTo(x + radius, y - radius);
                                cross.lineTo(x - radius, y + radius);
                                shape = cross;
                                break;
                        }
                        paintShape(shape, g, colorStroke, stroke, colorFill);
                    } else if (pathObject instanceof PathCellObject) {
                        PathCellObject cell = (PathCellObject) pathObject;
                        if (overlayOptions.getShowCellBoundaries())
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        if (overlayOptions.getShowCellNuclei())
                            paintROI(cell.getNucleusROI(), g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    } else {
                        if ((overlayOptions.getFillAnnotations() && pathObject.isAnnotation() && pathObject.getPathClass() != PathClassFactory.getPathClass(StandardPathClasses.REGION) && (pathObject.getPathClass() != null || !pathObject.hasChildren())) || (pathObject.isTMACore() && overlayOptions.getShowTMACoreLabels()))
                            paintROI(pathROI, g, colorStroke, stroke, ColorToolsAwt.getMoreTranslucentColor(colorStroke), downsample);
                        else
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    }
                }
            }
        }
    }
    // Paint the children, if necessary
    if (paintChildren) {
        for (PathObject childObject : pathObject.getChildObjectsAsArray()) {
            // Only call the painting method if required
            ROI childROI = childObject.getROI();
            if ((childROI != null && boundsDisplayed.intersects(childROI.getBoundsX(), childROI.getBoundsY(), childROI.getBoundsWidth(), childROI.getBoundsHeight())) || childObject.hasChildren())
                painted = paintObject(childObject, paintChildren, g, boundsDisplayed, overlayOptions, selectionModel, downsample) | painted;
        }
    }
    return painted;
}
Also used : BasicStroke(java.awt.BasicStroke) Stroke(java.awt.Stroke) PathDetectionObject(qupath.lib.objects.PathDetectionObject) Shape(java.awt.Shape) RectangularShape(java.awt.geom.RectangularShape) Color(java.awt.Color) EllipseROI(qupath.lib.roi.EllipseROI) PointsROI(qupath.lib.roi.PointsROI) RectangleROI(qupath.lib.roi.RectangleROI) LineROI(qupath.lib.roi.LineROI) ROI(qupath.lib.roi.interfaces.ROI) ParallelTileObject(qupath.lib.plugins.ParallelTileObject) PathClass(qupath.lib.objects.classes.PathClass) PathTileObject(qupath.lib.objects.PathTileObject) PathObject(qupath.lib.objects.PathObject) MeasurementMapper(qupath.lib.gui.tools.MeasurementMapper) LineROI(qupath.lib.roi.LineROI) PathCellObject(qupath.lib.objects.PathCellObject)

Example 2 with PathDetectionObject

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

the class IJExtension method extractOverlay.

/**
 * Extract an ImageJ overlay for the specified region.
 * @param hierarchy
 * @param request
 * @param options options to control which objects are being displayed
 * @param filter optional additional filter used to determine which objects will be included (may be used in combination with options)
 * @return
 */
public static Overlay extractOverlay(PathObjectHierarchy hierarchy, RegionRequest request, OverlayOptions options, Predicate<PathObject> filter) {
    Overlay overlay = new Overlay();
    double downsample = request.getDownsample();
    double xOrigin = -request.getX() / downsample;
    double yOrigin = -request.getY() / downsample;
    // TODO: Permit filling/unfilling ROIs
    for (PathObject child : hierarchy.getObjectsForRegion(PathObject.class, request, null)) {
        if (filter != null && !filter.test(child))
            continue;
        if (child.hasROI()) {
            // Check if this is displayed - skip it not
            if (options != null && ((child instanceof PathDetectionObject && !options.getShowDetections()) || (child instanceof PathAnnotationObject && !options.getShowAnnotations()) || (child instanceof TMACoreObject && !options.getShowTMAGrid())))
                continue;
            boolean isCell = child instanceof PathCellObject;
            Color color = ColorToolsAwt.getCachedColor(ColorToolsFX.getDisplayedColorARGB(child));
            if (!(isCell && (options == null || !options.getShowCellBoundaries()))) {
                Roi roi = IJTools.convertToIJRoi(child.getROI(), xOrigin, yOrigin, downsample);
                roi.setStrokeColor(color);
                roi.setName(child.getDisplayedName());
                // roi.setStrokeWidth(2);
                overlay.add(roi);
            }
            if (isCell && (options == null || options.getShowCellNuclei())) {
                ROI nucleus = ((PathCellObject) child).getNucleusROI();
                if (nucleus == null)
                    continue;
                Roi roi = IJTools.convertToIJRoi(((PathCellObject) child).getNucleusROI(), xOrigin, yOrigin, downsample);
                roi.setStrokeColor(color);
                roi.setName(child.getDisplayedName() + " - nucleus");
                overlay.add(roi);
            }
        }
    }
    return overlay;
}
Also used : PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) TMACoreObject(qupath.lib.objects.TMACoreObject) Color(java.awt.Color) Overlay(ij.gui.Overlay) Roi(ij.gui.Roi) ROI(qupath.lib.roi.interfaces.ROI) PathCellObject(qupath.lib.objects.PathCellObject)

Example 3 with PathDetectionObject

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

the class SmoothFeaturesPlugin method smoothMeasurements.

/**
 * Using the centroids of the ROIs within PathObjects, 'smooth' measurements by summing up the corresponding measurements of
 * nearby objects, weighted by centroid distance.
 *
 * @param pathObjects
 * @param measurements
 * @param fwhmPixels
 * @param fwhmString
 * @param withinClass
 * @param useLegacyNames
 */
// public static Set<String> smoothMeasurements(List<PathObject> pathObjects, List<String> measurements, double fwhmPixels) {
public static void smoothMeasurements(List<PathObject> pathObjects, List<String> measurements, double fwhmPixels, String fwhmString, boolean withinClass, boolean useLegacyNames) {
    if (measurements.isEmpty() || pathObjects.size() <= 1)
        // Collections.emptySet();
        return;
    if (fwhmString == null)
        fwhmString = String.format("%.2f px", fwhmPixels);
    double fwhmPixels2 = fwhmPixels * fwhmPixels;
    double sigmaPixels = fwhmPixels / Math.sqrt(8 * Math.log(2));
    double sigma2 = 2 * sigmaPixels * sigmaPixels;
    double maxDist = sigmaPixels * 3;
    // Maximum separation
    double maxDistSq = maxDist * maxDist;
    int nObjects = pathObjects.size();
    // int counter = 0;
    // Sort by x-coordinate - this gives us a method of breaking early
    Collections.sort(pathObjects, new Comparator<PathObject>() {

        @Override
        public int compare(PathObject o1, PathObject o2) {
            double x1 = o1.getROI().getCentroidX();
            double x2 = o2.getROI().getCentroidX();
            // System.out.println(String.format("(%.2f, %.2f) vs (%.2f, %.2f)", o1.getROI().getCentroidX(), o1.getROI().getCentroidY(), o2.getROI().getCentroidX(), o2.getROI().getCentroidY()));				}
            return Double.compare(x1, x2);
        // if (x1 > x2)
        // return 1;
        // if (x2 < x1)
        // return -1;
        // System.out.println(x1 + " vs. " + x2);
        // System.out.println(String.format("(%.2f, %.2f) vs (%.2f, %.2f)", o1.getROI().getCentroidX(), o1.getROI().getCentroidY(), o2.getROI().getCentroidX(), o2.getROI().getCentroidY()));
        // return 0;
        // return (int)Math.signum(o1.getROI().getCentroidX() - o2.getROI().getCentroidX());
        }
    });
    // Create a LUT for distances - calculating exp every time is expensive
    double[] distanceWeights = new double[(int) (maxDist + .5) + 1];
    for (int i = 0; i < distanceWeights.length; i++) {
        distanceWeights[i] = Math.exp(-(i * i) / sigma2);
    }
    System.currentTimeMillis();
    float[] xCentroids = new float[nObjects];
    float[] yCentroids = new float[nObjects];
    PathClass[] pathClasses = new PathClass[nObjects];
    int[] nearbyDetectionCounts = new int[nObjects];
    float[][] measurementsWeighted = new float[nObjects][measurements.size()];
    float[][] measurementDenominators = new float[nObjects][measurements.size()];
    float[][] measurementValues = new float[nObjects][measurements.size()];
    for (int i = 0; i < nObjects; i++) {
        PathObject pathObject = pathObjects.get(i);
        if (withinClass)
            pathClasses[i] = pathObject.getPathClass() == null ? null : pathObject.getPathClass().getBaseClass();
        ROI roi = pathObject.getROI();
        xCentroids[i] = (float) roi.getCentroidX();
        yCentroids[i] = (float) roi.getCentroidY();
        MeasurementList measurementList = pathObject.getMeasurementList();
        int ind = 0;
        for (String name : measurements) {
            float value = (float) measurementList.getMeasurementValue(name);
            // Used to cache values
            measurementValues[i][ind] = value;
            // Based on distances and measurements
            measurementsWeighted[i][ind] = value;
            // Based on distances along
            measurementDenominators[i][ind] = 1;
            ind++;
        }
    }
    String prefix, postfix, denomName, countsName;
    // Use previous syntax for naming smoothed measurements
    if (useLegacyNames) {
        prefix = "";
        postfix = String.format(" - Smoothed (FWHM %s)", fwhmString);
        denomName = String.format("Smoothed denominator (local density, FWHM %s)", fwhmString);
        countsName = String.format("Nearby detection counts (radius %s)", fwhmString);
    } else {
        prefix = String.format("Smoothed: %s: ", fwhmString);
        postfix = "";
        // prefix + "Weighted density";
        denomName = null;
        countsName = prefix + "Nearby detection counts";
    // denomName = prefix + "Denominator (local density)";
    // countsName = prefix + "Nearby detection counts";
    }
    // Loop through objects, computing predominant class based on distance weighting
    for (int i = 0; i < nObjects; i++) {
        // Extract the current class index
        PathObject pathObject = pathObjects.get(i);
        PathClass pathClass = pathClasses[i];
        MeasurementList measurementList = pathObject.getMeasurementList();
        float[] mValues = measurementValues[i];
        float[] mWeighted = measurementsWeighted[i];
        float[] mDenominator = measurementDenominators[i];
        // Compute centroid distances
        double xi = xCentroids[i];
        double yi = yCentroids[i];
        for (int j = i + 1; j < nObjects; j++) {
            double xj = xCentroids[j];
            double yj = yCentroids[j];
            // Break early if we are already too far away
            if (Math.abs(xj - xi) > maxDist) {
                break;
            }
            double distSq = (xj - xi) * (xj - xi) + (yj - yi) * (yj - yi);
            // // Check if we are close enough to have an influence
            if (distSq > maxDistSq || Double.isNaN(distSq))
                continue;
            // Check if the class is ok, if check needed
            if (withinClass && pathClass != pathClasses[j])
                continue;
            // Update the counts, if close enough
            if (distSq < fwhmPixels2) {
                nearbyDetectionCounts[i]++;
                nearbyDetectionCounts[j]++;
            }
            // Update the class weights for both objects currently being tested
            // Compute weight based on centroid distances
            // double weight = Math.exp(-distSq/sigma2);
            // * pathObjects.get(j).getClassProbability();
            double weight = distanceWeights[(int) (Math.sqrt(distSq) + .5)];
            float[] temp = measurementValues[j];
            float[] tempWeighted = measurementsWeighted[j];
            float[] tempDenominator = measurementDenominators[j];
            for (int ind = 0; ind < measurements.size(); ind++) {
                float tempVal = temp[ind];
                if (Float.isNaN(tempVal))
                    continue;
                mWeighted[ind] += tempVal * weight;
                mDenominator[ind] += weight;
                float tempVal2 = mValues[ind];
                if (Float.isNaN(tempVal2))
                    continue;
                tempWeighted[ind] += tempVal2 * weight;
                tempDenominator[ind] += weight;
            }
        }
        // Store the measurements
        int ind = 0;
        float maxDenominator = Float.NEGATIVE_INFINITY;
        for (String name : measurements) {
            // if (name.contains(" - Smoothed (FWHM ") || name.startsWith("Smoothed denominator (local density, ") || name.startsWith("Nearby detection counts"))
            // continue;
            float denominator = mDenominator[ind];
            if (denominator > maxDenominator)
                maxDenominator = denominator;
            String nameToAdd = prefix + name + postfix;
            measurementList.putMeasurement(nameToAdd, mWeighted[ind] / denominator);
            // measurementsAdded.add(nameToAdd);
            // measurementList.putMeasurement(name + " - weighted sum", mWeighted[ind]); // TODO: Support optionally providing weighted sums
            // measurementList.addMeasurement(name + " - smoothed", mWeighted[ind] / mDenominator[ind]);
            ind++;
        }
        if (pathObject instanceof PathDetectionObject && denomName != null) {
            measurementList.putMeasurement(denomName, maxDenominator);
        // measurementsAdded.add(denomName);
        }
        if (pathObject instanceof PathDetectionObject && countsName != null) {
            measurementList.putMeasurement(countsName, nearbyDetectionCounts[i]);
        // measurementsAdded.add(countsName);
        }
        measurementList.close();
    }
    System.currentTimeMillis();
// return measurementsAdded;
}
Also used : PathDetectionObject(qupath.lib.objects.PathDetectionObject) MeasurementList(qupath.lib.measurements.MeasurementList) ROI(qupath.lib.roi.interfaces.ROI) PathClass(qupath.lib.objects.classes.PathClass) PathObject(qupath.lib.objects.PathObject)

Example 4 with PathDetectionObject

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

the class ObservableMeasurementTableData method updateMeasurementList.

/**
 * Update the entire measurement list for the current objects.
 * @see #setImageData(ImageData, Collection)
 */
public synchronized void updateMeasurementList() {
    // PathPrefs.setAllredMinPercentagePositive(0);
    builderMap.clear();
    // Add the image name
    if (!PathPrefs.maskImageNamesProperty().get())
        builderMap.put("Image", new ImageNameMeasurementBuilder(imageData));
    // Check if we have any annotations / TMA cores
    boolean containsDetections = false;
    boolean containsAnnotations = false;
    // boolean containsParentAnnotations = false;
    boolean containsTMACores = false;
    boolean containsRoot = false;
    List<PathObject> pathObjectListCopy = new ArrayList<>(list);
    for (PathObject temp : pathObjectListCopy) {
        if (temp instanceof PathAnnotationObject) {
            // if (temp.hasChildren())
            // containsParentAnnotations = true;
            containsAnnotations = true;
        } else if (temp instanceof TMACoreObject) {
            containsTMACores = true;
        } else if (temp instanceof PathDetectionObject) {
            containsDetections = true;
        } else if (temp.isRootObject())
            containsRoot = true;
    }
    boolean detectionsAnywhere = imageData == null ? containsDetections : !imageData.getHierarchy().getDetectionObjects().isEmpty();
    // Include the object displayed name
    // if (containsDetections || containsAnnotations || containsTMACores)
    builderMap.put("Name", new ObjectNameMeasurementBuilder());
    // Include the class
    if (containsAnnotations || containsDetections) {
        builderMap.put("Class", new PathClassMeasurementBuilder());
        // Get the name of the containing TMA core if we have anything other than cores
        if (imageData != null && imageData.getHierarchy().getTMAGrid() != null) {
            builderMap.put("TMA core", new TMACoreNameMeasurementBuilder());
        }
        // Get the name of the first parent object
        builderMap.put("Parent", new ParentNameMeasurementBuilder());
    }
    // Include the TMA missing status, if appropriate
    if (containsTMACores) {
        builderMap.put("Missing", new MissingTMACoreMeasurementBuilder());
    }
    if (containsAnnotations || containsDetections) {
        builderMap.put("ROI", new ROINameMeasurementBuilder());
    }
    // Add centroids
    if (containsAnnotations || containsDetections || containsTMACores) {
        // ROICentroidMeasurementBuilder builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.X);
        // builderMap.put("Centroid X", builder);
        // builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.Y);
        // builderMap.put("Centroid Y", builder);
        ROICentroidMeasurementBuilder builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.X);
        builderMap.put(builder.getName(), builder);
        builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.Y);
        builderMap.put(builder.getName(), builder);
    }
    // If we have metadata, store it
    Set<String> metadataNames = new LinkedHashSet<>();
    metadataNames.addAll(builderMap.keySet());
    for (PathObject pathObject : pathObjectListCopy) {
        if (pathObject instanceof MetadataStore) {
            metadataNames.addAll(((MetadataStore) pathObject).getMetadataKeys());
        }
    }
    // Ensure we have suitable builders
    for (String name : metadataNames) {
        if (!builderMap.containsKey(name))
            builderMap.put(name, new StringMetadataMeasurementBuilder(name));
    }
    // Get all the 'built-in' feature measurements, stored in the measurement list
    Collection<String> features = PathClassifierTools.getAvailableFeatures(pathObjectListCopy);
    // Add derived measurements if we don't have only detections
    if (containsAnnotations || containsTMACores || containsRoot) {
        if (detectionsAnywhere) {
            var builder = new ObjectTypeCountMeasurementBuilder(PathDetectionObject.class);
            builderMap.put(builder.getName(), builder);
            features.add(builder.getName());
        }
        // Here, we allow TMA cores to act like annotations
        manager = new DerivedMeasurementManager(getImageData(), containsAnnotations || containsTMACores);
        for (MeasurementBuilder<?> builder2 : manager.getMeasurementBuilders()) {
            builderMap.put(builder2.getName(), builder2);
            features.add(builder2.getName());
        }
    }
    // If we have an annotation, add shape features
    if (containsAnnotations) {
        boolean anyPoints = false;
        boolean anyAreas = false;
        boolean anyLines = false;
        @SuppressWarnings("unused") boolean anyPolygons = false;
        for (PathObject pathObject : pathObjectListCopy) {
            if (!pathObject.isAnnotation())
                continue;
            ROI roi = pathObject.getROI();
            if (roi == null)
                continue;
            if (roi.isPoint())
                anyPoints = true;
            if (roi.isArea())
                anyAreas = true;
            if (roi.isLine())
                anyLines = true;
            if (pathObject.getROI() instanceof PolygonROI)
                anyPolygons = true;
        }
        // Add point count, if needed
        if (anyPoints) {
            MeasurementBuilder<?> builder = new NumPointsMeasurementBuilder();
            builderMap.put(builder.getName(), builder);
            features.add(builder.getName());
        }
        // Add spatial measurements, if needed
        if (anyAreas) {
            MeasurementBuilder<?> builder = new AreaMeasurementBuilder(imageData);
            builderMap.put(builder.getName(), builder);
            features.add(builder.getName());
            builder = new PerimeterMeasurementBuilder(imageData);
            builderMap.put(builder.getName(), builder);
            features.add(builder.getName());
        }
        if (anyLines) {
            MeasurementBuilder<?> builder = new LineLengthMeasurementBuilder(imageData);
            builderMap.put(builder.getName(), builder);
            features.add(builder.getName());
        }
    // if (anyPolygons) {
    // MeasurementBuilder<?> builder = new MaxDiameterMeasurementBuilder(imageData);
    // builderMap.put(builder.getName(), builder);
    // features.add(builder.getName());
    // 
    // builder = new MinDiameterMeasurementBuilder(imageData);
    // builderMap.put(builder.getName(), builder);
    // features.add(builder.getName());
    // }
    }
    if (containsAnnotations || containsTMACores || containsRoot) {
        var pixelClassifier = getPixelLayer(imageData);
        if (pixelClassifier instanceof ImageServer<?>) {
            ImageServer<BufferedImage> server = (ImageServer<BufferedImage>) pixelClassifier;
            if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION || server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.PROBABILITY) {
                var pixelManager = new PixelClassificationMeasurementManager(server);
                for (String name : pixelManager.getMeasurementNames()) {
                    // String nameLive = name + " (live)";
                    String nameLive = "(Live) " + name;
                    builderMap.put(nameLive, new PixelClassifierMeasurementBuilder(pixelManager, name));
                    features.add(nameLive);
                }
            }
        }
    }
    // Update all the lists, if necessary
    boolean changes = false;
    if (metadataNames.size() != metadataList.size() || !metadataNames.containsAll(metadataList)) {
        changes = metadataList.setAll(metadataNames);
    }
    if (features.size() != measurementList.size() || !features.containsAll(measurementList))
        changes = measurementList.setAll(features);
    if (changes) {
        if (metadataList.isEmpty())
            fullList.setAll(measurementList);
        else {
            fullList.setAll(metadataList);
            fullList.addAll(measurementList);
        }
    }
}
Also used : LinkedHashSet(java.util.LinkedHashSet) ArrayList(java.util.ArrayList) BufferedImage(java.awt.image.BufferedImage) PolygonROI(qupath.lib.roi.PolygonROI) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) ImageServer(qupath.lib.images.servers.ImageServer) PathDetectionObject(qupath.lib.objects.PathDetectionObject) TMACoreObject(qupath.lib.objects.TMACoreObject) PolygonROI(qupath.lib.roi.PolygonROI) ROI(qupath.lib.roi.interfaces.ROI) MetadataStore(qupath.lib.objects.MetadataStore) PathObject(qupath.lib.objects.PathObject) PixelClassificationMeasurementManager(qupath.opencv.ml.pixel.PixelClassificationMeasurementManager)

Example 5 with PathDetectionObject

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

the class SmoothFeaturesPlugin method addRunnableTasks.

@Override
protected void addRunnableTasks(final ImageData<T> imageData, final PathObject parentObject, List<Runnable> tasks) {
    double fwhm;
    ImageServer<T> server = imageData.getServer();
    String fwhmStringTemp;
    PixelCalibration cal = server == null ? null : server.getPixelCalibration();
    if (cal != null && cal.hasPixelSizeMicrons()) {
        fwhm = getParameterList(imageData).getDoubleParameterValue("fwhmMicrons");
        fwhmStringTemp = GeneralTools.createFormatter(2).format(fwhm) + " " + GeneralTools.micrometerSymbol();
        fwhm /= cal.getAveragedPixelSizeMicrons();
    // params.addDoubleParameter("fwhmPixels", "Radius (FWHM)", fwhm, "pixels"); // Set the FWHM in pixels too
    } else {
        fwhm = getParameterList(imageData).getDoubleParameterValue("fwhmPixels");
        fwhmStringTemp = GeneralTools.createFormatter(2).format(fwhm) + " px";
    }
    // sigma = 50;
    final String fwhmString = fwhmStringTemp;
    final double fwhmPixels = fwhm;
    final boolean withinClass = params.getBooleanParameterValue("smoothWithinClasses");
    final boolean useLegacyNames = params.containsKey("useLegacyNames") && Boolean.TRUE.equals(params.getBooleanParameterValue("useLegacyNames"));
    tasks.add(new Runnable() {

        @Override
        public void run() {
            try {
                if (!parentObject.hasChildren())
                    return;
                // System.out.println("Smoothing with FWHM " +fwhmPixels);
                // TODO: MAKE A MORE ELEGANT LIST!!!!
                List<PathObject> pathObjects = PathObjectTools.getFlattenedObjectList(parentObject, null, false);
                Iterator<PathObject> iterObjects = pathObjects.iterator();
                while (iterObjects.hasNext()) {
                    PathObject temp = iterObjects.next();
                    if (!(temp instanceof PathDetectionObject || temp instanceof PathTileObject))
                        iterObjects.remove();
                }
                if (pathObjects.isEmpty())
                    return;
                // TODO: ACCESS & USE THE CLASSIFIER DATA!!!!
                List<String> measurements = new ArrayList<>(PathClassifierTools.getAvailableFeatures(pathObjects));
                Iterator<String> iter = measurements.iterator();
                while (iter.hasNext()) {
                    String name = iter.next().toLowerCase();
                    if (name.endsWith("smoothed") || name.startsWith("smoothed") || name.contains(" - smoothed (fwhm ") || name.startsWith("smoothed denominator (local density, ") || name.startsWith("nearby detection counts"))
                        iter.remove();
                }
                logger.debug(String.format("Smooth features: %s (FWHM: %.2f px)", parentObject.getDisplayedName(), fwhmPixels));
                smoothMeasurements(pathObjects, measurements, fwhmPixels, fwhmString, withinClass, useLegacyNames);
            // // REMOVE - the purpose was to test a 'difference of Gaussians' type of thing
            // List<String> namesAdded1 = new ArrayList<>(smoothMeasurements(pathObjects, measurements, fwhmPixels));
            // List<String> namesAdded2 = new ArrayList<>(smoothMeasurements(pathObjects, measurements, fwhmPixels * 2));
            // for (PathObject pathObject : pathObjects) {
            // MeasurementList ml = pathObject.getMeasurementList();
            // ml.ensureListOpen();
            // for (int i = 0; i < namesAdded1.size(); i++) {
            // String name1 = namesAdded1.get(i);
            // String name2 = namesAdded2.get(i);
            // double m1 = ml.getMeasurementValue(name1);
            // double m2 = ml.getMeasurementValue(name2);
            // ml.addMeasurement(name1 + " - " + name2, m1 - m2);
            // }
            // ml.closeList();
            // }
            } catch (Exception e) {
                e.printStackTrace();
                throw (e);
            }
        }
    });
}
Also used : PathDetectionObject(qupath.lib.objects.PathDetectionObject) PixelCalibration(qupath.lib.images.servers.PixelCalibration) PathTileObject(qupath.lib.objects.PathTileObject) PathObject(qupath.lib.objects.PathObject) Iterator(java.util.Iterator) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) ParameterList(qupath.lib.plugins.parameters.ParameterList) List(java.util.List)

Aggregations

PathDetectionObject (qupath.lib.objects.PathDetectionObject)5 PathObject (qupath.lib.objects.PathObject)5 ROI (qupath.lib.roi.interfaces.ROI)4 Color (java.awt.Color)2 ArrayList (java.util.ArrayList)2 MeasurementList (qupath.lib.measurements.MeasurementList)2 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)2 PathCellObject (qupath.lib.objects.PathCellObject)2 PathTileObject (qupath.lib.objects.PathTileObject)2 TMACoreObject (qupath.lib.objects.TMACoreObject)2 PathClass (qupath.lib.objects.classes.PathClass)2 Overlay (ij.gui.Overlay)1 Roi (ij.gui.Roi)1 BasicStroke (java.awt.BasicStroke)1 Shape (java.awt.Shape)1 Stroke (java.awt.Stroke)1 RectangularShape (java.awt.geom.RectangularShape)1 BufferedImage (java.awt.image.BufferedImage)1 Iterator (java.util.Iterator)1 LinkedHashSet (java.util.LinkedHashSet)1