Search in sources :

Example 81 with ROI

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;
}
Also used : SimpleModifiableImage(qupath.lib.analysis.images.SimpleModifiableImage) MeasurementList(qupath.lib.measurements.MeasurementList) ImmutableDimension(qupath.lib.geom.ImmutableDimension) ROI(qupath.lib.roi.interfaces.ROI) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage)

Example 82 with ROI

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;
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) ROI(qupath.lib.roi.interfaces.ROI) LinkedHashSet(java.util.LinkedHashSet) HashSet(java.util.HashSet)

Example 83 with ROI

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());
}
Also used : IntStream(java.util.stream.IntStream) Rectangle(java.awt.Rectangle) Area(java.awt.geom.Area) Rectangle2D(java.awt.geom.Rectangle2D) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) PathIterator(java.awt.geom.PathIterator) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) Point2(qupath.lib.geom.Point2) Ellipse2D(java.awt.geom.Ellipse2D) ImageRegion(qupath.lib.regions.ImageRegion) Map(java.util.Map) PreparedGeometry(org.locationtech.jts.geom.prep.PreparedGeometry) AffineTransformation(org.locationtech.jts.geom.util.AffineTransformation) Shape(java.awt.Shape) Line2D(java.awt.geom.Line2D) Logger(org.slf4j.Logger) Collection(java.util.Collection) AwtTools(qupath.lib.awt.common.AwtTools) AffineTransform(java.awt.geom.AffineTransform) Collectors(java.util.stream.Collectors) Path2D(java.awt.geom.Path2D) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) ImmutableDimension(qupath.lib.geom.ImmutableDimension) Collections(java.util.Collections) PreparedGeometryFactory(org.locationtech.jts.geom.prep.PreparedGeometryFactory) Rectangle2D(java.awt.geom.Rectangle2D) ROI(qupath.lib.roi.interfaces.ROI) LinkedHashMap(java.util.LinkedHashMap) PreparedGeometry(org.locationtech.jts.geom.prep.PreparedGeometry) Geometry(org.locationtech.jts.geom.Geometry) PreparedGeometry(org.locationtech.jts.geom.prep.PreparedGeometry)

Example 84 with ROI

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();
    }
}
Also used : PathObject(qupath.lib.objects.PathObject) Point2(qupath.lib.geom.Point2) ArrayList(java.util.ArrayList) IOException(java.io.IOException) ROI(qupath.lib.roi.interfaces.ROI) Test(org.junit.jupiter.api.Test)

Example 85 with ROI

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;
}
Also used : DialogButton(qupath.lib.gui.dialogs.Dialogs.DialogButton) PathObject(qupath.lib.objects.PathObject) PathROIObject(qupath.lib.objects.PathROIObject) PolylineROI(qupath.lib.roi.PolylineROI) ROI(qupath.lib.roi.interfaces.ROI)

Aggregations

ROI (qupath.lib.roi.interfaces.ROI)87 PathObject (qupath.lib.objects.PathObject)61 ArrayList (java.util.ArrayList)31 BufferedImage (java.awt.image.BufferedImage)24 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)24 IOException (java.io.IOException)20 RegionRequest (qupath.lib.regions.RegionRequest)19 List (java.util.List)17 Collectors (java.util.stream.Collectors)17 RectangleROI (qupath.lib.roi.RectangleROI)17 Logger (org.slf4j.Logger)16 LoggerFactory (org.slf4j.LoggerFactory)16 PolygonROI (qupath.lib.roi.PolygonROI)16 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)15 Point2D (java.awt.geom.Point2D)14 Collection (java.util.Collection)14 Collections (java.util.Collections)14 Geometry (org.locationtech.jts.geom.Geometry)14 PathClass (qupath.lib.objects.classes.PathClass)14 ImagePlane (qupath.lib.regions.ImagePlane)13