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;
}
}
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;
}
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);
}
}
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();
// }
}
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;
}
Aggregations