use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class SimpleTissueDetection2 method convertToPathObjects.
private static List<PathObject> convertToPathObjects(ByteProcessor bp, double minArea, boolean smoothCoordinates, Calibration cal, double downsample, double maxHoleArea, boolean excludeOnBoundary, boolean singleAnnotation, ImagePlane plane, List<PathObject> pathObjects) {
//
// var roiIJ = new ThresholdToSelection().convert(bp);
// var roi = IJTools.convertToROI(roiIJ, cal, downsample, plane);
// roi = RoiTools.removeSmallPieces(roi, minArea, maxHoleArea);
// List<PathObject> annotations = new ArrayList<>();
// if (singleAnnotation)
// annotations.add(PathObjects.createAnnotationObject(roi));
// else {
// for (var roi2 : RoiTools.splitROI(roi)) {
// annotations.add(PathObjects.createAnnotationObject(roi2));
// }
// }
// for (var annotation : annotations)
// annotation.setLocked(true);
// return annotations;
//
List<PolygonRoi> rois = RoiLabeling.getFilledPolygonROIs(bp, Wand.FOUR_CONNECTED);
if (pathObjects == null)
pathObjects = new ArrayList<>(rois.size());
// We might need a copy of the original image
boolean fillAllHoles = maxHoleArea <= 0;
ByteProcessor bpOrig = !fillAllHoles ? (ByteProcessor) bp.duplicate() : null;
bp.setValue(255);
for (PolygonRoi r : rois) {
// Check for boundary overlap
if (excludeOnBoundary) {
Rectangle bounds = r.getBounds();
if (bounds.x <= 0 || bounds.y <= 0 || bounds.x + bounds.width >= bp.getWidth() - 1 || bounds.y + bounds.height >= bp.getHeight() - 1)
continue;
}
bp.setRoi(r);
if (bp.getStatistics().area < minArea)
continue;
// Fill holes as we go - it might matter later
bp.fill(r);
// if (smoothCoordinates) {
// // r = new PolygonRoi(r.getInterpolatedPolygon(2.5, false), Roi.POLYGON);
// r = new PolygonRoi(r.getInterpolatedPolygon(Math.min(2.5, r.getNCoordinates()*0.1), false), Roi.POLYGON); // TODO: Check this smoothing - it can be troublesome, causing nuclei to be outside cells
// }
PolygonROI pathPolygon = IJTools.convertToPolygonROI(r, cal, downsample, plane);
// Smooth the coordinates, if we downsampled quite a lot
if (smoothCoordinates) {
pathPolygon = ROIs.createPolygonROI(ShapeSimplifier.smoothPoints(pathPolygon.getAllPoints()), ImagePlane.getPlaneWithChannel(pathPolygon));
pathPolygon = ShapeSimplifier.simplifyPolygon(pathPolygon, downsample / 2);
}
pathObjects.add(PathObjects.createAnnotationObject(pathPolygon));
}
if (Thread.currentThread().isInterrupted())
return null;
// TODO: Optimise this - the many 'containsObject' calls are a (potentially easy-to-fix) bottleneck
if (!fillAllHoles) {
// Get the holes alone
bp.copyBits(bpOrig, 0, 0, Blitter.DIFFERENCE);
// new ImagePlus("Binary", bp).show();
bp.setThreshold(127, Double.POSITIVE_INFINITY, ImageProcessor.NO_LUT_UPDATE);
List<PathObject> holes = convertToPathObjects(bp, maxHoleArea, smoothCoordinates, cal, downsample, 0, false, false, plane, null);
// For each object, fill in any associated holes
List<Area> areaList = new ArrayList<>();
for (int ind = 0; ind < pathObjects.size(); ind++) {
if (holes.isEmpty())
break;
PathObject pathObject = pathObjects.get(ind);
var geom = PreparedGeometryFactory.prepare(pathObject.getROI().getGeometry());
areaList.clear();
Iterator<PathObject> iter = holes.iterator();
while (iter.hasNext()) {
PathObject hole = iter.next();
if (geom.covers(hole.getROI().getGeometry())) {
areaList.add(RoiTools.getArea(hole.getROI()));
iter.remove();
}
}
if (areaList.isEmpty())
continue;
// If we have some areas, combine them
// TODO: FIX MAJOR BOTTLENECK HERE!!!
Area hole = areaList.get(0);
for (int i = 1; i < areaList.size(); i++) {
hole.add(areaList.get(i));
if (i % 100 == 0) {
logger.debug("Added hole " + i + "/" + areaList.size());
if (Thread.currentThread().isInterrupted())
return null;
}
}
// Now subtract & create a new object
ROI pathROI = pathObject.getROI();
if (RoiTools.isShapeROI(pathROI)) {
Area areaMain = RoiTools.getArea(pathROI);
areaMain.subtract(hole);
pathROI = RoiTools.getShapeROI(areaMain, pathROI.getImagePlane());
pathObjects.set(ind, PathObjects.createAnnotationObject(pathROI));
}
}
}
// This is a clumsy way to do it...
if (singleAnnotation) {
ROI roi = null;
for (PathObject annotation : pathObjects) {
ROI currentShape = annotation.getROI();
if (roi == null)
roi = currentShape;
else
roi = RoiTools.combineROIs(roi, currentShape, CombineOp.ADD);
}
pathObjects.clear();
if (roi != null)
pathObjects.add(PathObjects.createAnnotationObject(roi));
}
// Lock the objects
for (PathObject pathObject : pathObjects) ((PathAnnotationObject) pathObject).setLocked(true);
return pathObjects;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class LabeledImageServer method createIndexedColorTile.
private BufferedImage createIndexedColorTile(TileRequest tileRequest, Collection<PathObject> pathObjects) {
RegionRequest request = tileRequest.getRegionRequest();
double downsampleFactor = request.getDownsample();
// Fill in the background color
int width = tileRequest.getTileWidth();
int height = tileRequest.getTileHeight();
boolean doRGB = maxLabel > 255;
// If we have > 255 labels, we can only use Graphics2D if we pretend to have an RGB image
BufferedImage img = doRGB ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) : new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
WritableRaster raster = img.getRaster();
Graphics2D g2d = img.createGraphics();
int bgLabel = params.labels.get(params.unannotatedClass);
Color color = getColorForLabel(bgLabel, doRGB);
g2d.setColor(color);
g2d.fillRect(0, 0, width, height);
if (!pathObjects.isEmpty()) {
g2d.setClip(0, 0, width, height);
double scale = 1.0 / downsampleFactor;
g2d.scale(scale, scale);
g2d.translate(-request.getX(), -request.getY());
BasicStroke stroke = new BasicStroke((float) (params.lineThickness * tileRequest.getDownsample()));
g2d.setStroke(stroke);
// We want to order consistently to avoid confusing overlaps
for (var entry : params.labels.entrySet()) {
var pathClass = getPathClass(entry.getKey());
int c = entry.getValue();
color = getColorForLabel(c, doRGB);
List<PathObject> toDraw;
if (instanceClassMapInverse != null) {
var temp = instanceClassMapInverse.get(c);
if (temp == null)
continue;
toDraw = Collections.singletonList(temp);
} else
toDraw = pathObjects.stream().filter(p -> getPathClass(p) == pathClass).collect(Collectors.toList());
for (var pathObject : toDraw) {
var roi = params.roiFunction.apply(pathObject);
g2d.setColor(color);
if (roi.isArea())
g2d.fill(roi.getShape());
else if (roi.isLine())
g2d.draw(roi.getShape());
else if (roi.isPoint()) {
for (var p : roi.getAllPoints()) {
int x = (int) ((p.getX() - request.getX()) / downsampleFactor);
int y = (int) ((p.getY() - request.getY()) / downsampleFactor);
if (x >= 0 && x < width && y >= 0 && y < height) {
if (doRGB)
img.setRGB(x, y, color.getRGB());
else
raster.setSample(x, y, 0, c);
}
}
}
}
}
for (var entry : params.boundaryLabels.entrySet()) {
int c = entry.getValue();
color = getColorForLabel(c, doRGB);
for (var pathObject : pathObjects) {
// if (pathObject.getPathClass() == pathClass) {
var pathClass = getPathClass(pathObject);
if (params.labels.containsKey(pathClass)) {
// && !PathClassTools.isIgnoredClass(pathObject.getPathClass())) {
var roi = params.roiFunction.apply(pathObject);
if (roi.isArea()) {
g2d.setColor(color);
g2d.draw(roi.getShape());
}
}
}
}
}
g2d.dispose();
if (doRGB) {
// Resort to RGB if we have to
if (maxLabel >= 65536)
return img;
// Convert to unsigned short if we can
var shortRaster = WritableRaster.createBandedRaster(DataBuffer.TYPE_USHORT, width, height, 1, null);
int[] samples = img.getRGB(0, 0, width, height, null, 0, width);
shortRaster.setSamples(0, 0, width, height, 0, samples);
// System.err.println("Before: " + Arrays.stream(samples).summaryStatistics());
raster = shortRaster;
// samples = raster.getSamples(0, 0, width, height, 0, (int[])null);
// System.err.println("After: " + Arrays.stream(samples).summaryStatistics());
}
return new BufferedImage((IndexColorModel) colorModel, raster, false, null);
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class PointIO method readPointsObjectFromString.
/**
* Helper method for readPointsObjectList(), will be removed in future releases.
* @param s
* @return
*/
@Deprecated
private static PathObject readPointsObjectFromString(String s) {
List<Point2> pointsList = new ArrayList<>();
Scanner scanner = new Scanner(s);
String name = scanner.nextLine().split("\t")[1].trim();
Integer color = Integer.parseInt(scanner.nextLine().split("\t")[1]);
// Skip the coordinate count line...
int count = Integer.parseInt(scanner.nextLine().split("\t")[1]);
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.length() == 0)
break;
String[] splits = line.split("\t");
double x = Double.parseDouble(splits[0]);
double y = Double.parseDouble(splits[1]);
pointsList.add(new Point2(x, y));
}
scanner.close();
if (count != pointsList.size())
logger.warn("Warning: {} points expected, {} points found", count, pointsList.size());
ROI points = ROIs.createPointsROI(pointsList, ImagePlane.getDefaultPlane());
PathObject pathObject = PathObjects.createAnnotationObject(points);
if (name != null && name.length() > 0 && !"null".equals(name))
pathObject.setName(name);
pathObject.setColorRGB(color);
return pathObject;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class PointIO method readPoints.
/**
* Read a list of point annotations from a stream.
* @param stream
* @return list of PathObjects
* @throws IOException
*/
public static List<PathObject> readPoints(InputStream stream) throws IOException {
List<PathObject> pathObjects = new ArrayList<>();
Map<String[], List<Point2>> pointsMap = new HashMap<>();
Scanner scanner = null;
String[] cols = null;
try {
scanner = new Scanner(stream);
// Header
cols = scanner.nextLine().split("\t");
while (scanner.hasNextLine()) {
putPointObjectFromString(scanner.nextLine(), cols, pointsMap);
}
} finally {
if (scanner != null)
scanner.close();
}
ImagePlane defaultPlane = ImagePlane.getDefaultPlane();
for (var entry : pointsMap.entrySet()) {
var temp = Arrays.asList(cols);
String pathClass = temp.indexOf("class") > -1 ? entry.getKey()[temp.indexOf("class") - 2] : "";
String name = temp.indexOf("name") > -1 ? entry.getKey()[temp.indexOf("name") - 2] : "";
Integer color = null;
if (temp.indexOf("color") > -1) {
var colorTemp = entry.getKey()[temp.indexOf("color") - 2];
if (colorTemp != null && !colorTemp.isEmpty())
color = Integer.parseInt(colorTemp);
}
int c = temp.indexOf("c") > defaultPlane.getC() ? Integer.parseInt(entry.getKey()[temp.indexOf("c") - 2]) : defaultPlane.getC();
int z = temp.indexOf("z") > defaultPlane.getZ() ? Integer.parseInt(entry.getKey()[temp.indexOf("z") - 2]) : defaultPlane.getZ();
int t = temp.indexOf("t") > defaultPlane.getT() ? Integer.parseInt(entry.getKey()[temp.indexOf("t") - 2]) : defaultPlane.getT();
ROI points = ROIs.createPointsROI(entry.getValue(), ImagePlane.getPlaneWithChannel(c, z, t));
PathObject pathObject = PathObjects.createAnnotationObject(points);
if (name != null && name.length() > 0 && !"null".equals(name))
pathObject.setName(name);
if (pathClass != null && pathClass.length() > 0 && !"null".equals(pathClass))
pathObject.setPathClass(PathClassFactory.getPathClass(pathClass, color));
pathObject.setColorRGB(color);
if (pathObject != null)
pathObjects.add(pathObject);
}
return pathObjects;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class PixelClassifierTools method createObjectsFromPixelClassifier.
/**
* Create objects based upon an {@link ImageServer} that provides classification or probability output.
*
* @param server image server providing pixels from which objects should be created
* @param labels classification labels; if null, these will be taken from ImageServer#getMetadata() and all non-ignored classifications will be used.
* Providing a map makes it possible to explicitly exclude some classifications.
* @param roi region of interest in which objects should be created (optional; if null, the entire image is used)
* @param creator function to create an object from a ROI (e.g. annotation or detection)
* @param minArea minimum area for an object fragment to retain, in calibrated units based on the pixel calibration
* @param minHoleArea minimum area for a hole to fill, in calibrated units based on the pixel calibration
* @param doSplit if true, split connected regions into separate objects
* @return the objects created within the ROI
* @throws IOException
*/
public static Collection<PathObject> createObjectsFromPixelClassifier(ImageServer<BufferedImage> server, Map<Integer, PathClass> labels, ROI roi, Function<ROI, ? extends PathObject> creator, double minArea, double minHoleArea, boolean doSplit) throws IOException {
// We need classification labels to do anything
if (labels == null)
labels = parseClassificationLabels(server.getMetadata().getClassificationLabels(), false);
if (labels == null || labels.isEmpty())
throw new IllegalArgumentException("Cannot create objects for server - no classification labels are available!");
ChannelThreshold[] thresholds = labels.entrySet().stream().map(e -> ChannelThreshold.create(e.getKey())).toArray(ChannelThreshold[]::new);
if (roi != null && !roi.isArea()) {
logger.warn("Cannot create objects for non-area ROIs");
return Collections.emptyList();
}
Geometry clipArea = roi == null ? null : roi.getGeometry();
// Identify regions for selected ROI or entire image
// This is a list because it might need to handle multiple z-slices or timepoints
List<RegionRequest> regionRequests;
if (roi != null) {
var request = RegionRequest.createInstance(server.getPath(), server.getDownsampleForResolution(0), roi);
regionRequests = Collections.singletonList(request);
} else {
regionRequests = RegionRequest.createAllRequests(server, server.getDownsampleForResolution(0));
}
double pixelArea = server.getPixelCalibration().getPixelWidth().doubleValue() * server.getPixelCalibration().getPixelHeight().doubleValue();
double minAreaPixels = minArea / pixelArea;
double minHoleAreaPixels = minHoleArea / pixelArea;
// Create output array
var pathObjects = new ArrayList<PathObject>();
// Loop through region requests (usually 1, unless we have a z-stack or time series)
for (RegionRequest regionRequest : regionRequests) {
Map<Integer, Geometry> geometryMap = ContourTracing.traceGeometries(server, regionRequest, clipArea, thresholds);
var labelMap = labels;
pathObjects.addAll(geometryMap.entrySet().parallelStream().flatMap(e -> geometryToObjects(e.getValue(), creator, labelMap.get(e.getKey()), minAreaPixels, minHoleAreaPixels, doSplit, regionRequest.getPlane()).stream()).collect(Collectors.toList()));
}
pathObjects.sort(DefaultPathObjectComparator.getInstance());
return pathObjects;
}
Aggregations