Search in sources :

Example 1 with PathObject

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

the class DelaunayTriangulation method updateNodeMap.

void updateNodeMap(Subdiv2D subdiv, final double pixelWidth, final double pixelHeight) {
    if (subdiv == null)
        return;
    int[] firstEdgeArray = new int[1];
    // double distanceThreshold = 0;
    boolean ignoreDistance = Double.isNaN(distanceThreshold) || Double.isInfinite(distanceThreshold) || distanceThreshold <= 0;
    DelaunayNodeFactory factory = new DelaunayNodeFactory(pixelWidth, pixelHeight);
    nodeMap = new HashMap<>(vertexMap.size(), 1f);
    for (Entry<Integer, PathObject> entry : vertexMap.entrySet()) {
        int v = entry.getKey();
        PathObject pathObject = entry.getValue();
        PathClass pathClass = pathObject.getPathClass() == null ? null : pathObject.getPathClass().getBaseClass();
        // // TODO: CHECK INTENSITY DIFFERENT THRESHOLD
        // String measurementName = "Nucleus: DAB OD mean";
        // double measurementDiffThreshold = 0.1;
        // double od = pathObject.getMeasurementList().getMeasurementValue(measurementName);
        subdiv.getVertex(v, firstEdgeArray);
        int firstEdge = firstEdgeArray[0];
        int edge = firstEdge;
        DelaunayNode node = factory.getNode(pathObject);
        while (true) {
            int edgeDest = subdiv.edgeDst(edge);
            PathObject destination = vertexMap.get(edgeDest);
            if (destination == null)
                break;
            boolean distanceOK = ignoreDistance || distance(getROI(pathObject), getROI(destination)) < distanceThreshold;
            boolean classOK = !limitByClass || pathClass == destination.getPathClass() || (destination.getPathClass() != null && destination.getPathClass().getBaseClass() == pathClass);
            if (distanceOK && classOK) {
                // Intensity test (works, but currently commented out)
                // if (Math.abs(od - destination.getMeasurementList().getMeasurementValue(measurementName)) < measurementDiffThreshold)
                DelaunayNode destinationNode = factory.getNode(destination);
                node.addEdge(destinationNode);
                destinationNode.addEdge(node);
            }
            // Unused code exploring how a similarity test could be included
            // if (ignoreDistance || distance(pathObject.getROI(), destination.getROI()) < distanceThreshold) {
            // MeasurementList m1 = pathObject.getMeasurementList();
            // MeasurementList m2 = destination.getMeasurementList();
            // double d2 = 0;
            // for (String name : new String[]{"Nucleus: Area", "Nucleus: DAB OD mean", "Nucleus: Eccentricity"}) {
            // double t1 = m1.getMeasurementValue(name);
            // double t2 = m2.getMeasurementValue(name);
            // double temp = ((t1 - t2) / (t1 + t2)) * 2;
            // d2 += temp*temp;
            // }
            // if (d2 < 1)
            // //					System.out.println(d2);
            // node.addEdge(factory.getNode(destination));
            // }
            edge = subdiv.getEdge(edge, Subdiv2D.NEXT_AROUND_ORG);
            if (edge == firstEdge)
                break;
        }
        Object previous = nodeMap.put(pathObject, node);
        assert previous == null;
    }
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) PathObject(qupath.lib.objects.PathObject) PathObject(qupath.lib.objects.PathObject) PathCellObject(qupath.lib.objects.PathCellObject)

Example 2 with PathObject

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

the class DelaunayTriangulation method getConnectedClusters.

/**
 * Get a list of PathObjects that are connected to each other in this triangulation.
 *
 * Warning: This list is recomputed on every call, therefore references should be cached by the caller if necessary
 * to avoid too much recomputation.
 *
 * @return
 */
public List<Set<PathObject>> getConnectedClusters() {
    if (nodeMap == null || nodeMap.isEmpty())
        return Collections.emptyList();
    // Compute distinct clusters
    List<PathObject> toProcess = new ArrayList<>(nodeMap.keySet());
    List<Set<PathObject>> clusters = new ArrayList<>();
    while (!toProcess.isEmpty()) {
        Set<PathObject> inCluster = new HashSet<>();
        Deque<PathObject> toCheck = new ArrayDeque<>();
        PathObject next = toProcess.remove(toProcess.size() - 1);
        toCheck.add(next);
        while (!toCheck.isEmpty()) {
            next = toCheck.pop();
            if (inCluster.add(next)) {
                toCheck.addAll(getConnectedObjects(next));
            }
        }
        // Avoid recursive call in case of stack overflow
        // getConnectedNodesRecursive(next, inCluster);
        toProcess.removeAll(inCluster);
        clusters.add(inCluster);
    }
    return clusters;
}
Also used : PathObject(qupath.lib.objects.PathObject) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet) Set(java.util.Set) ArrayList(java.util.ArrayList) ArrayDeque(java.util.ArrayDeque) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

Example 3 with PathObject

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

the class DelaunayTriangulation method addClusterMeasurements.

/**
 * Compute mean measurements from clustering all connected objects.
 */
public void addClusterMeasurements() {
    if (nodeMap == null || nodeMap.isEmpty())
        return;
    List<Set<PathObject>> clusters = getConnectedClusters();
    String key = "Cluster ";
    List<String> measurementNames = new ArrayList<>();
    for (String s : PathClassifierTools.getAvailableFeatures(nodeMap.keySet())) {
        if (!s.startsWith(key))
            measurementNames.add(s);
    }
    RunningStatistics[] averagedMeasurements = new RunningStatistics[measurementNames.size()];
    Set<String> missing = new LinkedHashSet<>();
    for (Set<PathObject> cluster : clusters) {
        for (int i = 0; i < averagedMeasurements.length; i++) averagedMeasurements[i] = new RunningStatistics();
        // Arrays.fill(averagedMeasurements, 0);
        int n = cluster.size();
        for (PathObject pathObject : cluster) {
            MeasurementList ml = pathObject.getMeasurementList();
            for (int i = 0; i < measurementNames.size(); i++) {
                String name = measurementNames.get(i);
                double val = ml.getMeasurementValue(name);
                if (Double.isFinite(val)) {
                    averagedMeasurements[i].addValue(val);
                } else
                    missing.add(name);
            }
        }
        for (PathObject pathObject : cluster) {
            MeasurementList ml = pathObject.getMeasurementList();
            for (int i = 0; i < measurementNames.size(); i++) {
                ml.putMeasurement(key + "mean: " + measurementNames.get(i), averagedMeasurements[i].getMean());
            }
            ml.putMeasurement(key + "size", n);
            ml.close();
        }
    }
    if (!missing.isEmpty()) {
        logger.warn("Some objects have missing measurements! Statistics will calculated only for objects with measurements available.");
        logger.warn("Missing measurements: {}", missing);
    // System.err.println("Missing measurements will be ignored! " + nMissing);
    }
}
Also used : LinkedHashSet(java.util.LinkedHashSet) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet) Set(java.util.Set) MeasurementList(qupath.lib.measurements.MeasurementList) ArrayList(java.util.ArrayList) RunningStatistics(qupath.lib.analysis.stats.RunningStatistics) PathObject(qupath.lib.objects.PathObject)

Example 4 with PathObject

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

the class DelaunayTriangulation method computeDelaunay.

void computeDelaunay(final List<PathObject> pathObjectList, final double pixelWidth, final double pixelHeight) {
    if (pathObjectList.size() <= 2)
        return;
    this.vertexMap = new HashMap<>(pathObjectList.size(), 1f);
    // Extract the centroids
    double minX = Double.POSITIVE_INFINITY;
    double minY = Double.POSITIVE_INFINITY;
    double maxX = Double.NEGATIVE_INFINITY;
    double maxY = Double.NEGATIVE_INFINITY;
    List<Point2f> centroids = new ArrayList<>(pathObjectList.size());
    for (PathObject pathObject : pathObjectList) {
        ROI pathROI = null;
        // First, try to get a nucleus ROI if we have a cell - otherwise just get the normal ROI
        pathROI = getROI(pathObject);
        // Check if we have a ROI at all
        if (pathROI == null) {
            centroids.add(null);
            continue;
        }
        double x = pathROI.getCentroidX();
        double y = pathROI.getCentroidY();
        if (Double.isNaN(x) || Double.isNaN(y)) {
            centroids.add(null);
            continue;
        }
        if (x < minX)
            minX = x;
        else if (x > maxX)
            maxX = x;
        if (y < minY)
            minY = y;
        else if (y > maxY)
            maxY = y;
        centroids.add(new Point2f((float) x, (float) y));
    }
    // Create Delaunay triangulation, updating vertex map
    Subdiv2D subdiv = new Subdiv2D();
    Rect bounds = new Rect((int) minX - 1, (int) minY - 1, (int) (maxX - minX) + 100, (int) (maxY - minY) + 100);
    subdiv.initDelaunay(bounds);
    for (int i = 0; i < centroids.size(); i++) {
        Point2f p = centroids.get(i);
        if (p == null)
            continue;
        int v = subdiv.insert(p);
        vertexMap.put(v, pathObjectList.get(i));
    }
    updateNodeMap(subdiv, pixelWidth, pixelHeight);
// // Connect only the closest paired nodes
// Map<DelaunayNode, Double> medianDistances = new HashMap<>();
// for (DelaunayNode node : nodeMap.values()) {
// medianDistances.put(node, node.medianDistance());
// }
// 
// for (DelaunayNode node : nodeMap.values()) {
// if (node.nNeighbors() <= 2)
// continue;
// double distance = medianDistances.get(node);
// Iterator<DelaunayNode> iter = node.nodeList.iterator();
// while (iter.hasNext()) {
// DelaunayNode node2 = iter.next();
// if (distance(node, node2) >= distance) {
// node2.nodeList.remove(node);
// iter.remove();
// }
// }
// }
// // Optionally require a minimum number of connected nodes
// List<DelaunayNode> toRemove = new ArrayList<>();
// for (DelaunayNode node : nodeMap.values()) {
// if (node.nNeighbors() <= 2) {
// toRemove.add(node);
// }
// }
// for (DelaunayNode node : toRemove) {
// for (DelaunayNode node2 : node.nodeList)
// node2.nodeList.remove(node);
// node.nodeList.clear();
// }
// for (DelaunayNode node : nodeMap.values()) {
// node.ensureDistancesUpdated();
// node.ensureTrianglesCalculated();
// }
}
Also used : Rect(org.bytedeco.opencv.opencv_core.Rect) Point2f(org.bytedeco.opencv.opencv_core.Point2f) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) Subdiv2D(org.bytedeco.opencv.opencv_imgproc.Subdiv2D) ROI(qupath.lib.roi.interfaces.ROI)

Example 5 with PathObject

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

the class DelaunayTriangulation method getConnectedNodes.

/**
 * Get connected nodes.  Returned as a list where pairs are consecutive, i.e.
 * get(i) links to get(i+1)
 * (although get(i+1) doesn't necessarily link to get(i+2)...)
 *
 * @param pathObjects
 * @param connections
 * @return
 */
@Deprecated
public Collection<double[]> getConnectedNodes(final Collection<PathObject> pathObjects, Collection<double[]> connections) {
    if (connections == null)
        connections = new HashSet<>();
    if (nodeMap == null || pathObjects.isEmpty())
        return connections;
    for (PathObject temp : pathObjects) {
        DelaunayNode node = nodeMap.get(temp);
        if (node == null)
            continue;
        ROI roi = getROI(temp);
        double x1 = roi.getCentroidX();
        double y1 = roi.getCentroidY();
        for (DelaunayNode node2 : node.nodeList) {
            ROI roi2 = getROI(node2.getPathObject());
            double x2 = roi2.getCentroidX();
            double y2 = roi2.getCentroidY();
            if (x1 < x2 || (x1 == x2 && y1 <= y2))
                connections.add(new double[] { x1, y1, x2, y2 });
            else
                connections.add(new double[] { x2, y2, x1, y1 });
        }
    }
    return connections;
}
Also used : PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

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