use of qupath.lib.plugins.ParallelTileObject in project qupath by qupath.
the class PathHierarchyPaintingHelper method paintObject.
/**
* Paint an object (or, more precisely, its ROI), optionally along with the ROIs of any child objects.
*
* This is subject to the OverlayOptions, and therefore may not actually end up painting anything
* (if the settings are such that objects of the class provided are not to be displayed)
*
* @param pathObject
* @param paintChildren
* @param g
* @param boundsDisplayed
* @param overlayOptions
* @param selectionModel
* @param downsample
* @return true if anything was painted, false otherwise
*/
public static boolean paintObject(PathObject pathObject, boolean paintChildren, Graphics2D g, Rectangle boundsDisplayed, OverlayOptions overlayOptions, PathObjectSelectionModel selectionModel, double downsample) {
if (pathObject == null)
return false;
// g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
// g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
// Always paint the selected object
// Note: this makes the assumption that child ROIs are completely contained within their parents;
// this probably should be the case, but isn't guaranteed
boolean isSelected = (selectionModel != null && selectionModel.isSelected(pathObject)) && (PathPrefs.useSelectedColorProperty().get() || !PathObjectTools.hasPointROI(pathObject));
boolean isDetectedObject = pathObject.isDetection() || (pathObject.isTile() && pathObject.hasMeasurements());
// Check if the PathClass isn't being shown
PathClass pathClass = pathObject.getPathClass();
if (!isSelected && overlayOptions != null && overlayOptions.isPathClassHidden(pathClass))
return false;
boolean painted = false;
// See if we need to check the children
ROI pathROI = pathObject.getROI();
if (pathROI != null) {
double roiBoundsX = pathROI.getBoundsX();
double roiBoundsY = pathROI.getBoundsY();
double roiBoundsWidth = pathROI.getBoundsWidth();
double roiBoundsHeight = pathROI.getBoundsHeight();
if (PathObjectTools.hasPointROI(pathObject) || boundsDisplayed == null || pathROI instanceof LineROI || boundsDisplayed.intersects(roiBoundsX, roiBoundsY, Math.max(roiBoundsWidth, 1), Math.max(roiBoundsHeight, 1))) {
// Paint the ROI, if necessary
if (isSelected || (overlayOptions.getShowDetections() && isDetectedObject) || (overlayOptions.getShowAnnotations() && pathObject.isAnnotation()) || (overlayOptions.getShowTMAGrid() && pathObject.isTMACore())) {
boolean doFill = overlayOptions.getFillDetections() || pathObject instanceof ParallelTileObject;
boolean doOutline = true;
Color color = null;
boolean useMapper = false;
double fillOpacity = .75;
if (isSelected && PathPrefs.useSelectedColorProperty().get() && PathPrefs.colorSelectedObjectProperty().getValue() != null)
color = ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get());
else {
MeasurementMapper mapper = overlayOptions.getMeasurementMapper();
useMapper = mapper != null && mapper.isValid() && pathObject.isDetection();
if (useMapper) {
if (pathObject.hasMeasurements()) {
Integer rgb = mapper.getColorForObject(pathObject);
// If the mapper returns null, the object shouldn't be painted
if (rgb == null)
return false;
// , mapper.getColorMapper().hasAlpha());
color = ColorToolsAwt.getCachedColor(rgb);
} else
color = null;
// System.out.println(color + " - " + pathObject.getMeasurementList().getMeasurementValue(mapper.));
fillOpacity = 1.0;
// Outlines are not so helpful with the measurement mapper
if (doFill)
doOutline = doOutline && !pathObject.isTile();
} else {
Integer rgb = ColorToolsFX.getDisplayedColorARGB(pathObject);
color = ColorToolsAwt.getCachedColor(rgb);
}
// color = PathObjectHelpers.getDisplayedColor(pathObject);
}
// Check if we have only one or two pixels to draw - if so, we can be done quickly
if (isDetectedObject && downsample > 4 && roiBoundsWidth / downsample < 3 && roiBoundsHeight / downsample < 3) {
int x = (int) roiBoundsX;
int y = (int) roiBoundsY;
// Prefer rounding up, lest we lose a lot of regions unnecessarily
int w = (int) (roiBoundsWidth + .9);
int h = (int) (roiBoundsHeight + .9);
if (w > 0 && h > 0) {
g.setColor(color);
// g.setColor(DisplayHelpers.getMoreTranslucentColor(color));
// g.setStroke(getCachedStroke(overlayOptions.strokeThinThicknessProperty().get()));
g.fillRect(x, y, w, h);
}
painted = true;
} else {
Stroke stroke = null;
// Decide whether to fill or not
Color colorFill = doFill && (isDetectedObject || PathObjectTools.hasPointROI(pathObject)) ? color : null;
if (colorFill != null && fillOpacity != 1) {
if (pathObject instanceof ParallelTileObject)
colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
else if (pathObject instanceof PathCellObject && overlayOptions.getShowCellBoundaries() && overlayOptions.getShowCellNuclei()) {
// if (isSelected)
// colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
// else
colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
} else if (pathObject.getParent() instanceof PathDetectionObject) {
colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
} else if (pathObject instanceof PathTileObject && pathClass == null && color != null && color.getRGB() == PathPrefs.colorTileProperty().get()) {
// Don't fill in empty, unclassified tiles
// DisplayHelpers.getMoreTranslucentColor(colorFill);
colorFill = null;
}
}
// Color colorStroke = doOutline ? (colorFill == null ? color : (downsample > overlayOptions.strokeThinThicknessProperty().get() ? null : DisplayHelpers.darkenColor(color))) : null;
Color colorStroke = doOutline ? (colorFill == null ? color : ColorToolsAwt.darkenColor(color)) : null;
// For thick lines, antialiasing is very noticeable... less so for thin lines (of which there may be a huge number)
if (isDetectedObject) {
// Detections inside detections get half the line width
if (pathObject.getParent() instanceof PathDetectionObject)
stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get() / 2.0);
else
stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get());
} else {
double thicknessScale = downsample * (isSelected && !PathPrefs.useSelectedColorProperty().get() ? 1.6 : 1);
float thickness = (float) (PathPrefs.annotationStrokeThicknessProperty().get() * thicknessScale);
if (isSelected && pathObject.getParent() == null && PathPrefs.selectionModeProperty().get()) {
stroke = getCachedStrokeDashed(thickness);
} else {
stroke = getCachedStroke(thickness);
}
}
g.setStroke(stroke);
boolean paintSymbols = overlayOptions.getDetectionDisplayMode() == DetectionDisplayMode.CENTROIDS && pathObject.isDetection() && !pathObject.isTile();
if (paintSymbols) {
pathROI = PathObjectTools.getROI(pathObject, true);
double x = pathROI.getCentroidX();
double y = pathROI.getCentroidY();
double radius = PathPrefs.detectionStrokeThicknessProperty().get() * 2.0;
if (pathObject.getParent() instanceof PathDetectionObject)
radius /= 2.0;
Shape shape;
int nSubclasses = 0;
if (pathClass != null) {
nSubclasses = PathClassTools.splitNames(pathClass).size();
}
switch(nSubclasses) {
case 0:
var ellipse = localEllipse2D.get();
ellipse.setFrame(x - radius, y - radius, radius * 2, radius * 2);
shape = ellipse;
break;
case 1:
var rect = localRect2D.get();
rect.setFrame(x - radius, y - radius, radius * 2, radius * 2);
shape = rect;
break;
case 2:
var triangle = localPath2D.get();
double sqrt3 = Math.sqrt(3.0);
triangle.reset();
triangle.moveTo(x, y - radius * 2.0 / sqrt3);
triangle.lineTo(x - radius, y + radius / sqrt3);
triangle.lineTo(x + radius, y + radius / sqrt3);
triangle.closePath();
shape = triangle;
break;
case 3:
var plus = localPath2D.get();
plus.reset();
plus.moveTo(x, y - radius);
plus.lineTo(x, y + radius);
plus.moveTo(x - radius, y);
plus.lineTo(x + radius, y);
shape = plus;
break;
default:
var cross = localPath2D.get();
cross.reset();
radius /= Math.sqrt(2);
cross.moveTo(x - radius, y - radius);
cross.lineTo(x + radius, y + radius);
cross.moveTo(x + radius, y - radius);
cross.lineTo(x - radius, y + radius);
shape = cross;
break;
}
paintShape(shape, g, colorStroke, stroke, colorFill);
} else if (pathObject instanceof PathCellObject) {
PathCellObject cell = (PathCellObject) pathObject;
if (overlayOptions.getShowCellBoundaries())
paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
if (overlayOptions.getShowCellNuclei())
paintROI(cell.getNucleusROI(), g, colorStroke, stroke, colorFill, downsample);
painted = true;
} else {
if ((overlayOptions.getFillAnnotations() && pathObject.isAnnotation() && pathObject.getPathClass() != PathClassFactory.getPathClass(StandardPathClasses.REGION) && (pathObject.getPathClass() != null || !pathObject.hasChildren())) || (pathObject.isTMACore() && overlayOptions.getShowTMACoreLabels()))
paintROI(pathROI, g, colorStroke, stroke, ColorToolsAwt.getMoreTranslucentColor(colorStroke), downsample);
else
paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
painted = true;
}
}
}
}
}
// Paint the children, if necessary
if (paintChildren) {
for (PathObject childObject : pathObject.getChildObjectsAsArray()) {
// Only call the painting method if required
ROI childROI = childObject.getROI();
if ((childROI != null && boundsDisplayed.intersects(childROI.getBoundsX(), childROI.getBoundsY(), childROI.getBoundsWidth(), childROI.getBoundsHeight())) || childObject.hasChildren())
painted = paintObject(childObject, paintChildren, g, boundsDisplayed, overlayOptions, selectionModel, downsample) | painted;
}
}
return painted;
}
use of qupath.lib.plugins.ParallelTileObject in project qupath by qupath.
the class UndoRedoManager method hierarchyChanged.
@Override
public void hierarchyChanged(PathObjectHierarchyEvent event) {
// Try to avoid calling too often
if (undoingOrRedoing || event.isChanging() || maxUndoHierarchySize.get() <= 0 || event.getChangedObjects().stream().allMatch(p -> p instanceof ParallelTileObject))
return;
// *Potentially* we might have the same hierarchy in multiple viewers
// Since we don't have the viewer stored in the event, check to see what viewers are impacted
QuPathViewer[] viewers = map.keySet().toArray(new QuPathViewer[map.size()]);
PathObjectHierarchy hierarchy = event.getHierarchy();
int maxSize = maxUndoHierarchySize.get();
boolean sizeOK = hierarchy.nObjects() <= maxSize;
for (QuPathViewer viewer : viewers) {
if (viewer.getHierarchy() == hierarchy) {
SerializableUndoRedoStack<PathObjectHierarchy> undoRedo = map.get(viewer);
// If the size is ok, register the change for potential undo-ing
if (sizeOK) {
if (undoRedo == null)
map.put(viewer, new SerializableUndoRedoStack<>(hierarchy));
else
undoRedo.addLatest(hierarchy, maxUndoLevels.get());
} else {
// If the hierarchy is too big turn off undo/redo
map.put(viewer, (SerializableUndoRedoStack<PathObjectHierarchy>) null);
}
}
}
refreshProperties();
}
Aggregations