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