use of qupath.lib.objects.PathTileObject 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.objects.PathTileObject in project qupath by qupath.
the class BrushTool method createShape.
/**
* Create a new Geometry using the specified tool, assuming a user click/drag at the provided x & y coordinates.
* @param e
*
* @param x
* @param y
* @param useTiles If true, request generating a shape from existing tile objects.
* @param addToShape If provided, it can be assumed that any new shape ought to be added to this one.
* The purpose is that this method may (optionally) use the shape to refine the one it will generate,
* e.g. to avoid having isolated or jagged boundaries.
* @return
*/
protected Geometry createShape(MouseEvent e, double x, double y, boolean useTiles, Geometry addToShape) {
// See if we're on top of a tile
if (useTiles) {
List<PathObject> listSelectable = getSelectableObjectList(x, y);
for (PathObject temp : listSelectable) {
// if ((temp instanceof PathDetectionObject) && temp.getROI() instanceof PathArea)
if (temp instanceof PathTileObject && temp.hasROI() && temp.getROI().isArea() && !(temp.getROI() instanceof RectangleROI)) {
creatingTiledROI = true;
return temp.getROI().getGeometry();
}
}
// If we're currently creating a tiled, ROI, but now not clicked on a tile, just return
if (creatingTiledROI)
return null;
}
// Compute a diameter scaled according to the pressure being applied
double diameter = Math.max(1, getBrushDiameter());
Geometry geometry;
if (lastPoint == null) {
var shapeFactory = new GeometricShapeFactory(getGeometryFactory());
shapeFactory.setCentre(new Coordinate(x, y));
shapeFactory.setSize(diameter);
// shapeFactory.setCentre(new Coordinate(x-diameter/2, y-diameter/2));
geometry = shapeFactory.createEllipse();
} else {
if (lastPoint.distanceSq(x, y) == 0)
return null;
var factory = getGeometryFactory();
geometry = factory.createLineString(new Coordinate[] { new Coordinate(lastPoint.getX(), lastPoint.getY()), new Coordinate(x, y) }).buffer(diameter / 2.0);
}
return geometry;
}
use of qupath.lib.objects.PathTileObject in project qupath by qupath.
the class SmoothFeaturesPlugin method addRunnableTasks.
@Override
protected void addRunnableTasks(final ImageData<T> imageData, final PathObject parentObject, List<Runnable> tasks) {
double fwhm;
ImageServer<T> server = imageData.getServer();
String fwhmStringTemp;
PixelCalibration cal = server == null ? null : server.getPixelCalibration();
if (cal != null && cal.hasPixelSizeMicrons()) {
fwhm = getParameterList(imageData).getDoubleParameterValue("fwhmMicrons");
fwhmStringTemp = GeneralTools.createFormatter(2).format(fwhm) + " " + GeneralTools.micrometerSymbol();
fwhm /= cal.getAveragedPixelSizeMicrons();
// params.addDoubleParameter("fwhmPixels", "Radius (FWHM)", fwhm, "pixels"); // Set the FWHM in pixels too
} else {
fwhm = getParameterList(imageData).getDoubleParameterValue("fwhmPixels");
fwhmStringTemp = GeneralTools.createFormatter(2).format(fwhm) + " px";
}
// sigma = 50;
final String fwhmString = fwhmStringTemp;
final double fwhmPixels = fwhm;
final boolean withinClass = params.getBooleanParameterValue("smoothWithinClasses");
final boolean useLegacyNames = params.containsKey("useLegacyNames") && Boolean.TRUE.equals(params.getBooleanParameterValue("useLegacyNames"));
tasks.add(new Runnable() {
@Override
public void run() {
try {
if (!parentObject.hasChildren())
return;
// System.out.println("Smoothing with FWHM " +fwhmPixels);
// TODO: MAKE A MORE ELEGANT LIST!!!!
List<PathObject> pathObjects = PathObjectTools.getFlattenedObjectList(parentObject, null, false);
Iterator<PathObject> iterObjects = pathObjects.iterator();
while (iterObjects.hasNext()) {
PathObject temp = iterObjects.next();
if (!(temp instanceof PathDetectionObject || temp instanceof PathTileObject))
iterObjects.remove();
}
if (pathObjects.isEmpty())
return;
// TODO: ACCESS & USE THE CLASSIFIER DATA!!!!
List<String> measurements = new ArrayList<>(PathClassifierTools.getAvailableFeatures(pathObjects));
Iterator<String> iter = measurements.iterator();
while (iter.hasNext()) {
String name = iter.next().toLowerCase();
if (name.endsWith("smoothed") || name.startsWith("smoothed") || name.contains(" - smoothed (fwhm ") || name.startsWith("smoothed denominator (local density, ") || name.startsWith("nearby detection counts"))
iter.remove();
}
logger.debug(String.format("Smooth features: %s (FWHM: %.2f px)", parentObject.getDisplayedName(), fwhmPixels));
smoothMeasurements(pathObjects, measurements, fwhmPixels, fwhmString, withinClass, useLegacyNames);
// // REMOVE - the purpose was to test a 'difference of Gaussians' type of thing
// List<String> namesAdded1 = new ArrayList<>(smoothMeasurements(pathObjects, measurements, fwhmPixels));
// List<String> namesAdded2 = new ArrayList<>(smoothMeasurements(pathObjects, measurements, fwhmPixels * 2));
// for (PathObject pathObject : pathObjects) {
// MeasurementList ml = pathObject.getMeasurementList();
// ml.ensureListOpen();
// for (int i = 0; i < namesAdded1.size(); i++) {
// String name1 = namesAdded1.get(i);
// String name2 = namesAdded2.get(i);
// double m1 = ml.getMeasurementValue(name1);
// double m2 = ml.getMeasurementValue(name2);
// ml.addMeasurement(name1 + " - " + name2, m1 - m2);
// }
// ml.closeList();
// }
} catch (Exception e) {
e.printStackTrace();
throw (e);
}
}
});
}
use of qupath.lib.objects.PathTileObject in project qupath by qupath.
the class ColorToolsFX method getDisplayedColorARGB.
/**
* Get the color with which an object should be displayed, as a packaged ARGB integer.
*
* This could be stored internally, or obtained from its PathClass.
*
* If neither of these produces a result, a default color will be returned based on PathPrefs
* for the specific (Java) class of the PathObject.
*
* Assuming PathPrefs does not contain any nulls, this will therefore not return nulls either.
*
* @param pathObject
* @return
*/
public static Integer getDisplayedColorARGB(final PathObject pathObject) {
// Check if any color has been set - if so, return it
Integer color = pathObject.getColorRGB();
if (color != null)
return color;
// Check if any class has been set, if so then use its color
PathClass pathClass = pathObject.getPathClass();
if (pathClass != null)
color = pathClass.getColor();
if (color != null)
return color;
if (pathObject instanceof PathTileObject)
return PathPrefs.colorTileProperty().getValue();
if (pathObject instanceof TMACoreObject) {
if (((TMACoreObject) pathObject).isMissing())
return PathPrefs.colorTMAMissingProperty().getValue();
else
return PathPrefs.colorTMAProperty().getValue();
}
return PathPrefs.colorDefaultObjectsProperty().getValue();
}
Aggregations