use of qupath.lib.objects.PathObjects in project qupath by qupath.
the class PathIO method exportObjectsAsGeoJSON.
/**
* Export a collection of objects as a GeoJSON "FeatureCollection" to an output stream.
* @param stream
* @param pathObjects
* @param options
* @throws IOException
*/
public static void exportObjectsAsGeoJSON(OutputStream stream, Collection<? extends PathObject> pathObjects, GeoJsonExportOptions... options) throws IOException {
Collection<GeoJsonExportOptions> optionList = Arrays.asList(options);
// If exclude measurements, 'transform' each PathObject to get rid of measurements
if (optionList.contains(GeoJsonExportOptions.EXCLUDE_MEASUREMENTS))
pathObjects = pathObjects.stream().map(e -> PathObjectTools.transformObject(e, null, false)).collect(Collectors.toList());
var writer = new OutputStreamWriter(new BufferedOutputStream(stream), StandardCharsets.UTF_8);
var gson = GsonTools.getInstance(optionList.contains(GeoJsonExportOptions.PRETTY_JSON));
if (optionList.contains(GeoJsonExportOptions.FEATURE_COLLECTION))
gson.toJson(GsonTools.wrapFeatureCollection(pathObjects), writer);
else if (pathObjects.size() == 1) {
gson.toJson(pathObjects.iterator().next(), writer);
} else {
gson.toJson(pathObjects, new TypeToken<List<PathObject>>() {
}.getType(), writer);
}
writer.flush();
}
use of qupath.lib.objects.PathObjects in project qupath by qupath.
the class QP method makeInverseAnnotation.
/**
* Make an annotation, for which the ROI is obtained by subtracting the ROIs of the specified objects from the closest
* common ancestor ROI (or entire image if the closest ancestor is the root).
* <p>
* In an inverted annotation can be created, it is added to the hierarchy and set as selected.
*
* @param imageData the image containing the annotation
* @param pathObjects the annotation to invert
* @return true if an inverted annotation is added to the hierarchy, false otherwise.
*/
public static boolean makeInverseAnnotation(final ImageData<?> imageData, Collection<PathObject> pathObjects) {
if (imageData == null)
return false;
var map = pathObjects.stream().filter(p -> p.hasROI() && p.getROI().isArea()).collect(Collectors.groupingBy(p -> p.getROI().getImagePlane()));
if (map.isEmpty()) {
logger.warn("No area annotations available - cannot created inverse ROI!");
return false;
}
if (map.size() > 1) {
logger.error("Cannot merge annotations from different image planes!");
return false;
}
ImagePlane plane = map.keySet().iterator().next();
List<PathObject> pathObjectList = map.get(plane);
PathObjectHierarchy hierarchy = imageData.getHierarchy();
// Try to get the best candidate parent
Collection<PathObject> parentSet = pathObjectList.stream().map(p -> p.getParent()).collect(Collectors.toCollection(HashSet::new));
PathObject parent;
if (parentSet.size() > 1) {
parentSet.clear();
boolean firstTime = true;
for (PathObject temp : pathObjectList) {
if (firstTime)
parentSet.addAll(PathObjectTools.getAncestorList(temp));
else
parentSet.retainAll(PathObjectTools.getAncestorList(temp));
firstTime = false;
}
List<PathObject> parents = new ArrayList<>(parentSet);
Collections.sort(parents, Comparator.comparingInt(PathObject::getLevel).reversed().thenComparingDouble(p -> p.hasROI() ? p.getROI().getArea() : Double.MAX_VALUE));
parent = parents.get(0);
} else
parent = parentSet.iterator().next();
// Get the parent area
Geometry geometryParent;
if (parent == null || parent.isRootObject() || !parent.hasROI())
geometryParent = GeometryTools.createRectangle(0, 0, imageData.getServer().getWidth(), imageData.getServer().getHeight());
else
geometryParent = parent.getROI().getGeometry();
// Get the parent area to use
var union = GeometryTools.union(pathObjectList.stream().map(p -> p.getROI().getGeometry()).collect(Collectors.toList()));
var geometry = geometryParent.difference(union);
// Create the new ROI
ROI shapeNew = GeometryTools.geometryToROI(geometry, plane);
PathObject pathObjectNew = PathObjects.createAnnotationObject(shapeNew);
parent.addPathObject(pathObjectNew);
hierarchy.fireHierarchyChangedEvent(parent);
hierarchy.getSelectionModel().setSelectedObject(pathObjectNew);
return true;
}
use of qupath.lib.objects.PathObjects 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;
}
use of qupath.lib.objects.PathObjects in project qupath by qupath.
the class PixelClassifierTools method classifyObjectsByCentroid.
/**
* Apply classification from a server to a collection of objects.
*
* @param classifierServer an {@link ImageServer} with output type
* @param pathObjects
* @param preferNucleusROI
*/
public static void classifyObjectsByCentroid(ImageServer<BufferedImage> classifierServer, Collection<PathObject> pathObjects, boolean preferNucleusROI) {
var labels = classifierServer.getMetadata().getClassificationLabels();
var reclassifiers = pathObjects.parallelStream().map(p -> {
try {
var roi = PathObjectTools.getROI(p, preferNucleusROI);
int x = (int) roi.getCentroidX();
int y = (int) roi.getCentroidY();
int ind = getClassification(classifierServer, x, y, roi.getZ(), roi.getT());
return new Reclassifier(p, labels.getOrDefault(ind, null), false);
} catch (Exception e) {
return new Reclassifier(p, null, false);
}
}).collect(Collectors.toList());
reclassifiers.parallelStream().forEach(r -> r.apply());
}
use of qupath.lib.objects.PathObjects in project qupath by qupath.
the class Commands method combineAnnotations.
/**
* Combine all the annotations that overlap with a selected object.
* <p>
* The selected object should itself be an annotation.
*
* @param hierarchy
* @param pathObjects
* @param op
* @return true if any changes were made, false otherwise
*/
static boolean combineAnnotations(PathObjectHierarchy hierarchy, List<PathObject> pathObjects, RoiTools.CombineOp op) {
if (hierarchy == null || hierarchy.isEmpty() || pathObjects.isEmpty()) {
logger.warn("Combine annotations: Cannot combine - no annotations found");
return false;
}
pathObjects = new ArrayList<>(pathObjects);
PathObject pathObject = pathObjects.get(0);
if (!pathObject.isAnnotation()) {
// || !RoiTools.isShapeROI(pathObject.getROI())) {
logger.warn("Combine annotations: No annotation with ROI selected");
return false;
}
var plane = pathObject.getROI().getImagePlane();
// pathObjects.removeIf(p -> !RoiTools.isShapeROI(p.getROI())); // Remove any null or point ROIs, TODO: Consider supporting points
// Remove any null or point ROIs, TODO: Consider supporting points
pathObjects.removeIf(p -> !p.hasROI() || !p.getROI().getImagePlane().equals(plane));
if (pathObjects.isEmpty()) {
logger.warn("Cannot combine annotations - only one suitable annotation found");
return false;
}
var allROIs = pathObjects.stream().map(p -> p.getROI()).collect(Collectors.toCollection(() -> new ArrayList<>()));
ROI newROI;
switch(op) {
case ADD:
newROI = RoiTools.union(allROIs);
break;
case INTERSECT:
newROI = RoiTools.intersection(allROIs);
break;
case SUBTRACT:
var first = allROIs.remove(0);
newROI = RoiTools.combineROIs(first, RoiTools.union(allROIs), op);
break;
default:
throw new IllegalArgumentException("Unknown combine op " + op);
}
if (newROI == null) {
logger.debug("No changes were made");
return false;
}
PathObject newObject = null;
if (!newROI.isEmpty()) {
newObject = PathObjects.createAnnotationObject(newROI, pathObject.getPathClass());
newObject.setName(pathObject.getName());
newObject.setColorRGB(pathObject.getColorRGB());
}
// Remove previous objects
hierarchy.removeObjects(pathObjects, true);
if (newObject != null)
hierarchy.addPathObject(newObject);
return true;
}
Aggregations