Search in sources :

Example 1 with LabeledImageServer

use of qupath.lib.images.servers.LabeledImageServer in project qupath by qupath.

the class TileExporter method writeTiles.

/**
 * Export the image tiles to the specified directory.
 * @param dirOutput full path to the export directory
 * @throws IOException if an error occurs during export
 */
public void writeTiles(String dirOutput) throws IOException {
    if (!new File(dirOutput).isDirectory())
        throw new IOException("Output directory " + dirOutput + " does not exist!");
    // Make sure we have any required subdirectories
    if (imageSubDir != null)
        new File(dirOutput, imageSubDir).mkdirs();
    if (labelSubDir != null)
        new File(dirOutput, labelSubDir).mkdirs();
    if (serverLabeled != null) {
        if (extLabeled == null)
            extLabeled = serverLabeled.getMetadata().getChannelType() == ChannelType.CLASSIFICATION ? ".png" : ".tif";
    }
    // Work out which RegionRequests to use
    Collection<RegionRequestWrapper> requests = createRequests();
    if (requests.isEmpty()) {
        logger.warn("No regions to export!");
        return;
    }
    if (requests.size() > 1)
        logger.info("Exporting {} tiles", requests.size());
    var pool = Executors.newFixedThreadPool(ThreadTools.getParallelism(), ThreadTools.createThreadFactory("tile-exporter", true));
    String imageName = GeneralTools.stripInvalidFilenameChars(GeneralTools.getNameWithoutExtension(server.getMetadata().getName()));
    // Create something we can input as the image path for export
    String imagePathName = null;
    var uris = server.getURIs();
    if (uris.isEmpty())
        imagePathName = imageName;
    else if (uris.size() == 1)
        imagePathName = uris.iterator().next().toString();
    else
        imagePathName = "[" + uris.stream().map(u -> u.toString()).collect(Collectors.joining("|")) + "]";
    // // If we have pixel calibration information, use it in the export
    // PixelCalibration pixelSize = server.getPixelCalibration();
    // if (pixelSize.equals(PixelCalibration.getDefaultInstance()))
    // pixelSize = null;
    // else
    // pixelSize = pixelSize.createScaledInstance(downsample, downsample);
    int tileWidth = this.tileWidth;
    int tileHeight = this.tileHeight;
    // int tileWidth = includePartialTiles || (parentObjects != null && useParentRoiBounds) ? -1 : this.tileWidth;
    // int tileHeight = includePartialTiles || (parentObjects != null && useParentRoiBounds) ? -1 : this.tileHeight;
    // Maintain a record of what we exported
    List<TileExportEntry> exportImages = new ArrayList<>();
    for (var r : requests) {
        boolean ensureSize = !r.partialTile;
        String baseName = String.format("%s [%s]", imageName, getRegionString(r.request));
        String exportImageName = baseName + ext;
        if (imageSubDir != null)
            exportImageName = Paths.get(imageSubDir, exportImageName).toString();
        String pathImageOutput = Paths.get(dirOutput, exportImageName).toAbsolutePath().toString();
        ExportTask taskImage = new ExportTask(server, r.request, pathImageOutput, tileWidth, tileHeight, ensureSize);
        String exportLabelName = null;
        ExportTask taskLabels = null;
        if (serverLabeled != null) {
            String labelName = baseName;
            if ((labelSubDir == null || labelSubDir.equals(imageSubDir)) && labelId == null && ext.equals(extLabeled)) {
                labelName = baseName + "-labelled";
            } else if (labelId != null)
                labelName = baseName + labelId;
            exportLabelName = labelName + extLabeled;
            if (labelSubDir != null)
                exportLabelName = Paths.get(labelSubDir, exportLabelName).toString();
            String pathLabelsOutput = Paths.get(dirOutput, exportLabelName).toAbsolutePath().toString();
            taskLabels = new ExportTask(serverLabeled, r.request.updatePath(serverLabeled.getPath()), pathLabelsOutput, tileWidth, tileHeight, ensureSize);
        }
        exportImages.add(new TileExportEntry(r.request.updatePath(imagePathName), // pixelSize,
        exportImageName, exportLabelName));
        if (taskImage != null)
            pool.submit(taskImage);
        if (taskLabels != null) {
            pool.submit(taskLabels);
        }
    }
    // Write JSON, if we need to
    if (exportJson) {
        var gson = GsonTools.getInstance(true).newBuilder().disableHtmlEscaping().create();
        var data = new TileExportData(dirOutput, exportImages);
        if (serverLabeled instanceof LabeledImageServer) {
            var labels = ((LabeledImageServer) serverLabeled).getLabels();
            var boundaryLabels = ((LabeledImageServer) serverLabeled).getBoundaryLabels();
            List<TileExportLabel> labelList = new ArrayList<>();
            Set<PathClass> existingLabels = new HashSet<>();
            for (var entry : labels.entrySet()) {
                var pathClass = entry.getKey();
                var label = new TileExportLabel(pathClass.toString(), entry.getValue(), boundaryLabels.getOrDefault(pathClass, null));
                labelList.add(label);
            }
            for (var entry : boundaryLabels.entrySet()) {
                var pathClass = entry.getKey();
                if (!existingLabels.contains(pathClass)) {
                    var label = new TileExportLabel(pathClass.toString(), null, boundaryLabels.getOrDefault(pathClass, null));
                    labelList.add(label);
                }
            }
            data.labels = labelList;
        }
        var pathJson = Paths.get(dirOutput, imageName + "-tiles.json");
        if (Files.exists(pathJson)) {
            logger.warn("Overwriting existing JSON file {}", pathJson);
        }
        try (var writer = Files.newBufferedWriter(pathJson, StandardCharsets.UTF_8)) {
            gson.toJson(data, writer);
        }
    }
    pool.shutdown();
    try {
        pool.awaitTermination(24, TimeUnit.HOURS);
    } catch (InterruptedException e) {
        pool.shutdownNow();
        logger.error("Tile export interrupted: {}", e);
        logger.error("", e);
    }
}
Also used : ArrayList(java.util.ArrayList) IOException(java.io.IOException) LabeledImageServer(qupath.lib.images.servers.LabeledImageServer) PathClass(qupath.lib.objects.classes.PathClass) File(java.io.File) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

Example 2 with LabeledImageServer

use of qupath.lib.images.servers.LabeledImageServer in project qupath by qupath.

the class TileExporter method readFixedSizeRegion.

private static BufferedImage readFixedSizeRegion(ImageServer<BufferedImage> server, RegionRequest request, int width, int height) throws IOException {
    BufferedImage img;
    double xProp = 0, yProp = 0;
    if (request.getX() >= 0 && request.getY() >= 0 && request.getMaxX() <= server.getWidth() && request.getMaxY() <= server.getHeight()) {
        img = server.readBufferedImage(request);
    } else {
        int x = GeneralTools.clipValue(request.getMinX(), 0, server.getWidth());
        int x2 = GeneralTools.clipValue(request.getMaxX(), 0, server.getWidth());
        int y = GeneralTools.clipValue(request.getMinY(), 0, server.getHeight());
        int y2 = GeneralTools.clipValue(request.getMaxY(), 0, server.getHeight());
        double downsample = request.getDownsample();
        var request2 = RegionRequest.createInstance(server.getPath(), downsample, x, y, x2 - x, y2 - y, request.getPlane());
        img = server.readBufferedImage(request2);
        // Pad if required
        if (height > img.getHeight() || width > img.getWidth()) {
            // Calculate relative amount of padding for left and top
            xProp = calculateFirstPadProportion(request.getMinX(), request.getMaxX(), 0, server.getWidth());
            yProp = calculateFirstPadProportion(request.getMinY(), request.getMaxY(), 0, server.getHeight());
        }
        img = cropOrPad(img, width, height, xProp, yProp);
    }
    // if image is a label map, use nearest neighbors interpolation to ensure that no new values (outside the labels) are created
    boolean smoothInterpolate = true;
    if ((img.getColorModel() instanceof IndexColorModel) || (server instanceof LabeledImageServer)) {
        smoothInterpolate = false;
    }
    return BufferedImageTools.resize(img, width, height, smoothInterpolate);
}
Also used : LabeledImageServer(qupath.lib.images.servers.LabeledImageServer) BufferedImage(java.awt.image.BufferedImage) IndexColorModel(java.awt.image.IndexColorModel)

Example 3 with LabeledImageServer

use of qupath.lib.images.servers.LabeledImageServer in project qupath by qupath.

the class TileExporter method createRequests.

/**
 * Create region requests, along with information about whether we have a partial tile (which should not be resized/padded) or not.
 * @return
 */
private Collection<RegionRequestWrapper> createRequests() {
    List<RegionRequestWrapper> requests = new ArrayList<>();
    // Work out which RegionRequests to use
    // If the downsample hasn't been specified, use the level 0 resolution
    double downsample = this.downsample;
    if (downsample <= 0) {
        downsample = server.getDownsampleForResolution(0);
        if (this.downsample < 0)
            logger.warn("Invalid downsample {}, I will use the level 0 downsample {}", this.downsample, downsample);
        else
            logger.debug("Using level 0 downsample {}", downsample);
    }
    if (parentObjects == null)
        requests.addAll(getTiledRegionRequests(downsample));
    else {
        for (var parent : parentObjects) {
            int w = (int) Math.ceil(tileWidth * downsample);
            int h = (int) Math.ceil(tileHeight * downsample);
            if (parent.isRootObject()) {
                for (int t = 0; t < server.nTimepoints(); t++) {
                    for (int z = 0; z < server.nZSlices(); z++) {
                        RegionRequest newRequest;
                        if (useParentRoiBounds) {
                            newRequest = RegionRequest.createInstance(server.getPath(), downsample, 0, 0, server.getWidth(), server.getHeight(), z, t);
                        } else {
                            int x = (int) Math.floor(server.getWidth() / 2.0 - w / 2.0);
                            int y = (int) Math.floor(server.getHeight() / 2.0 - h / 2.0);
                            newRequest = RegionRequest.createInstance(server.getPath(), downsample, x, y, w, h, z, t);
                        }
                        if (includePartialTiles || withinImage(newRequest, server))
                            requests.add(new RegionRequestWrapper(newRequest, false));
                    }
                }
            } else if (parent.hasROI()) {
                RegionRequest newRequest;
                var roi = PathObjectTools.getROI(parent, preferNucleus);
                if (useParentRoiBounds) {
                    newRequest = RegionRequest.createInstance(server.getPath(), downsample, roi);
                } else {
                    int x = (int) Math.floor(roi.getCentroidX() - w / 2.0);
                    int y = (int) Math.floor(roi.getCentroidY() - h / 2.0);
                    newRequest = RegionRequest.createInstance(server.getPath(), downsample, x, y, w, h, roi.getImagePlane());
                }
                if (includePartialTiles || withinImage(newRequest, server))
                    requests.add(new RegionRequestWrapper(newRequest, false));
            }
        }
    }
    // If we want only annotated tiles, skip regions that lack annotations
    var iterator = requests.iterator();
    while (iterator.hasNext()) {
        var r = iterator.next().request;
        if (annotatedCentroidTilesOnly) {
            double cx = (r.getMinX() + r.getMaxX()) / 2.0;
            double cy = (r.getMinY() + r.getMaxY()) / 2.0;
            if (serverLabeled != null && (serverLabeled instanceof LabeledImageServer)) {
                if (!((LabeledImageServer) serverLabeled).getObjectsForRegion(r).stream().anyMatch(p -> p.getROI().contains(cx, cy))) {
                    logger.trace("Skipping empty labelled region based on centroid test {}", r);
                    iterator.remove();
                    continue;
                }
            } else if (imageData != null) {
                if (PathObjectTools.getObjectsForLocation(imageData.getHierarchy(), cx, cy, r.getZ(), r.getT(), 0).isEmpty()) {
                    iterator.remove();
                    continue;
                }
            }
        } else if (annotatedTilesOnly) {
            if (serverLabeled != null) {
                if (serverLabeled.isEmptyRegion(r)) {
                    logger.trace("Skipping empty labelled region {}", r);
                    iterator.remove();
                    continue;
                }
            } else if (imageData != null) {
                if (!imageData.getHierarchy().getObjectsForRegion(PathAnnotationObject.class, r, null).stream().anyMatch(p -> RoiTools.intersectsRegion(p.getROI(), r))) {
                    iterator.remove();
                    continue;
                }
            }
        }
    }
    return requests;
}
Also used : LabeledImageServer(qupath.lib.images.servers.LabeledImageServer) ImageServer(qupath.lib.images.servers.ImageServer) GsonTools(qupath.lib.io.GsonTools) LoggerFactory(org.slf4j.LoggerFactory) NumberFormat(java.text.NumberFormat) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) ImageRegion(qupath.lib.regions.ImageRegion) Locale(java.util.Locale) IndexColorModel(java.awt.image.IndexColorModel) BufferedImageTools(qupath.lib.awt.common.BufferedImageTools) LabeledImageServer(qupath.lib.images.servers.LabeledImageServer) LinkedHashSet(java.util.LinkedHashSet) ImageData(qupath.lib.images.ImageData) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) BufferedImage(java.awt.image.BufferedImage) Files(java.nio.file.Files) GeneralTools(qupath.lib.common.GeneralTools) RegionRequest(qupath.lib.regions.RegionRequest) Predicate(java.util.function.Predicate) Collection(java.util.Collection) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) IOException(java.io.IOException) Padding(qupath.lib.regions.Padding) Collectors(java.util.stream.Collectors) File(java.io.File) StandardCharsets(java.nio.charset.StandardCharsets) Executors(java.util.concurrent.Executors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) TimeUnit(java.util.concurrent.TimeUnit) List(java.util.List) Paths(java.nio.file.Paths) ThreadTools(qupath.lib.common.ThreadTools) TransformedServerBuilder(qupath.lib.images.servers.TransformedServerBuilder) Collections(java.util.Collections) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) ArrayList(java.util.ArrayList) RegionRequest(qupath.lib.regions.RegionRequest)

Aggregations

LabeledImageServer (qupath.lib.images.servers.LabeledImageServer)3 BufferedImage (java.awt.image.BufferedImage)2 IndexColorModel (java.awt.image.IndexColorModel)2 File (java.io.File)2 IOException (java.io.IOException)2 ArrayList (java.util.ArrayList)2 HashSet (java.util.HashSet)2 LinkedHashSet (java.util.LinkedHashSet)2 PathClass (qupath.lib.objects.classes.PathClass)2 StandardCharsets (java.nio.charset.StandardCharsets)1 Files (java.nio.file.Files)1 Paths (java.nio.file.Paths)1 NumberFormat (java.text.NumberFormat)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 List (java.util.List)1 Locale (java.util.Locale)1 Set (java.util.Set)1 Executors (java.util.concurrent.Executors)1 TimeUnit (java.util.concurrent.TimeUnit)1