Search in sources :

Example 76 with PathObject

use of qupath.lib.objects.PathObject 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 77 with PathObject

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

the class TileClassificationsToAnnotationsPlugin method getParentObjects.

@Override
protected Collection<PathObject> getParentObjects(final PluginRunner<T> runner) {
    PathObjectHierarchy hierarchy = runner.getImageData().getHierarchy();
    List<PathObject> parents = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
    // Deal with nested objects - the code is clumsy, but the idea is to take the highest
    // object in the hierarchy in instances where tiles are nested within other objects
    List<PathObject> tempList = new ArrayList<>(parents);
    for (PathObject temp : tempList) {
        Iterator<PathObject> iter = parents.iterator();
        while (iter.hasNext()) {
            if (PathObjectTools.isAncestor(iter.next(), temp))
                iter.remove();
        }
    }
    return parents;
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList)

Example 78 with PathObject

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

the class RefineAnnotationsPlugin method getTasks.

@Override
protected Collection<Runnable> getTasks(final PluginRunner<T> runner) {
    Collection<? extends PathObject> parentObjects = getParentObjects(runner);
    if (parentObjects == null || parentObjects.isEmpty())
        return Collections.emptyList();
    // Add a single task, to avoid multithreading - which may complicate setting parents
    List<Runnable> tasks = new ArrayList<>(1);
    PathObjectHierarchy hierarchy = getHierarchy(runner);
    double minFragmentSize;
    double maxHoleSize, maxHoleSizeTemp;
    ImageServer<T> server = getServer(runner);
    PixelCalibration cal = server.getPixelCalibration();
    if (cal.hasPixelSizeMicrons()) {
        double pixelAreaMicrons = cal.getPixelWidthMicrons() * cal.getPixelHeightMicrons();
        minFragmentSize = params.getDoubleParameterValue("minFragmentSizeMicrons") / pixelAreaMicrons;
        maxHoleSizeTemp = params.getDoubleParameterValue("maxHoleSizeMicrons") / pixelAreaMicrons;
    } else {
        minFragmentSize = params.getDoubleParameterValue("minFragmentSizePixels");
        maxHoleSizeTemp = params.getDoubleParameterValue("maxHoleSizePixels");
    }
    // Handle negative values
    if (maxHoleSizeTemp < 0)
        maxHoleSize = Double.POSITIVE_INFINITY;
    else
        maxHoleSize = maxHoleSizeTemp;
    // Want to reset selection
    PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
    Collection<PathObject> previousSelection = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
    tasks.add(() -> {
        List<PathObject> toRemove = new ArrayList<>();
        Map<PathROIObject, ROI> toUpdate = new HashMap<>();
        for (PathObject pathObject : parentObjects) {
            ROI roiOrig = pathObject.getROI();
            if (roiOrig == null || !roiOrig.isArea())
                continue;
            ROI roiUpdated = RoiTools.removeSmallPieces(roiOrig, minFragmentSize, maxHoleSize);
            if (roiUpdated == null || roiUpdated.isEmpty())
                toRemove.add(pathObject);
            else if (roiOrig != roiUpdated && pathObject instanceof PathROIObject) {
                toUpdate.put((PathROIObject) pathObject, roiUpdated);
            }
        }
        if (toRemove.isEmpty() && toUpdate.isEmpty())
            return;
        hierarchy.getSelectionModel().clearSelection();
        if (!toRemove.isEmpty())
            hierarchy.removeObjects(toRemove, true);
        if (!toUpdate.isEmpty()) {
            hierarchy.removeObjects(toUpdate.keySet(), true);
            toUpdate.forEach((p, r) -> p.setROI(r));
            hierarchy.addPathObjects(toUpdate.keySet());
        }
        hierarchy.getSelectionModel().selectObjects(previousSelection);
        hierarchy.getSelectionModel().setSelectedObject(selected, true);
    });
    return tasks;
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) PixelCalibration(qupath.lib.images.servers.PixelCalibration) PathROIObject(qupath.lib.objects.PathROIObject) ROI(qupath.lib.roi.interfaces.ROI) PathObject(qupath.lib.objects.PathObject)

Example 79 with PathObject

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

the class MeasurementMapPane method updateMeasurements.

/**
 * Update the measurements according to the current image
 */
public void updateMeasurements() {
    QuPathViewer viewer = qupath.getViewer();
    PathObjectHierarchy hierarchy = viewer.getHierarchy();
    if (hierarchy == null) {
        baseList.clear();
        return;
    }
    Collection<PathObject> pathObjects = hierarchy.getDetectionObjects();
    Set<String> measurements = PathClassifierTools.getAvailableFeatures(pathObjects);
    for (PathObject pathObject : pathObjects) {
        if (!Double.isNaN(pathObject.getClassProbability())) {
            measurements.add("Class probability");
            break;
        }
    }
    // Apply any changes
    baseList.setAll(measurements);
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathObject(qupath.lib.objects.PathObject) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer)

Example 80 with PathObject

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

the class PathObjectHierarchyView method synchronizeSelectionModelToTree.

/**
 * Ensure that the hierarchy selection model matches the selection within the TreeView.
 * @param change
 */
private void synchronizeSelectionModelToTree(final ListChangeListener.Change<? extends TreeItem<PathObject>> change) {
    if (synchronizingTreeToModel)
        return;
    PathObjectSelectionModel model = getHierarchySelectionModel();
    if (model == null) {
        return;
    }
    boolean wasSynchronizingToTree = synchronizingModelToTree;
    try {
        synchronizingModelToTree = true;
        // Check - was anything removed?
        boolean removed = false;
        if (change != null) {
            while (change.next()) removed = removed | change.wasRemoved();
        }
        MultipleSelectionModel<TreeItem<PathObject>> treeModel = treeView.getSelectionModel();
        List<TreeItem<PathObject>> selectedItems = treeModel.getSelectedItems();
        // If we just have no selected items, and something was removed, then clear the selection
        if (selectedItems.isEmpty() && removed) {
            model.clearSelection();
            return;
        }
        // if (selectedItems.size() == 1 && removed) {
        if (selectedItems.size() == 1) {
            model.setSelectedObject(selectedItems.get(0).getValue(), false);
            return;
        }
        // If we have multiple selected items, we need to ensure that everything in the tree matches with everything in the selection model
        Set<PathObject> toSelect = treeView.getSelectionModel().getSelectedItems().stream().map(t -> t.getValue()).collect(Collectors.toSet());
        TreeItem<PathObject> mainSelection = treeView.getSelectionModel().getSelectedItem();
        PathObject primary = mainSelection == null ? null : mainSelection.getValue();
        model.setSelectedObjects(toSelect, primary);
    } finally {
        synchronizingModelToTree = wasSynchronizingToTree;
    }
}
Also used : EventHandler(javafx.event.EventHandler) java.util(java.util) javafx.scene.control(javafx.scene.control) MouseEvent(javafx.scene.input.MouseEvent) BreadCrumbBar(org.controlsfx.control.BreadCrumbBar) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathRootObject(qupath.lib.objects.PathRootObject) ListChangeListener(javafx.collections.ListChangeListener) PathObjectHierarchyEvent(qupath.lib.objects.hierarchy.events.PathObjectHierarchyEvent) Callback(javafx.util.Callback) QuPathGUI(qupath.lib.gui.QuPathGUI) Pane(javafx.scene.layout.Pane) ImageData(qupath.lib.images.ImageData) ObjectProperty(javafx.beans.property.ObjectProperty) BufferedImage(java.awt.image.BufferedImage) IconFactory(qupath.lib.gui.tools.IconFactory) PathObjectSelectionListener(qupath.lib.objects.hierarchy.events.PathObjectSelectionListener) DefaultPathObjectComparator(qupath.lib.objects.DefaultPathObjectComparator) PathObjectSelectionModel(qupath.lib.objects.hierarchy.events.PathObjectSelectionModel) Collectors(java.util.stream.Collectors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) Platform(javafx.application.Platform) BooleanProperty(javafx.beans.property.BooleanProperty) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) ObservableValue(javafx.beans.value.ObservableValue) PathObjectHierarchyListener(qupath.lib.objects.hierarchy.events.PathObjectHierarchyListener) ObservableList(javafx.collections.ObservableList) BorderPane(javafx.scene.layout.BorderPane) ChangeListener(javafx.beans.value.ChangeListener) PathPrefs(qupath.lib.gui.prefs.PathPrefs) PathObject(qupath.lib.objects.PathObject) PathObjectSelectionModel(qupath.lib.objects.hierarchy.events.PathObjectSelectionModel)

Aggregations

PathObject (qupath.lib.objects.PathObject)182 ArrayList (java.util.ArrayList)84 ROI (qupath.lib.roi.interfaces.ROI)74 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)61 List (java.util.List)48 BufferedImage (java.awt.image.BufferedImage)37 IOException (java.io.IOException)37 PathClass (qupath.lib.objects.classes.PathClass)37 Collectors (java.util.stream.Collectors)35 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)34 Map (java.util.Map)33 Logger (org.slf4j.Logger)33 LoggerFactory (org.slf4j.LoggerFactory)33 ImageData (qupath.lib.images.ImageData)31 TMACoreObject (qupath.lib.objects.TMACoreObject)31 Collection (java.util.Collection)29 Collections (java.util.Collections)29 HashMap (java.util.HashMap)28 PathObjectTools (qupath.lib.objects.PathObjectTools)26 Arrays (java.util.Arrays)25