use of qupath.lib.objects.PathObjectConnections 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);
}
}
}
}
use of qupath.lib.objects.PathObjectConnections in project qupath by qupath.
the class PathHierarchyImageServer method readTile.
@Override
protected BufferedImage readTile(TileRequest tileRequest) throws IOException {
RegionRequest request = tileRequest.getRegionRequest();
// long startTime = System.currentTimeMillis();
// Get connections
Object o = options.getShowConnections() ? imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS) : null;
PathObjectConnections connections = (o instanceof PathObjectConnections) ? (PathObjectConnections) o : null;
List<PathObject> pathObjects = new ArrayList<>(getObjectsToPaint(request));
if (pathObjects == null || pathObjects.isEmpty()) {
// We can only return null if no connections - otherwise we might still need to draw something
if (connections == null) {
return null;
}
}
// Because levels *can* change, we need to extract them first to avoid breaking the contract for comparable
// in a multithreaded environment
var levels = pathObjects.stream().collect(Collectors.toMap(p -> p, p -> p.getLevel()));
var comparator = DefaultPathObjectComparator.getInstance().thenComparingInt(p -> levels.get(p));
Collections.sort(pathObjects, comparator);
// Collections.sort(pathObjects, new HierarchyOverlay.DetectionComparator());
double downsampleFactor = request.getDownsample();
int width = tileRequest.getTileWidth();
int height = tileRequest.getTileHeight();
BufferedImage img = createDefaultRGBImage(width, height);
Graphics2D g2d = img.createGraphics();
g2d.setClip(0, 0, width, height);
// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double scale = 1.0 / downsampleFactor;
// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.scale(scale, scale);
g2d.translate(-request.getX(), -request.getY());
// Note we don't want to pass a selection model, as selections shouldn't be included
if (pathObjects != null && !pathObjects.isEmpty())
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, AwtTools.getBounds(request), pathObjects, options, null, downsampleFactor);
// See if we have any connections to draw
if (connections != null) {
PathHierarchyPaintingHelper.paintConnections(connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
}
g2d.dispose();
// System.out.println("Single tile image creation time: " + (endTime - startTime)/1000.);
return img;
}
Aggregations