use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class LocalBinaryPatternsPlugin method processObject.
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageServer<BufferedImage> server, final ColorDeconvolutionStains stains) throws InterruptedException, IOException {
String stainsName = (String) params.getChoiceParameterValue("stainChoice");
double mag = params.getDoubleParameterValue("magnification");
// int d = params.getIntParameterValue("haralickDistance");
boolean includeStats = params.getBooleanParameterValue("includeStats");
boolean doCircular = params.getBooleanParameterValue("doCircular");
double downsample = server.getMetadata().getMagnification() / mag;
ROI pathROI = pathObject.getROI();
if (pathROI == null)
return false;
// Get bounds
ImmutableDimension size = getPreferredTileSizePixels(server, params);
if (size.getWidth() / downsample < 1 || size.getHeight() / downsample < 1)
return false;
RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int) (pathROI.getCentroidX() + .5) - size.width / 2, (int) (pathROI.getCentroidY() + .5) - size.height / 2, size.width, size.height, pathROI.getT(), pathROI.getZ());
// System.out.println(bounds);
// System.out.println("Size: " + size);
BufferedImage img = server.readBufferedImage(region);
// System.out.println("Image size: " + img.getWidth() + " x " + img.getHeight() + " pixels");
// Get a buffer containing the image pixels
int w = img.getWidth();
int h = img.getHeight();
int[] buf = img.getRGB(0, 0, w, h, null, 0, w);
// Create a color transformer to get the images we need
float[] pixels = new float[buf.length];
SimpleModifiableImage pxImg = SimpleImages.createFloatImage(pixels, w, h);
MeasurementList measurementList = pathObject.getMeasurementList();
String postfix = " (" + getDiameterString(server, params) + ")";
if (stainsName.equals("H-DAB")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB, stains, includeStats, doCircular);
} else if (stainsName.equals("H&E")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E, stains, includeStats, doCircular);
} else if (stainsName.equals("H-DAB (8-bit)")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB_8_bit, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB 8-bit" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB_8_bit, stains, includeStats, doCircular);
} else if (stainsName.equals("H&E (8-bit)")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E_8_bit, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E_8_bit, stains, includeStats, doCircular);
} else if (stainsName.equals("Optical density")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "OD sum" + postfix, ColorTransformer.ColorTransformMethod.Optical_density_sum, stains, includeStats, doCircular);
} else if (stainsName.equals("RGB")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Red" + postfix, ColorTransformer.ColorTransformMethod.Red, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Green" + postfix, ColorTransformer.ColorTransformMethod.Green, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Blue" + postfix, ColorTransformer.ColorTransformMethod.Blue, stains, includeStats, doCircular);
} else if (stainsName.equals("Grayscale")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Grayscale" + postfix, ColorTransformer.ColorTransformMethod.RGB_mean, stains, includeStats, doCircular);
}
measurementList.close();
return true;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class QP method mergeAnnotations.
/**
* Merge the specified annotations to create a new annotation containing the union of their ROIs.
* <p>
* Note:
* <ul>
* <li>The existing annotations will be removed from the hierarchy if possible, therefore should be duplicated first
* if this is not desired.</li>
* <li>The new object will be set to be the selected object in the hierarchy (which can be used to retrieve it if needed).</li>
* </ul>
*
* @param hierarchy
* @param annotations
* @return true if changes are made to the hierarchy, false otherwise
*/
public static boolean mergeAnnotations(final PathObjectHierarchy hierarchy, final Collection<PathObject> annotations) {
if (hierarchy == null)
return false;
// Get all the selected annotations with area
ROI shapeNew = null;
List<PathObject> merged = new ArrayList<>();
Set<PathClass> pathClasses = new HashSet<>();
for (PathObject annotation : annotations) {
if (annotation.isAnnotation() && annotation.hasROI() && (annotation.getROI().isArea() || annotation.getROI().isPoint())) {
if (shapeNew == null)
// .duplicate();
shapeNew = annotation.getROI();
else if (shapeNew.getImagePlane().equals(annotation.getROI().getImagePlane()))
shapeNew = RoiTools.combineROIs(shapeNew, annotation.getROI(), RoiTools.CombineOp.ADD);
else {
logger.warn("Cannot merge ROIs across different image planes!");
return false;
}
if (annotation.getPathClass() != null)
pathClasses.add(annotation.getPathClass());
merged.add(annotation);
}
}
// Check if we actually merged anything
if (merged.isEmpty() || merged.size() == 1)
return false;
// Create and add the new object, removing the old ones
PathObject pathObjectNew = PathObjects.createAnnotationObject(shapeNew);
if (pathClasses.size() == 1)
pathObjectNew.setPathClass(pathClasses.iterator().next());
else
logger.warn("Cannot assign class unambiguously - " + pathClasses.size() + " classes represented in selection");
hierarchy.removeObjects(merged, true);
hierarchy.addPathObject(pathObjectNew);
hierarchy.getSelectionModel().setSelectedObject(pathObjectNew);
// hierarchy.fireHierarchyChangedEvent(pathObject);
return true;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class RoiTools method computeTiledROIs.
/**
* Create a collection of tiled ROIs corresponding to a specified parentROI if it is larger than sizeMax, with optional overlaps.
* <p>
* The purpose of this is to create useful tiles whenever the exact tile size may not be essential, and overlaps may be required.
* Tiles at the parentROI boundary will be trimmed to fit inside. If the parentROI is smaller, it is returned as is.
*
* @param parentROI main ROI to be tiled
* @param sizePreferred the preferred size; in general tiles should have this size
* @param sizeMax the maximum allowed size; occasionally it is more efficient to have a tile larger than the preferred size towards a ROI boundary to avoid creating very small tiles unnecessarily
* @param fixedSize if true, the tile size is enforced so that complete tiles have the same size
* @param overlap optional requested overlap between tiles
* @return
*
* @see #makeTiles(ROI, int, int, boolean)
*/
public static Collection<? extends ROI> computeTiledROIs(ROI parentROI, ImmutableDimension sizePreferred, ImmutableDimension sizeMax, boolean fixedSize, int overlap) {
ROI pathArea = parentROI != null && parentROI.isArea() ? parentROI : null;
Rectangle2D bounds = AwtTools.getBounds2D(parentROI);
if (pathArea == null || (bounds.getWidth() <= sizeMax.width && bounds.getHeight() <= sizeMax.height)) {
return Collections.singletonList(parentROI);
}
Geometry geometry = pathArea.getGeometry();
PreparedGeometry prepared = null;
double xMin = bounds.getMinX();
double yMin = bounds.getMinY();
int nx = (int) Math.ceil(bounds.getWidth() / sizePreferred.width);
int ny = (int) Math.ceil(bounds.getHeight() / sizePreferred.height);
double w = fixedSize ? sizePreferred.width : (int) Math.ceil(bounds.getWidth() / nx);
double h = fixedSize ? sizePreferred.height : (int) Math.ceil(bounds.getHeight() / ny);
// Center the tiles
xMin = (int) (bounds.getCenterX() - (nx * w * .5));
yMin = (int) (bounds.getCenterY() - (ny * h * .5));
// This can be very slow if we have an extremely large number of vertices/tiles.
// For that reason, we try to split initially by either rows or columns if needed.
boolean byRow = false;
boolean byColumn = false;
Map<Integer, Geometry> rowParents = null;
Map<Integer, Geometry> columnParents = null;
var envelope = geometry.getEnvelopeInternal();
if (ny > 1 && nx > 1 && geometry.getNumPoints() > 1000) {
// If we have a lot of points, create a prepared geometry so we can check covers/intersects quickly;
// (for a regular geometry, it would be faster to just compute an intersection and see if it's empty)
prepared = PreparedGeometryFactory.prepare(geometry);
var prepared2 = prepared;
var empty = geometry.getFactory().createEmpty(2);
byRow = nx > ny;
byColumn = !byRow;
double yMin2 = yMin;
double xMin2 = xMin;
// Compute intersection by row so that later intersections are simplified
if (byRow) {
rowParents = IntStream.range(0, ny).parallel().mapToObj(yi -> yi).collect(Collectors.toMap(yi -> yi, yi -> {
double y = yMin2 + yi * h - overlap;
var row = GeometryTools.createRectangle(envelope.getMinX(), y, envelope.getMaxX(), h + overlap * 2);
if (!prepared2.intersects(row))
return empty;
else if (prepared2.covers(row))
return row;
var temp = intersect(geometry, row);
return temp == null ? geometry : temp;
}));
}
if (byColumn) {
columnParents = IntStream.range(0, nx).parallel().mapToObj(xi -> xi).collect(Collectors.toMap(xi -> xi, xi -> {
double x = xMin2 + xi * w - overlap;
var col = GeometryTools.createRectangle(x, envelope.getMinY(), w + overlap * 2, envelope.getMaxX());
if (!prepared2.intersects(col))
return empty;
else if (prepared2.covers(col))
return col;
var temp = intersect(geometry, col);
return temp == null ? geometry : temp;
}));
}
}
// Geometry local is the one we're working with for the current row or column
// (often it's the same as the full ROI)
Geometry geometryLocal = geometry;
// Generate all the rectangles as geometries
Map<Geometry, Geometry> tileGeometries = new LinkedHashMap<>();
for (int yi = 0; yi < ny; yi++) {
double y = yMin + yi * h - overlap;
if (rowParents != null)
geometryLocal = rowParents.getOrDefault(y, geometry);
for (int xi = 0; xi < nx; xi++) {
double x = xMin + xi * w - overlap;
if (columnParents != null)
geometryLocal = columnParents.getOrDefault(x, geometry);
if (geometryLocal.isEmpty())
continue;
// Create the tile
var rect = GeometryTools.createRectangle(x, y, w + overlap * 2, h + overlap * 2);
// Use a prepared geometry if we have one to check covers/intersects & save some effort
if (prepared != null) {
if (!prepared.intersects(rect)) {
continue;
} else if (prepared.covers(rect)) {
tileGeometries.put(rect, rect);
continue;
}
}
// Checking geometryLocal.intersects(rect) first is actually much slower!
// So add everything and filter out empty tiles later.
tileGeometries.put(rect, geometryLocal);
}
}
// Compute intersections & map to ROIs
var plane = parentROI.getImagePlane();
var tileROIs = tileGeometries.entrySet().parallelStream().map(entry -> intersect(entry.getKey(), entry.getValue())).filter(g -> g != null).map(g -> GeometryTools.geometryToROI(g, plane)).collect(Collectors.toList());
// If there was an exception, the tile will be null
if (tileROIs.size() < tileGeometries.size()) {
logger.warn("Tiles lost during tiling: {}", tileGeometries.size() - tileROIs.size());
logger.warn("You may be able to avoid tiling errors by calling 'Simplify shape' on any complex annotations first.");
}
// Remove any empty/non-area tiles
return tileROIs.stream().filter(t -> !t.isEmpty() && t.isArea()).collect(Collectors.toList());
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class PointIOTest method test1WritePoints.
/**
* Uses the map generated in init() method and writes all its points down to a TSV file.
*/
@Test
public void test1WritePoints() {
List<PathObject> pathObjects = new ArrayList<>();
Integer[] colors = new Integer[] { -14336, -13487566, null, -3342337, -1305168 };
for (var entry : map.entrySet()) {
ArrayList<Point2> pointsList = new ArrayList<>();
int c = entry.getKey() == 2 ? 2 : -1;
int z = entry.getKey() == 2 ? 1 : 0;
int t = entry.getKey() == 2 ? 7 : 0;
for (var coord : entry.getValue()) {
pointsList.add(new Point2(coord[0], coord[1]));
}
ROI points = ROIs.createPointsROI(pointsList, ImagePlane.getPlaneWithChannel(c, z, t));
PathObject pathObject = PathObjects.createAnnotationObject(points);
if (entry.getKey() == 3)
pathObject.setPathClass(PathClassFactory.getPathClass("Other"));
if (entry.getKey() == 4)
pathObject.setName("foo");
else if (entry.getKey() == 5) {
pathObject.setPathClass(PathClassFactory.getPathClass("Tumor"));
pathObject.setName("bar");
}
pathObject.setColorRGB(colors[entry.getKey() - 1]);
pathObjects.add(pathObject);
}
try {
file = File.createTempFile("tmp", ".tsv");
PointIO.writePoints(file, pathObjects);
} catch (IOException e) {
fail();
}
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class RigidObjectEditorCommand method commitChanges.
private void commitChanges(final boolean ignoreChanges) {
if (this.originalObject == null)
return;
// PathObject pathObject = null;
if (!ignoreChanges) {
DialogButton option = Dialogs.showYesNoCancelDialog("Affine object editing", "Confirm object changes?");
if (option == DialogButton.CANCEL)
return;
if (option == DialogButton.NO) {
for (Entry<PathObject, ROI> entry : originalObjectROIs.entrySet()) ((PathROIObject) entry.getKey()).setROI(entry.getValue());
} else {
var transform = transformer.transform;
var values = transform.getMatrixEntries();
logger.info("Applied ROI transform: {}", String.format("\n %f, %f, %f,\n%f, %f, %f", values[0], values[1], values[2], values[3], values[4], values[5]));
// Apply clipping now
for (Entry<PathObject, ROI> entry : originalObjectROIs.entrySet()) {
ROI roiTransformed = transformer.getTransformedROI(entry.getValue(), true);
((PathROIObject) entry.getKey()).setROI(roiTransformed);
}
viewer.getHierarchy().fireHierarchyChangedEvent(this, originalObject);
}
}
// Update the mode if the viewer is still active
qupath.setToolSwitchingEnabled(true);
if (viewer == qupath.getViewer())
viewer.setActiveTool(qupath.getSelectedTool());
viewer.getView().removeEventHandler(MouseEvent.ANY, mouseListener);
viewer.getCustomOverlayLayers().remove(overlay);
viewer.removeViewerListener(this);
// if (pathObject != null)
// viewer.getHierarchy().addPathObject(pathObject, true);
// // Ensure the object is selected
// viewer.setSelectedObject(pathObject);
viewer = null;
overlay = null;
originalObjectROIs.clear();
originalObject = null;
transformer = null;
}
Aggregations