Search in sources :

Example 21 with PathObject

use of qupath.lib.objects.PathObject in project qupath by qupath.

the class EstimateStainVectorsCommand method promptToEstimateStainVectors.

public static void promptToEstimateStainVectors(ImageData<BufferedImage> imageData) {
    if (imageData == null) {
        Dialogs.showNoImageError(TITLE);
        return;
    }
    if (imageData == null || !imageData.isBrightfield() || imageData.getServer() == null || !imageData.getServer().isRGB()) {
        Dialogs.showErrorMessage(TITLE, "No brightfield, RGB image selected!");
        return;
    }
    ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
    if (stains == null || !stains.getStain(3).isResidual()) {
        Dialogs.showErrorMessage(TITLE, "Sorry, stain editing is only possible for brightfield, RGB images with 2 stains");
        return;
    }
    PathObject pathObject = imageData.getHierarchy().getSelectionModel().getSelectedObject();
    ROI roi = pathObject == null ? null : pathObject.getROI();
    if (roi == null)
        roi = ROIs.createRectangleROI(0, 0, imageData.getServer().getWidth(), imageData.getServer().getHeight(), ImagePlane.getDefaultPlane());
    double downsample = Math.max(1, Math.sqrt((roi.getBoundsWidth() * roi.getBoundsHeight()) / MAX_PIXELS));
    RegionRequest request = RegionRequest.createInstance(imageData.getServerPath(), downsample, roi);
    BufferedImage img = null;
    try {
        img = imageData.getServer().readBufferedImage(request);
    } catch (IOException e) {
        Dialogs.showErrorMessage("Estimate stain vectors", e);
        logger.error("Unable to obtain pixels for " + request.toString(), e);
    }
    // Apply small amount of smoothing to reduce compression artefacts
    img = EstimateStainVectors.smoothImage(img);
    // Check modes for background
    int[] rgb = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
    int[] rgbMode = EstimateStainVectors.getModeRGB(rgb);
    int rMax = rgbMode[0];
    int gMax = rgbMode[1];
    int bMax = rgbMode[2];
    // Check if the background values may need to be changed
    if (rMax != stains.getMaxRed() || gMax != stains.getMaxGreen() || bMax != stains.getMaxBlue()) {
        DialogButton response = Dialogs.showYesNoCancelDialog(TITLE, String.format("Modal RGB values %d, %d, %d do not match current background values - do you want to use the modal values?", rMax, gMax, bMax));
        if (response == DialogButton.CANCEL)
            return;
        else if (response == DialogButton.YES) {
            stains = stains.changeMaxValues(rMax, gMax, bMax);
            imageData.setColorDeconvolutionStains(stains);
        }
    }
    ColorDeconvolutionStains stainsUpdated = null;
    logger.info("Requesting region for stain vector editing: ", request);
    try {
        stainsUpdated = showStainEditor(img, stains);
    } catch (Exception e) {
        Dialogs.showErrorMessage(TITLE, "Error with stain estimation: " + e.getLocalizedMessage());
        logger.error("{}", e.getLocalizedMessage(), e);
        // JOptionPane.showMessageDialog(qupath.getFrame(), "Error with stain estimation: " + e.getLocalizedMessage(), "Estimate stain vectors", JOptionPane.ERROR_MESSAGE, null);
        return;
    }
    if (!stains.equals(stainsUpdated)) {
        String suggestedName;
        String collectiveNameBefore = stainsUpdated.getName();
        if (collectiveNameBefore.endsWith("default"))
            suggestedName = collectiveNameBefore.substring(0, collectiveNameBefore.lastIndexOf("default")) + "estimated";
        else
            suggestedName = collectiveNameBefore;
        String newName = Dialogs.showInputDialog(TITLE, "Set name for stain vectors", suggestedName);
        if (newName == null)
            return;
        if (!newName.isBlank())
            stainsUpdated = stainsUpdated.changeName(newName);
        imageData.setColorDeconvolutionStains(stainsUpdated);
    }
}
Also used : DialogButton(qupath.lib.gui.dialogs.Dialogs.DialogButton) PathObject(qupath.lib.objects.PathObject) IOException(java.io.IOException) ROI(qupath.lib.roi.interfaces.ROI) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains)

Example 22 with PathObject

use of qupath.lib.objects.PathObject in project qupath by qupath.

the class ParallelTileObject method resolveOverlaps.

/**
 * Request that the tile object attempts to resolve overlaps with its neighboring tiles.
 */
public synchronized void resolveOverlaps() {
    // // If we don't have any children, notify that the test is complete
    // if (!hasChildren()) {
    // for (ParallelTileObject pto : map.keySet())
    // pto.notifyTestComplete(this);
    // map.clear();
    // checkAllTestsComplete();
    // return;
    // }
    long startTime = System.currentTimeMillis();
    int nRemoved = 0;
    boolean preferNucleus = false;
    // If we do have children, loop through & perform tests
    Iterator<Entry<ParallelTileObject, Rectangle2D>> iterMap = map.entrySet().iterator();
    while (iterMap.hasNext()) {
        Entry<ParallelTileObject, Rectangle2D> entry = iterMap.next();
        // If the parallel tile object hasn't been processed yet, then just continue - nothing to compare
        ParallelTileObject pto = entry.getKey();
        if (!pto.isComplete())
            continue;
        ParallelTileObject first, second;
        // Choose a consistent order for the comparison
        if (this.getROI().getBoundsX() > pto.getROI().getBoundsX() || this.getROI().getBoundsY() > pto.getROI().getBoundsY()) {
            first = this;
            second = pto;
        } else {
            first = pto;
            second = this;
        }
        // ROI firstBounds = first.getROI();
        // ROI secondBounds = second.getROI();
        // Compare this object's lists with that object's list
        List<PathObject> listFirst = first.getObjectsForRegion(entry.getValue());
        List<PathObject> listSecond = second.getObjectsForRegion(entry.getValue());
        // Only need to compare potential overlaps if both lists are non-empty
        if (!listFirst.isEmpty() && !listSecond.isEmpty()) {
            Map<ROI, Geometry> cache = new HashMap<>();
            Iterator<PathObject> iterFirst = listFirst.iterator();
            while (iterFirst.hasNext()) {
                PathObject firstObject = iterFirst.next();
                ROI firstROI = PathObjectTools.getROI(firstObject, preferNucleus);
                ImageRegion firstRegion = ImageRegion.createInstance(firstROI);
                Geometry firstGeometry = null;
                double firstArea = Double.NaN;
                Iterator<PathObject> iterSecond = listSecond.iterator();
                while (iterSecond.hasNext()) {
                    PathObject secondObject = iterSecond.next();
                    ROI secondROI = PathObjectTools.getROI(secondObject, preferNucleus);
                    // Do quick overlap test
                    if (!firstRegion.intersects(secondROI.getBoundsX(), secondROI.getBoundsY(), secondROI.getBoundsWidth(), secondROI.getBoundsHeight()))
                        continue;
                    // Get geometries
                    if (firstGeometry == null) {
                        firstGeometry = firstROI.getGeometry();
                        firstArea = firstGeometry.getArea();
                    }
                    Geometry secondGeometry = cache.get(secondROI);
                    if (secondGeometry == null) {
                        secondGeometry = secondROI.getGeometry();
                        cache.put(secondROI, secondGeometry);
                    }
                    Geometry intersection;
                    try {
                        // Get the intersection
                        if (!firstGeometry.intersects(secondGeometry))
                            continue;
                        intersection = firstGeometry.intersection(secondGeometry);
                    } catch (Exception e) {
                        logger.warn("Error resolving overlaps: {}", e.getLocalizedMessage());
                        logger.debug(e.getLocalizedMessage(), e);
                        continue;
                    }
                    if (intersection.isEmpty())
                        continue;
                    // Check areas
                    double intersectionArea = intersection.getArea();
                    double secondArea = secondGeometry.getArea();
                    double threshold = 0.1;
                    if (firstArea >= secondArea) {
                        if (intersectionArea / secondArea > threshold) {
                            second.removePathObject(secondObject);
                            nRemoved++;
                        }
                    } else {
                        if (intersectionArea / firstArea > threshold) {
                            first.removePathObject(firstObject);
                            nRemoved++;
                            break;
                        }
                    }
                }
            }
        }
        // Remove the neighbor from the map
        iterMap.remove();
        pto.notifyTestComplete(this);
    }
    checkAllTestsComplete();
    long endTime = System.currentTimeMillis();
    logger.debug(String.format("Resolved %d overlaps: %.2f seconds", nRemoved, (endTime - startTime) / 1000.));
// logger.info(String.format("Resolved %d possible overlaps with %d iterations (tested %d of %d): %.2f seconds", nOverlaps, counter, detectedCounter-skipCounter, detectedCounter, (endTime2 - startTime2) / 1000.));
}
Also used : HashMap(java.util.HashMap) Rectangle2D(java.awt.geom.Rectangle2D) ImageRegion(qupath.lib.regions.ImageRegion) ROI(qupath.lib.roi.interfaces.ROI) Geometry(org.locationtech.jts.geom.Geometry) Entry(java.util.Map.Entry) PathObject(qupath.lib.objects.PathObject)

Example 23 with PathObject

use of qupath.lib.objects.PathObject in project qupath by qupath.

the class PathObjectTileCache method addToCache.

/**
 * Add a PathObject to the cache, optionally including children.
 *
 * @param pathObject
 * @param includeChildren
 */
private void addToCache(PathObject pathObject, boolean includeChildren, Class<? extends PathObject> limitToClass) {
    // If the cache isn't active, we can ignore this... it will be constructed when it is needed
    if (!isActive())
        return;
    if (pathObject.hasROI()) {
        Class<? extends PathObject> cls = pathObject.getClass();
        if (limitToClass == null || cls == limitToClass) {
            SpatialIndex mapObjects = map.get(cls);
            if (mapObjects == null) {
                mapObjects = createSpatialIndex();
                map.put(cls, mapObjects);
            }
            Envelope envelope = getEnvelope(pathObject);
            mapObjects.insert(envelope, pathObject);
        }
    }
    // Add the children
    if (includeChildren && !(pathObject instanceof TemporaryObject) && pathObject.hasChildren()) {
        for (PathObject child : pathObject.getChildObjectsAsArray()) addToCache(child, includeChildren, limitToClass);
    }
}
Also used : PathObject(qupath.lib.objects.PathObject) SpatialIndex(org.locationtech.jts.index.SpatialIndex) TemporaryObject(qupath.lib.objects.TemporaryObject) Envelope(org.locationtech.jts.geom.Envelope)

Example 24 with PathObject

use of qupath.lib.objects.PathObject in project qupath by qupath.

the class PathObjectTileCache method getObjectsForRegion.

// /**
// * Add a PathObject to the cache.  Child objects are not added.
// * @param pathObject
// */
// private void addToCache(PathObject pathObject) {
// addToCache(pathObject, false);
// }
/**
 * Get all the PathObjects stored in this cache of a specified type and having ROIs with bounds overlapping a specified region.
 * This does not guarantee that the ROI (which may not be rectangular) overlaps the region...
 * but a quick test is preferred over a more expensive one.
 * <p>
 * Note that pathObjects will be added to the collection provided, if there is one.
 * The same object will be added to this collection multiple times if it overlaps different tiles -
 * again in the interests of speed, no check is made.
 * However this can be addressed by using a Set as the collection.
 * <p>
 * If a collection is not provided, another Collection is created & used instead.
 *
 * @param cls a PathObject class, or null if all object types should be returned
 * @param region an image region, or null if all objects with ROIs should be return
 * @param pathObjects an (optional) existing collection to which PathObjects should be added
 * @param includeSubclasses true if subclasses of the specified class should be included
 * @return
 */
public Collection<PathObject> getObjectsForRegion(Class<? extends PathObject> cls, ImageRegion region, Collection<PathObject> pathObjects, boolean includeSubclasses) {
    ensureCacheConstructed();
    var envelope = region == null ? MAX_ENVELOPE : getEnvelope(region);
    int z = region == null ? -1 : region.getZ();
    int t = region == null ? -1 : region.getT();
    r.lock();
    try {
        // Iterate through all the classes, getting objects of the specified class or subclasses thereof
        for (Entry<Class<? extends PathObject>, SpatialIndex> entry : map.entrySet()) {
            if (cls == null || (includeSubclasses && cls.isAssignableFrom(entry.getKey())) || cls.isInstance(entry.getKey())) {
                if (entry.getValue() != null) {
                    var list = entry.getValue().query(envelope);
                    if (list.isEmpty())
                        continue;
                    if (pathObjects == null)
                        pathObjects = new HashSet<PathObject>();
                    // Add all objects that have a parent, i.e. might be in the hierarchy
                    for (PathObject pathObject : (List<PathObject>) list) {
                        var roi = pathObject.getROI();
                        if (roi == null || region == null || (roi.getZ() == z && roi.getT() == t)) {
                            if (pathObject.getParent() != null || pathObject.isRootObject()) {
                                if (envelope.intersects(getEnvelope(pathObject)))
                                    pathObjects.add(pathObject);
                            }
                        }
                    }
                }
            // pathObjects = entry.getValue().getObjectsForRegion(region, pathObjects);
            }
        }
        // logger.info("Objects for " + region + ": " + (pathObjects == null ? 0 : pathObjects.size()));
        if (pathObjects == null)
            return Collections.emptySet();
        return pathObjects;
    } finally {
        r.unlock();
    }
}
Also used : PathObject(qupath.lib.objects.PathObject) SpatialIndex(org.locationtech.jts.index.SpatialIndex) List(java.util.List) HashSet(java.util.HashSet)

Example 25 with PathObject

use of qupath.lib.objects.PathObject in project qupath by qupath.

the class PathObjectTileCache method hierarchyChanged.

// public synchronized Collection<PathObject> getObjectsForRegion(Class<? extends PathObject> cls, Rectangle region, Collection<PathObject> pathObjects) {
// ensureCacheConstructed();
// 
// if (pathObjects == null)
// pathObjects = new HashSet<>();
// 
// // Iterate through all the classes, getting objects of the specified class or subclasses thereof
// if (cls == null) {
// for (Class<? extends PathObject> tempClass : map.keySet())
// getObjectsForRegion(tempClass, region, pathObjects);
// return pathObjects;
// }
// 
// // Extract the map for the type
// PathObjectTileMap mapObjects = map.get(cls);
// if (mapObjects == null)
// return pathObjects;
// 
// // Get the objects
// return mapObjects.getObjectsForRegion(region, pathObjects);
// }
// @Override
// public void pathObjectChanged(PathObjectHierarchy pathObjectHierarchy, PathObject pathObject) {
// // Remove, then re-add the object - ignoring children (which shouldn't be changed, as no structural change is associated with this event)
// removeFromCache(pathObject, false);
// addToCache(pathObject, false);
// }
@Override
public void hierarchyChanged(final PathObjectHierarchyEvent event) {
    w.lock();
    try {
        boolean singleChange = event.getChangedObjects().size() == 1;
        PathObject singleObject = singleChange ? event.getChangedObjects().get(0) : null;
        if (singleChange && event.getEventType() == HierarchyEventType.ADDED) {
            removeFromCache(singleObject, false);
            addToCache(singleObject, false, singleObject.getClass());
        } else if (singleChange && event.getEventType() == HierarchyEventType.REMOVED) {
            removeFromCache(singleObject, false);
        } else if (event.getEventType() == HierarchyEventType.OTHER_STRUCTURE_CHANGE || event.getEventType() == HierarchyEventType.CHANGE_OTHER) {
            // System.err.println(event);
            if (!event.isChanging())
                resetCache();
        }
    } finally {
        w.unlock();
    }
// else if (event.getEventType() == HierarchyEventType.OBJECT_CHANGE)
// resetCache(); // TODO: Check if full change is necessary for object change events
}
Also used : PathObject(qupath.lib.objects.PathObject)

Aggregations

PathObject (qupath.lib.objects.PathObject)182 ArrayList (java.util.ArrayList)84 ROI (qupath.lib.roi.interfaces.ROI)74 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)61 List (java.util.List)48 BufferedImage (java.awt.image.BufferedImage)37 IOException (java.io.IOException)37 PathClass (qupath.lib.objects.classes.PathClass)37 Collectors (java.util.stream.Collectors)35 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)34 Map (java.util.Map)33 Logger (org.slf4j.Logger)33 LoggerFactory (org.slf4j.LoggerFactory)33 ImageData (qupath.lib.images.ImageData)31 TMACoreObject (qupath.lib.objects.TMACoreObject)31 Collection (java.util.Collection)29 Collections (java.util.Collections)29 HashMap (java.util.HashMap)28 PathObjectTools (qupath.lib.objects.PathObjectTools)26 Arrays (java.util.Arrays)25