Search in sources :

Example 11 with RegionRequest

use of qupath.lib.regions.RegionRequest in project qupath by qupath.

the class AbstractImageRegionStore method workerComplete.

/**
 * Every TileWorker should call this when the task is done!
 *
 * @param worker
 */
protected void workerComplete(final TileWorker<T> worker) {
    workers.remove(worker);
    manager.taskCompleted(worker);
    if (worker.isCancelled() || !stopWaiting(worker.getRequest())) {
        return;
    }
    try {
        T imgNew = worker.get();
        if (imgNew == null)
            return;
        RegionRequest request = worker.getRequest();
        worker.getRequestedCache().put(request, imgNew);
        // Notify listeners that we have a new tile, if desired
        List<TileListener<T>> myTileListeners = new ArrayList<>(tileListeners);
        for (TileListener<T> listener : myTileListeners) listener.tileAvailable(request.getPath(), request, imgNew);
    } catch (InterruptedException e) {
        logger.warn("Tile request interrupted", e);
    } catch (ExecutionException e) {
        logger.warn("Tile request exception", e);
    }
}
Also used : ArrayList(java.util.ArrayList) ExecutionException(java.util.concurrent.ExecutionException) RegionRequest(qupath.lib.regions.RegionRequest)

Example 12 with RegionRequest

use of qupath.lib.regions.RegionRequest in project qupath by qupath.

the class DefaultImageRegionStore method paintRegionInternal.

private void paintRegionInternal(ImageServer<BufferedImage> server, Graphics g, Shape clipShapeVisible, int zPosition, int tPosition, double downsampleFactor, BufferedImage imgThumbnail, ImageObserver observer, ImageRenderer imageDisplay) {
    // // We don't need it... but try to request the thumbnail to keep it present in the cache, if it is there
    // cache.get(getThumbnailRequest(server, zPosition, tPosition));
    // Check if we have all the regions required for this request
    List<RegionRequest> requests = ImageRegionStoreHelpers.getTilesToRequest(server, clipShapeVisible, downsampleFactor, zPosition, tPosition, null);
    // If we should be painting recursively, ending up with the thumbnail, do so
    if (imgThumbnail != null) {
        Rectangle missingBounds = null;
        for (RegionRequest request : requests) {
            // Load the image
            BufferedImage img = getCachedTile(server, request);
            if (img == null && !cache.containsKey(request)) {
                if (missingBounds == null)
                    missingBounds = AwtTools.getBounds(request);
                else
                    missingBounds = missingBounds.union(AwtTools.getBounds(request));
            }
        }
        // If we are missing regions, try (recursively) to repaint at a lower resolution
        if (missingBounds != null) {
            double[] preferredDownsamples = server.getPreferredDownsamples();
            Arrays.sort(preferredDownsamples);
            // -1 is the flag that indicates that we don't have a lower resolution to go to
            double nextDownsample = -1;
            for (double d : preferredDownsamples) {
                if (d > Math.max(downsampleFactor, 1)) {
                    nextDownsample = d;
                    break;
                }
            }
            // Get the next downsample level if we can
            if (nextDownsample > 0)
                // paintRegion(server, g, clipShapeVisible, zPosition, tPosition, nextDownsample, imgThumbnail, observer, imageDisplay);
                paintRegionInternal(server, g, missingBounds, zPosition, tPosition, nextDownsample, imgThumbnail, observer, imageDisplay);
            else if (imgThumbnail != null) {
                // The best we can do is paint the thumbnail
                if (imageDisplay != null) {
                    BufferedImage imgTemp = imageDisplay.applyTransforms(imgThumbnail, null);
                    imgThumbnail = imgTemp;
                }
                g.drawImage(imgThumbnail, 0, 0, server.getWidth(), server.getHeight(), observer);
            }
        }
    }
    // If we're compositing channels, it's worthwhile to cache RGB tiles for so long as the ImageDisplay remains constant
    // boolean useDisplayCache = imageDisplay != null && !server.isRGB() && server.nChannels() > 1;
    boolean useDisplayCache = server != null && !server.isRGB() && server.getMetadata().getChannelType() != ChannelType.CLASSIFICATION && (server.nChannels() > 1 || server.getPixelType() != PixelType.UINT8);
    long displayTimestamp = imageDisplay == null ? 0L : imageDisplay.getLastChangeTimestamp();
    String displayCachePath = null;
    if (useDisplayCache) {
        if (imageDisplay == null)
            displayCachePath = "RGB::" + server.getPath();
        else if (server != null)
            displayCachePath = server.getPath() + imageDisplay.getUniqueID();
    }
    // Loop through and draw whatever tiles we've got
    BufferedImage imgTemp = null;
    for (RegionRequest request : requests) {
        // Load the image
        BufferedImage img = getCachedRegion(server, request);
        // this can actually paint over previously-available regions, but they will be repainted again when this region's request comes through
        if (img == null)
            continue;
        // Apply any required color transformations
        if (imageDisplay != null || useDisplayCache) {
            // We can abort now - we know the display has changed, additional painting is futile...
            if (imageDisplay != null && displayTimestamp != imageDisplay.getLastChangeTimestamp())
                return;
            if (useDisplayCache) {
                // Apply transforms, creating & caching new temp images
                RegionRequest requestCache = RegionRequest.createInstance(displayCachePath, request.getDownsample(), request);
                imgTemp = cache.get(requestCache);
                if (imgTemp == null) {
                    if (imageDisplay != null)
                        imgTemp = imageDisplay.applyTransforms(img, null);
                    else {
                        imgTemp = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
                        Graphics2D g2d = imgTemp.createGraphics();
                        g2d.drawImage(img, 0, 0, null);
                        g2d.dispose();
                    }
                    // This avoids making the cache inconsistent
                    if (imgTemp != null && (imageDisplay == null || displayTimestamp == imageDisplay.getLastChangeTimestamp()))
                        cache.put(requestCache, imgTemp);
                    else
                        return;
                }
            } else {
                // Apply transforms, trying to reuse temp image
                if (imgTemp != null && (imgTemp.getWidth() != img.getWidth() || imgTemp.getHeight() != img.getHeight()))
                    imgTemp = null;
                if (imageDisplay != null)
                    imgTemp = imageDisplay.applyTransforms(img, imgTemp);
                else
                    imgTemp = img;
            }
            img = imgTemp;
        }
        // System.err.println(String.format("%dx%d, %.2f - %.2f", img.getWidth(), img.getHeight(), (double)request.getHeight()/img.getHeight(), request.getDownsample()));
        g.drawImage(img, request.getX(), request.getY(), request.getWidth(), request.getHeight(), observer);
        if (DEBUG_TILES) {
            g.setColor(Color.RED);
            g.drawRect(request.getX(), request.getY(), request.getWidth(), request.getHeight());
        }
    }
}
Also used : Rectangle(java.awt.Rectangle) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) Graphics2D(java.awt.Graphics2D)

Example 13 with RegionRequest

use of qupath.lib.regions.RegionRequest in project qupath by qupath.

the class DefaultImageRegionStore method paintRegionCompletely.

/**
 * Similar to paintRegion, but wait until all the tiles have arrived (or abort if it is taking too long)
 *
 * @param server
 * @param g
 * @param clipShapeVisible
 * @param zPosition
 * @param tPosition
 * @param downsampleFactor
 * @param observer
 * @param imageDisplay
 * @param timeoutMilliseconds Timeout after which a request is made from the PathImageServer directly, rather than waiting for tile requests.
 */
@Override
@SuppressWarnings("unchecked")
public void paintRegionCompletely(ImageServer<BufferedImage> server, Graphics g, Shape clipShapeVisible, int zPosition, int tPosition, double downsampleFactor, ImageObserver observer, ImageRenderer imageDisplay, long timeoutMilliseconds) {
    // if (downsampleFactor > 1)
    // ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    // else
    // ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
    // Loop through and create the image
    List<TileWorker<BufferedImage>> workers = new ArrayList<>();
    BufferedImage imgTemp = null;
    for (RegionRequest request : ImageRegionStoreHelpers.getTilesToRequest(server, clipShapeVisible, downsampleFactor, zPosition, tPosition, null)) {
        Object result = requestImageTile(server, request, cache, true);
        // If we have an image, paint it & record coordinates
        if (result instanceof BufferedImage) {
            if (imageDisplay != null) {
                imgTemp = imageDisplay.applyTransforms((BufferedImage) result, imgTemp);
                g.drawImage(imgTemp, request.getX(), request.getY(), request.getWidth(), request.getHeight(), observer);
            } else
                g.drawImage((BufferedImage) result, request.getX(), request.getY(), request.getWidth(), request.getHeight(), observer);
        } else if (result instanceof TileWorker) {
            // If we've a tile worker, prepare for requesting its results soon...
            // System.out.println(((TileWorker)result).getRegion());
            workers.add((TileWorker<BufferedImage>) result);
        }
    }
    // Loop through any workers now, drawing their tiles too
    for (TileWorker<BufferedImage> worker : workers) {
        BufferedImage imgTile = null;
        try {
            imgTile = worker.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
        } catch (CancellationException e) {
            logger.debug("Repaint skipped...");
            // e.printStackTrace();
            continue;
        } catch (InterruptedException e) {
            logger.warn("Tile request interrupted in 'paintRegionCompletely': {}", e.getLocalizedMessage());
            return;
        } catch (ExecutionException e) {
            logger.error("Execution exception in 'paintRegionCompletely'", e);
            return;
        } catch (TimeoutException e) {
            // If we timed out, try reading directly
            logger.warn("Timed out requesting region ({} ms)... {}", timeoutMilliseconds, worker.getRequest());
            RegionRequest request = worker.getRequest();
            if (server.isEmptyRegion(request))
                imgTile = null;
            else {
                if (worker.cancel(false)) {
                    try {
                        imgTile = server.readBufferedImage(request);
                        if (imgTile != null)
                            cache.put(request, imgTile);
                    } catch (IOException e1) {
                        logger.warn("Unable to read tile for " + request, e1);
                    }
                } else
                    try {
                        imgTile = worker.get();
                    } catch (InterruptedException e1) {
                        logger.warn("Tile request interrupted; {}", e1.getLocalizedMessage());
                    } catch (ExecutionException e1) {
                        logger.warn("Execution exception during tile request: {}", e1.getLocalizedMessage());
                    } catch (CancellationException e1) {
                        logger.warn("Tile request cancelled: {}", e1.getLocalizedMessage());
                    }
            }
        }
        if (imgTile == null)
            continue;
        RegionRequest request = worker.getRequest();
        if (imageDisplay != null) {
            imgTemp = imageDisplay.applyTransforms(imgTile, imgTemp);
            g.drawImage(imgTemp, request.getX(), request.getY(), request.getWidth(), request.getHeight(), observer);
        } else
            g.drawImage(imgTile, request.getX(), request.getY(), request.getWidth(), request.getHeight(), observer);
    }
}
Also used : CancellationException(java.util.concurrent.CancellationException) ArrayList(java.util.ArrayList) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) TimeoutException(java.util.concurrent.TimeoutException)

Example 14 with RegionRequest

use of qupath.lib.regions.RegionRequest in project qupath by qupath.

the class ImageRegionStoreHelpers method getTileRequest.

/**
 * Given an {@code ImageServer}, determine the boundaries of the image tile that contains specified x, y coordinates.
 * The downsampleFactor is used to determine the resolution at which to request the tiles.
 *
 * @param server the {@code ImageServer} from which the tiles would be requested
 * @param x
 * @param y
 * @param downsampleFactor	the downsampleFactor determining the resolution at which tiles should be requested
 * @param zPosition
 * @param tPosition
 * @return
 */
public static RegionRequest getTileRequest(ImageServer<BufferedImage> server, double x, double y, double downsampleFactor, int zPosition, int tPosition) {
    int serverWidth = server.getWidth();
    int serverHeight = server.getHeight();
    if (x < 0 || y < 0 || x >= serverWidth || y >= serverHeight)
        return null;
    double downsamplePreferred = ServerTools.getPreferredDownsampleFactor(server, downsampleFactor);
    // Determine what the tile size will be in the original image space for the requested downsample
    // Aim for a round number - preferred downsamples can be a bit off due to rounding
    int tileWidth = server.getMetadata().getPreferredTileWidth();
    int tileHeight = server.getMetadata().getPreferredTileHeight();
    if (tileWidth < 0)
        tileWidth = 256;
    if (tileHeight < 0)
        tileHeight = 256;
    // System.out.println("Tile size: " + tileWidth + ", " + tileHeight);
    int tileWidthForLevel;
    int tileHeightForLevel;
    if (GeneralTools.almostTheSame(downsamplePreferred, (int) (downsamplePreferred + .5), 0.001)) {
        tileWidthForLevel = (int) (tileWidth * (int) (downsamplePreferred + .5) + .5);
        tileHeightForLevel = (int) (tileHeight * (int) (downsamplePreferred + .5) + .5);
    } else {
        tileWidthForLevel = (int) (tileWidth * downsamplePreferred + .5);
        tileHeightForLevel = (int) (tileHeight * downsamplePreferred + .5);
    }
    // Get the starting indices, shifted to actual tile boundaries
    int xx = (int) (x / tileWidthForLevel) * tileWidthForLevel;
    int yy = (int) (y / tileHeightForLevel) * tileHeightForLevel;
    RegionRequest request = RegionRequest.createInstance(server.getPath(), downsamplePreferred, xx, yy, (int) Math.min(serverWidth, (xx + tileWidthForLevel)) - xx, (int) Math.min(serverHeight, (yy + tileHeightForLevel)) - yy, zPosition, tPosition);
    return request;
}
Also used : RegionRequest(qupath.lib.regions.RegionRequest)

Example 15 with RegionRequest

use of qupath.lib.regions.RegionRequest 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;
}
Also used : ImageServer(qupath.lib.images.servers.ImageServer) Arrays(java.util.Arrays) PathClassTools(qupath.lib.objects.classes.PathClassTools) LoggerFactory(org.slf4j.LoggerFactory) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ChannelThreshold(qupath.lib.analysis.images.ContourTracing.ChannelThreshold) Function(java.util.function.Function) ArrayList(java.util.ArrayList) ClassifierFunction(qupath.opencv.ml.pixel.PixelClassifiers.ClassifierFunction) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) Map(java.util.Map) Reclassifier(qupath.lib.objects.classes.Reclassifier) GeometryTools(qupath.lib.roi.GeometryTools) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) PathObjects(qupath.lib.objects.PathObjects) Collection(java.util.Collection) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) DefaultPathObjectComparator(qupath.lib.objects.DefaultPathObjectComparator) IOException(java.io.IOException) Collectors(java.util.stream.Collectors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) PixelClassificationImageServer(qupath.lib.classifiers.pixel.PixelClassificationImageServer) ColorModel(java.awt.image.ColorModel) ContourTracing(qupath.lib.analysis.images.ContourTracing) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) Comparator(java.util.Comparator) Collections(java.util.Collections) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) DataBuffer(java.awt.image.DataBuffer) Geometry(org.locationtech.jts.geom.Geometry) ArrayList(java.util.ArrayList) ChannelThreshold(qupath.lib.analysis.images.ContourTracing.ChannelThreshold) RegionRequest(qupath.lib.regions.RegionRequest)

Aggregations

RegionRequest (qupath.lib.regions.RegionRequest)37 BufferedImage (java.awt.image.BufferedImage)29 IOException (java.io.IOException)18 ROI (qupath.lib.roi.interfaces.ROI)15 ArrayList (java.util.ArrayList)13 PathObject (qupath.lib.objects.PathObject)10 List (java.util.List)9 Collection (java.util.Collection)8 Collectors (java.util.stream.Collectors)8 PixelCalibration (qupath.lib.images.servers.PixelCalibration)8 Collections (java.util.Collections)7 TMACoreObject (qupath.lib.objects.TMACoreObject)7 Graphics2D (java.awt.Graphics2D)6 Logger (org.slf4j.Logger)6 LoggerFactory (org.slf4j.LoggerFactory)6 ImageData (qupath.lib.images.ImageData)6 ImageServer (qupath.lib.images.servers.ImageServer)6 PathObjectTools (qupath.lib.objects.PathObjectTools)6 LinkedHashMap (java.util.LinkedHashMap)5 Map (java.util.Map)5