use of qupath.lib.roi.interfaces.ROI 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.roi.interfaces.ROI 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;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class SplitAnnotationsPlugin 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);
// Want to reset selection
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
tasks.add(() -> {
/*
* Resolving the hierarchy with many objects can be very slow.
* Here, we take an object, split it and then add it below the original object in the hierarchy.
* We then just need to remove the originals, allowing the newly-added objects to have their
* parents reassigned.
*/
List<PathObject> toAdd = new ArrayList<>();
List<PathObject> toRemove = new ArrayList<>();
List<PathObject> localSplit = new ArrayList<>();
Set<PathObject> toSelect = new HashSet<>();
for (PathObject pathObject : parentObjects) {
localSplit.clear();
ROI roiOrig = pathObject.getROI();
if (roiOrig == null) {
toSelect.add(pathObject);
continue;
}
var splitROIs = RoiTools.splitROI(roiOrig);
if (splitROIs.size() == 1)
continue;
toRemove.add(pathObject);
for (var r : splitROIs) {
var annotation = PathObjects.createAnnotationObject(r, pathObject.getPathClass());
annotation.setLocked(pathObject.isLocked());
localSplit.add(annotation);
}
if (pathObject.hasChildren()) {
for (var temp : localSplit) hierarchy.addPathObjectBelowParent(pathObject, temp, false);
} else
pathObject.addPathObjects(localSplit);
toAdd.addAll(localSplit);
}
if (toAdd.isEmpty() && toRemove.isEmpty())
return;
hierarchy.getSelectionModel().clearSelection();
toSelect.addAll(toAdd);
hierarchy.removeObjects(toRemove, true);
// hierarchy.addPathObjects(toAdd, false);
hierarchy.getSelectionModel().selectObjects(toSelect);
if (toSelect.contains(selected))
hierarchy.getSelectionModel().setSelectedObject(selected, true);
});
return tasks;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class RigidObjectEditorCommand method createTransformedObject.
PathObject createTransformedObject() {
ROI roi = originalObject.getROI();
ROI shape = GeometryTools.geometryToROI(transformer.getTransformedShape(), roi.getImagePlane());
return PathObjects.createAnnotationObject(shape, originalObject.getPathClass());
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class RigidObjectEditorCommand method run.
@Override
public void run() {
// Object is already being edited
if (this.originalObject != null) {
// viewer.setSelectedObject(tempObject);
return;
}
// Get the selected object
viewer = qupath.getViewer();
PathObject pathObject = getSelectedObject(viewer);
if (pathObject == null || !(pathObject.isAnnotation() || pathObject.isTMACore())) {
Dialogs.showErrorNotification("Rotate annotation", "No annotation selected!");
return;
}
if (pathObject.isLocked()) {
Dialogs.showErrorNotification("Rotate annotation", "Selected annotation is locked!");
return;
}
// if (pathObject.getROI().isPoint()) {
// Dialogs.showErrorNotification("Rotate annotation", "Point annotations cannot be rotated, sorry!");
// return;
// }
ImageRegion bounds = viewer.getServerBounds();
if (pathObject.isTMACore()) {
for (PathObject child : pathObject.getChildObjectsAsArray()) {
if (isSuitableAnnotation(child)) {
originalObjectROIs.put(child, child.getROI());
}
}
if (originalObjectROIs.isEmpty()) {
Dialogs.showErrorMessage("Rigid refinement problem", "TMA core must contain empty annotations objects for rigid refinement");
return;
}
}
originalObjectROIs.put(pathObject, pathObject.getROI());
this.originalObject = pathObject;
viewer.setActiveTool(null);
qupath.setToolSwitchingEnabled(false);
viewer.addViewerListener(this);
viewer.getView().addEventHandler(MouseEvent.ANY, mouseListener);
// // Remove selected object & create an overlay showing the currently-being-edited version
// viewer.getHierarchy().removeObject(originalObject, true, true);
transformer = new RoiAffineTransformer(bounds, originalObject.getROI());
// editingROI = new RotatedROI((PathArea)originalObject.getROI());
// editingROI.setAngle(Math.PI/3);
overlay = new AffineEditOverlay(viewer.getOverlayOptions());
viewer.getCustomOverlayLayers().add(overlay);
PathPrefs.paintSelectedBoundsProperty().set(false);
// Create & show temporary object
for (Entry<PathObject, ROI> entry : originalObjectROIs.entrySet()) ((PathROIObject) entry.getKey()).setROI(transformer.getTransformedROI(entry.getValue(), false));
// Reset any existing editor (and its visible handles)
viewer.getROIEditor().setROI(null);
viewer.repaint();
// tempObject = createTransformedObject();
// ((PathAnnotationObject)tempObject).setLocked(true);
// viewer.setSelectedObject(tempObject);
}
Aggregations