use of qupath.lib.objects.PathCellObject 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.PathCellObject in project qupath by qupath.
the class IJExtension method extractOverlay.
/**
* Extract an ImageJ overlay for the specified region.
* @param hierarchy
* @param request
* @param options options to control which objects are being displayed
* @param filter optional additional filter used to determine which objects will be included (may be used in combination with options)
* @return
*/
public static Overlay extractOverlay(PathObjectHierarchy hierarchy, RegionRequest request, OverlayOptions options, Predicate<PathObject> filter) {
Overlay overlay = new Overlay();
double downsample = request.getDownsample();
double xOrigin = -request.getX() / downsample;
double yOrigin = -request.getY() / downsample;
// TODO: Permit filling/unfilling ROIs
for (PathObject child : hierarchy.getObjectsForRegion(PathObject.class, request, null)) {
if (filter != null && !filter.test(child))
continue;
if (child.hasROI()) {
// Check if this is displayed - skip it not
if (options != null && ((child instanceof PathDetectionObject && !options.getShowDetections()) || (child instanceof PathAnnotationObject && !options.getShowAnnotations()) || (child instanceof TMACoreObject && !options.getShowTMAGrid())))
continue;
boolean isCell = child instanceof PathCellObject;
Color color = ColorToolsAwt.getCachedColor(ColorToolsFX.getDisplayedColorARGB(child));
if (!(isCell && (options == null || !options.getShowCellBoundaries()))) {
Roi roi = IJTools.convertToIJRoi(child.getROI(), xOrigin, yOrigin, downsample);
roi.setStrokeColor(color);
roi.setName(child.getDisplayedName());
// roi.setStrokeWidth(2);
overlay.add(roi);
}
if (isCell && (options == null || options.getShowCellNuclei())) {
ROI nucleus = ((PathCellObject) child).getNucleusROI();
if (nucleus == null)
continue;
Roi roi = IJTools.convertToIJRoi(((PathCellObject) child).getNucleusROI(), xOrigin, yOrigin, downsample);
roi.setStrokeColor(color);
roi.setName(child.getDisplayedName() + " - nucleus");
overlay.add(roi);
}
}
}
return overlay;
}
use of qupath.lib.objects.PathCellObject in project qupath by qupath.
the class PathObjectIOTest method test_IOObjectsGeoJSONImpl.
private void test_IOObjectsGeoJSONImpl(boolean keepMeasurements, GeoJsonExportOptions... options) throws IOException {
ROI roiDetection = ROIs.createRectangleROI(0, 0, 10, 10, ImagePlane.getDefaultPlane());
ROI roiAnnotation = ROIs.createRectangleROI(100, 100, 10, 10, ImagePlane.getDefaultPlane());
ROI roiCell1 = ROIs.createRectangleROI(25, 25, 25, 25, ImagePlane.getDefaultPlane());
ROI roiCell2 = ROIs.createRectangleROI(12, 12, 5, 5, ImagePlane.getDefaultPlane());
ROI roiTile = ROIs.createRectangleROI(100, 100, 10, 10, ImagePlane.getDefaultPlane());
MeasurementList mlDetection = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList mlCell = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
PathObject myPDO = PathObjects.createDetectionObject(roiDetection, PathClassFactory.getPathClass("PathClassTest1", ColorTools.BLACK), mlDetection);
PathObject myPAO = PathObjects.createAnnotationObject(roiAnnotation, PathClassFactory.getPathClass("PathClassTest1", ColorTools.BLACK));
PathObject myPCO = PathObjects.createCellObject(roiCell1, roiCell2, PathClassFactory.getPathClass("PathClassTest2", ColorTools.GREEN), mlCell);
PathObject myPTO = PathObjects.createTileObject(roiTile, PathClassFactory.getPathClass("PathClassTest2", ColorTools.GREEN), null);
PathObject myTMA = PathObjects.createTMACoreObject(25, 25, 25, false);
Collection<PathObject> objs = Arrays.asList(myPDO, myPCO, myPAO, myPTO, myTMA);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// Add measurements
mlDetection.addMeasurement("TestMeasurement1", 5.0);
mlDetection.addMeasurement("TestMeasurement2", 10.0);
mlCell.addMeasurement("TestMeasurement3", 15.0);
mlCell.addMeasurement("TestMeasurement4", 20.0);
// Export to GeoJSON
PathIO.exportObjectsAsGeoJSON(bos, objs, options);
// Import from GeoJSON
List<PathObject> objsBack = new ArrayList<>(PathIO.readObjectsFromGeoJSON(new ByteArrayInputStream(bos.toByteArray())));
assertEquals(objs.size(), objsBack.size());
// Array to count number of each PathObject type
int[] countCheck = new int[] { 0, 0, 0, 0, 0 };
for (PathObject po : objsBack) {
if (po == null)
continue;
// Test whether po has a ROI
assertTrue(po.hasROI());
if (po.isTile()) {
assertEquals(po.getPathClass(), PathClassFactory.getPathClass("PathClassTest2", ColorTools.GREEN));
assertSameROIs(po.getROI(), roiTile);
assertFalse(po.hasMeasurements());
countCheck[0]++;
} else if (po.isCell()) {
assertEquals(po.getPathClass(), PathClassFactory.getPathClass("PathClassTest2", ColorTools.GREEN));
assertSameROIs(po.getROI(), roiCell1);
assertSameROIs(((PathCellObject) po).getNucleusROI(), roiCell2);
if (keepMeasurements) {
assertTrue(po.hasMeasurements());
assertSameMeasurements(po.getMeasurementList(), myPCO.getMeasurementList());
} else
assertFalse(po.hasMeasurements());
countCheck[1]++;
} else if (po.isDetection()) {
assertEquals(po.getPathClass(), PathClassFactory.getPathClass("PathClassTest1", ColorTools.BLACK));
assertSameROIs(po.getROI(), roiDetection);
if (keepMeasurements) {
assertTrue(po.hasMeasurements());
assertSameMeasurements(po.getMeasurementList(), myPDO.getMeasurementList());
} else
assertFalse(po.hasMeasurements());
countCheck[2]++;
} else if (po.isAnnotation()) {
assertEquals(po.getPathClass(), PathClassFactory.getPathClass("PathClassTest1", ColorTools.BLACK));
assertSameROIs(po.getROI(), roiAnnotation);
assertFalse(po.hasMeasurements());
countCheck[3]++;
} else if (po.isTMACore()) {
assertFalse(po.hasMeasurements());
assertSameROIs(po.getROI(), myTMA.getROI());
countCheck[4]++;
}
}
assertArrayEquals(countCheck, new int[] { 1, 1, 1, 1, 1 });
}
use of qupath.lib.objects.PathCellObject in project qupath by qupath.
the class ObjectMeasurements method addIntensityMeasurements.
/**
* Measure all channels of an image for one individual object or cell.
* All compartments are measured where possible (nucleus, cytoplasm, membrane and full cell).
* <p>
* Note: This implementation is likely to change in the future, to enable neighboring cells to be
* measured more efficiently.
*
* @param server the server containing the pixels (and channels) to be measured
* @param pathObject the cell to measure (the {@link MeasurementList} will be updated)
* @param downsample resolution at which to request pixels
* @param measurements requested measurements to make
* @param compartments the cell compartments to measure; ignored if the object is not a cell
* @throws IOException
*/
public static void addIntensityMeasurements(ImageServer<BufferedImage> server, PathObject pathObject, double downsample, Collection<Measurements> measurements, Collection<Compartments> compartments) throws IOException {
var roi = pathObject.getROI();
int pad = (int) Math.ceil(downsample * 2);
var request = RegionRequest.createInstance(server.getPath(), downsample, roi).pad2D(pad, pad).intersect2D(0, 0, server.getWidth(), server.getHeight());
var pathImage = IJTools.convertToImagePlus(server, request);
var imp = pathImage.getImage();
Map<String, ImageProcessor> channels = new LinkedHashMap<>();
var serverChannels = server.getMetadata().getChannels();
if (server.isRGB() && imp.getStackSize() == 1 && imp.getProcessor() instanceof ColorProcessor) {
ColorProcessor cp = (ColorProcessor) imp.getProcessor();
for (int i = 0; i < serverChannels.size(); i++) {
channels.put(serverChannels.get(i).getName(), cp.getChannel(i + 1, null));
}
} else {
assert imp.getStackSize() == serverChannels.size();
for (int i = 0; i < imp.getStackSize(); i++) {
channels.put(serverChannels.get(i).getName(), imp.getStack().getProcessor(i + 1));
}
}
ByteProcessor bpCell = new ByteProcessor(imp.getWidth(), imp.getHeight());
bpCell.setValue(1.0);
var roiIJ = IJTools.convertToIJRoi(roi, pathImage);
bpCell.fill(roiIJ);
if (pathObject instanceof PathCellObject) {
var cell = (PathCellObject) pathObject;
ByteProcessor bpNucleus = new ByteProcessor(imp.getWidth(), imp.getHeight());
if (cell.getNucleusROI() != null) {
bpNucleus.setValue(1.0);
var roiNucleusIJ = IJTools.convertToIJRoi(cell.getNucleusROI(), pathImage);
bpNucleus.fill(roiNucleusIJ);
}
measureCells(bpNucleus, bpCell, Map.of(1.0, cell), channels, compartments, measurements);
} else {
var imgLabels = new PixelImageIJ(bpCell);
for (var entry : channels.entrySet()) {
var img = new PixelImageIJ(entry.getValue());
measureObjects(img, imgLabels, new PathObject[] { pathObject }, entry.getKey(), measurements);
}
}
}
use of qupath.lib.objects.PathCellObject in project qupath by qupath.
the class ObjectMeasurements method addShapeMeasurements.
/**
* Add shape measurements for multiple objects. If any of these objects is a cell, measurements will be made for both the
* nucleus and cell boundary where possible.
*
* @param pathObjects the objects for which measurements should be added
* @param cal pixel calibration, used to determine units and scaling
* @param features specific features to add; if empty, all available shape features will be added
*/
public static void addShapeMeasurements(Collection<? extends PathObject> pathObjects, PixelCalibration cal, ShapeFeatures... features) {
PixelCalibration calibration = cal == null || !cal.unitsMatch2D() ? PixelCalibration.getDefaultInstance() : cal;
Collection<ShapeFeatures> featureCollection = features.length == 0 ? ALL_SHAPE_FEATURES : Arrays.asList(features);
pathObjects.parallelStream().filter(p -> p.hasROI()).forEach(pathObject -> {
if (pathObject instanceof PathCellObject) {
addCellShapeMeasurements((PathCellObject) pathObject, calibration, featureCollection);
} else {
try (var ml = pathObject.getMeasurementList()) {
addShapeMeasurements(ml, pathObject.getROI(), calibration, "", featureCollection);
}
}
});
}
Aggregations