use of qupath.lib.regions.ImageRegion 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);
}
use of qupath.lib.regions.ImageRegion in project qupath by qupath.
the class ParallelTileObject method resolveOverlaps.
/**
* Request that the tile object attempts to resolve overlaps with its neighboring tiles.
*/
public synchronized void resolveOverlaps() {
// // If we don't have any children, notify that the test is complete
// if (!hasChildren()) {
// for (ParallelTileObject pto : map.keySet())
// pto.notifyTestComplete(this);
// map.clear();
// checkAllTestsComplete();
// return;
// }
long startTime = System.currentTimeMillis();
int nRemoved = 0;
boolean preferNucleus = false;
// If we do have children, loop through & perform tests
Iterator<Entry<ParallelTileObject, Rectangle2D>> iterMap = map.entrySet().iterator();
while (iterMap.hasNext()) {
Entry<ParallelTileObject, Rectangle2D> entry = iterMap.next();
// If the parallel tile object hasn't been processed yet, then just continue - nothing to compare
ParallelTileObject pto = entry.getKey();
if (!pto.isComplete())
continue;
ParallelTileObject first, second;
// Choose a consistent order for the comparison
if (this.getROI().getBoundsX() > pto.getROI().getBoundsX() || this.getROI().getBoundsY() > pto.getROI().getBoundsY()) {
first = this;
second = pto;
} else {
first = pto;
second = this;
}
// ROI firstBounds = first.getROI();
// ROI secondBounds = second.getROI();
// Compare this object's lists with that object's list
List<PathObject> listFirst = first.getObjectsForRegion(entry.getValue());
List<PathObject> listSecond = second.getObjectsForRegion(entry.getValue());
// Only need to compare potential overlaps if both lists are non-empty
if (!listFirst.isEmpty() && !listSecond.isEmpty()) {
Map<ROI, Geometry> cache = new HashMap<>();
Iterator<PathObject> iterFirst = listFirst.iterator();
while (iterFirst.hasNext()) {
PathObject firstObject = iterFirst.next();
ROI firstROI = PathObjectTools.getROI(firstObject, preferNucleus);
ImageRegion firstRegion = ImageRegion.createInstance(firstROI);
Geometry firstGeometry = null;
double firstArea = Double.NaN;
Iterator<PathObject> iterSecond = listSecond.iterator();
while (iterSecond.hasNext()) {
PathObject secondObject = iterSecond.next();
ROI secondROI = PathObjectTools.getROI(secondObject, preferNucleus);
// Do quick overlap test
if (!firstRegion.intersects(secondROI.getBoundsX(), secondROI.getBoundsY(), secondROI.getBoundsWidth(), secondROI.getBoundsHeight()))
continue;
// Get geometries
if (firstGeometry == null) {
firstGeometry = firstROI.getGeometry();
firstArea = firstGeometry.getArea();
}
Geometry secondGeometry = cache.get(secondROI);
if (secondGeometry == null) {
secondGeometry = secondROI.getGeometry();
cache.put(secondROI, secondGeometry);
}
Geometry intersection;
try {
// Get the intersection
if (!firstGeometry.intersects(secondGeometry))
continue;
intersection = firstGeometry.intersection(secondGeometry);
} catch (Exception e) {
logger.warn("Error resolving overlaps: {}", e.getLocalizedMessage());
logger.debug(e.getLocalizedMessage(), e);
continue;
}
if (intersection.isEmpty())
continue;
// Check areas
double intersectionArea = intersection.getArea();
double secondArea = secondGeometry.getArea();
double threshold = 0.1;
if (firstArea >= secondArea) {
if (intersectionArea / secondArea > threshold) {
second.removePathObject(secondObject);
nRemoved++;
}
} else {
if (intersectionArea / firstArea > threshold) {
first.removePathObject(firstObject);
nRemoved++;
break;
}
}
}
}
}
// Remove the neighbor from the map
iterMap.remove();
pto.notifyTestComplete(this);
}
checkAllTestsComplete();
long endTime = System.currentTimeMillis();
logger.debug(String.format("Resolved %d overlaps: %.2f seconds", nRemoved, (endTime - startTime) / 1000.));
// logger.info(String.format("Resolved %d possible overlaps with %d iterations (tested %d of %d): %.2f seconds", nOverlaps, counter, detectedCounter-skipCounter, detectedCounter, (endTime2 - startTime2) / 1000.));
}
use of qupath.lib.regions.ImageRegion in project qupath by qupath.
the class TileExporter method getTiledRegionRequests.
Collection<RegionRequestWrapper> getTiledRegionRequests(double downsample) {
List<RegionRequestWrapper> requests = new ArrayList<>();
if (downsample == 0)
throw new IllegalArgumentException("No downsample was specified!");
ImageRegion regionLocal = region == null ? RegionRequest.createInstance(server, downsample) : region;
// Z and T shouldn't be lower than 0
int minZLocal = minZ < 0 ? 0 : minZ;
int minTLocal = minT < 0 ? 0 : minT;
// Cap Z and T variables to their maximum possible value if needed
int maxZLocal = maxZ > server.nZSlices() || maxZ == -1 ? server.nZSlices() : maxZ;
int maxTLocal = maxT > server.nTimepoints() || maxT == -1 ? server.nTimepoints() : maxT;
// Create another region to account for ImageRegion and RegionRequest params simultaneously
var regionLocal2 = RegionRequest.createInstance(server.getPath(), downsample, regionLocal);
for (int t = minTLocal; t < maxTLocal; t++) {
regionLocal2 = regionLocal2.updateT(t);
for (int z = minZLocal; z < maxZLocal; z++) {
regionLocal2 = regionLocal2.updateZ(z);
requests.addAll(splitRegionRequests(regionLocal2, tileWidth, tileHeight, overlapX, overlapY, includePartialTiles));
}
}
return requests;
}
use of qupath.lib.regions.ImageRegion in project qupath by qupath.
the class PathHierarchyPaintingHelper method paintConnections.
/**
* Paint connections between objects (e.g. from Delaunay triangulation).
*
* @param connections
* @param hierarchy
* @param g2d
* @param color
* @param downsampleFactor
*/
public static void paintConnections(final PathObjectConnections connections, final PathObjectHierarchy hierarchy, Graphics2D g2d, final Color color, final double downsampleFactor) {
if (hierarchy == null || connections == null || connections.isEmpty())
return;
float alpha = (float) (1f - downsampleFactor / 5);
alpha = Math.min(alpha, 0.25f);
float thickness = PathPrefs.detectionStrokeThicknessProperty().get();
if (alpha < .1f || thickness / downsampleFactor <= 0.5)
return;
g2d = (Graphics2D) g2d.create();
// Shape clipShape = g2d.getClip();
g2d.setStroke(getCachedStroke(thickness));
// g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha * .5f));
// g2d.setColor(ColorToolsAwt.getColorWithOpacity(getPreferredOverlayColor(), 1));
g2d.setColor(ColorToolsAwt.getColorWithOpacity(color.getRGB(), alpha));
// g2d.setColor(Color.BLACK);
Line2D line = new Line2D.Double();
// We can have trouble whenever two objects are outside the clip, but their connections would be inside it
// Here, we just enlarge the region (by quite a lot)
// It's not guaranteed to work, but it usually does... and avoids much expensive computations
Rectangle bounds = g2d.getClipBounds();
int factor = 1;
Rectangle bounds2 = factor > 0 ? new Rectangle(bounds.x - bounds.width * factor, bounds.y - bounds.height * factor, bounds.width * (factor * 2 + 1), bounds.height * (factor * 2 + 1)) : bounds;
ImageRegion imageRegion = AwtTools.getImageRegion(bounds2, 0, 0);
// ImageRegion imageRegion = AwtTools.getImageRegion(bounds, 0, 0);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
// g2d.draw(g2d.getClipBounds());
Collection<PathObject> pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, imageRegion, null);
// double threshold = downsampleFactor*downsampleFactor*4;
for (PathObject pathObject : pathObjects) {
ROI roi = PathObjectTools.getROI(pathObject, true);
double x1 = roi.getCentroidX();
double y1 = roi.getCentroidY();
for (PathObjectConnectionGroup dt : connections.getConnectionGroups()) {
for (PathObject siblingObject : dt.getConnectedObjects(pathObject)) {
ROI roi2 = PathObjectTools.getROI(siblingObject, true);
double x2 = roi2.getCentroidX();
double y2 = roi2.getCentroidY();
if (bounds.intersectsLine(x1, y1, x2, y2)) {
line.setLine(x1, y1, x2, y2);
g2d.draw(line);
}
}
}
}
g2d.dispose();
}
use of qupath.lib.regions.ImageRegion in project qupath by qupath.
the class HierarchyOverlay method paintOverlay.
@Override
public void paintOverlay(final Graphics2D g2d, final ImageRegion imageRegion, final double downsampleFactor, final ImageData<BufferedImage> imageData, final boolean paintCompletely) {
if (this.imageData != imageData) {
this.imageData = imageData;
updateOverlayServer();
}
// Get the selection model, which can influence colours (TODO: this might not be the best way to do it!)
PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
if (hierarchy == null)
return;
if (!isVisible() && hierarchy.getSelectionModel().noSelection())
return;
// Default RenderingHints (may be temporarily changed in some places)
var defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_ON;
var defaultStroke = RenderingHints.VALUE_STROKE_PURE;
// Doesn't seem to help...?
// boolean fastRendering = true;
// if (fastRendering) {
// defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_OFF;
// defaultStroke = RenderingHints.VALUE_STROKE_DEFAULT;
// }
OverlayOptions overlayOptions = getOverlayOptions();
long timestamp = overlayOptions.lastChangeTimestamp().get();
int pointRadius = PathPrefs.pointRadiusProperty().get();
if (overlayOptionsTimestamp != timestamp || pointRadius != lastPointRadius) {
lastPointRadius = pointRadius;
overlayOptionsTimestamp = timestamp;
}
int t = imageRegion.getT();
int z = imageRegion.getZ();
Rectangle serverBounds = AwtTools.getBounds(imageRegion);
// Ensure antialias is on...?
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
// Get the displayed clip bounds for fast checking if ROIs need to be drawn
Shape shapeRegion = g2d.getClip();
if (shapeRegion == null)
shapeRegion = AwtTools.getBounds(imageRegion);
var boundsDisplayed = shapeRegion.getBounds();
// Ensure the bounds do not extend beyond what the server actually contains
boundsDisplayed = boundsDisplayed.intersection(serverBounds);
if (boundsDisplayed.width <= 0 || boundsDisplayed.height <= 0)
return;
// Get the annotations & selected objects (which must be painted directly)
Collection<PathObject> selectedObjects = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
selectedObjects.removeIf(p -> !p.hasROI() || (p.getROI().getZ() != z || p.getROI().getT() != t));
ImageRegion region = AwtTools.getImageRegion(boundsDisplayed, z, t);
// Paint detection objects
long startTime = System.currentTimeMillis();
if (overlayOptions.getShowDetections() && !hierarchy.isEmpty()) {
// If we aren't downsampling by much, or we're upsampling, paint directly - making sure to paint the right number of times, and in the right order
if (overlayServer == null || regionStore == null || downsampleFactor < 1.0) {
Collection<PathObject> pathObjects;
try {
Set<PathObject> pathObjectsToPaint = new TreeSet<>(comparator);
pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, pathObjectsToPaint);
} catch (IllegalArgumentException e) {
// This can happen (rarely) in a multithreaded environment if the level of a detection changes.
// However, protecting against this fully by caching the level with integer boxing/unboxing would be expensive.
logger.debug("Exception requesting detections to paint: " + e.getLocalizedMessage(), e);
pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, null);
}
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
if (overlayOptions.getShowConnections()) {
Object connections = imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS);
if (connections instanceof PathObjectConnections)
PathHierarchyPaintingHelper.paintConnections((PathObjectConnections) connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
}
} else {
// On the other hand, if a large image has been updated then we may be browsing quickly - better to repaint quickly while tiles may still be loading
if (paintCompletely) {
regionStore.paintRegionCompletely(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, 5000);
} else {
regionStore.paintRegion(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, null);
}
}
}
long endTime = System.currentTimeMillis();
if (endTime - startTime > 500)
logger.debug("Painting time: {} seconds", GeneralTools.formatNumber((endTime - startTime) / 1000.0, 4));
// The setting below stops some weird 'jiggling' effects during zooming in/out, or poor rendering of shape ROIs
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, defaultStroke);
// Prepare to handle labels, if we need to
Collection<PathObject> objectsWithNames = new ArrayList<>();
Collection<PathObject> annotations = hierarchy.getObjectsForRegion(PathAnnotationObject.class, region, null);
for (var iterator = annotations.iterator(); iterator.hasNext(); ) {
var next = iterator.next();
if ((next.getName() != null && !next.getName().isBlank()))
objectsWithNames.add(next);
if (selectedObjects.contains(next))
iterator.remove();
}
// Paint the annotations
List<PathObject> pathObjectList = new ArrayList<>(annotations);
Collections.sort(pathObjectList, Comparator.comparingInt(PathObject::getLevel).reversed().thenComparing(Comparator.comparingDouble((PathObject p) -> -p.getROI().getArea())));
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjectList, overlayOptions, null, downsampleFactor);
// Ensure that selected objects are painted last, to make sure they aren't obscured
if (!selectedObjects.isEmpty()) {
Composite previousComposite = g2d.getComposite();
float opacity = overlayOptions.getOpacity();
if (opacity < 1) {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
g2d.setComposite(previousComposite);
} else {
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
}
}
// Paint labels
if (overlayOptions.getShowNames() && !objectsWithNames.isEmpty()) {
double requestedFontSize;
switch(PathPrefs.viewerFontSizeProperty().get()) {
case HUGE:
requestedFontSize = 24;
break;
case LARGE:
requestedFontSize = 18;
break;
case SMALL:
requestedFontSize = 10;
break;
case TINY:
requestedFontSize = 8;
break;
case MEDIUM:
default:
requestedFontSize = 14;
break;
}
float fontSize = (float) (requestedFontSize * downsampleFactor);
if (!GeneralTools.almostTheSame(font.getSize2D(), fontSize, 0.001))
font = font.deriveFont(fontSize);
g2d.setFont(font);
var metrics = g2d.getFontMetrics(font);
var rect = new Rectangle2D.Double();
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (var annotation : objectsWithNames) {
var name = annotation.getName();
var roi = annotation.getROI();
if (name != null && !name.isBlank() && roi != null && !overlayOptions.isPathClassHidden(annotation.getPathClass())) {
g2d.setColor(ColorToolsAwt.TRANSLUCENT_BLACK);
var bounds = metrics.getStringBounds(name, g2d);
double pad = 5.0 * downsampleFactor;
double x = roi.getCentroidX() - bounds.getWidth() / 2.0;
double y = roi.getCentroidY() + bounds.getY() + metrics.getAscent() + pad;
rect.setFrame(x + bounds.getX() - pad, y + bounds.getY() - pad, bounds.getWidth() + pad * 2, bounds.getHeight() + pad * 2);
g2d.fill(rect);
g2d.setColor(Color.WHITE);
g2d.drawString(name, (float) x, (float) y);
}
}
}
}
Aggregations