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);
}
}
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());
}
}
}
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);
}
}
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;
}
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;
}
Aggregations