use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class ContourTracing method traceGeometriesImpl.
private static List<GeometryWrapper> traceGeometriesImpl(ImageServer<BufferedImage> server, TileRequest tile, Geometry clipArea, ChannelThreshold... thresholds) throws IOException {
if (thresholds.length == 0)
return Collections.emptyList();
var request = tile.getRegionRequest();
var list = new ArrayList<GeometryWrapper>();
var img = server.readBufferedImage(request);
// Get an image to threshold
var channelType = server.getMetadata().getChannelType();
int h = img.getHeight();
int w = img.getWidth();
// If we have probabilities, then the 'true' classification is the one with the highest values.
// If we have classifications, then the 'true' classification is the value of the pixel (which is expected to have a single band).
boolean doClassification = channelType == ImageServerMetadata.ChannelType.PROBABILITY || channelType == ImageServerMetadata.ChannelType.CLASSIFICATION;
if (doClassification) {
SimpleImage image;
if (channelType == ImageServerMetadata.ChannelType.PROBABILITY) {
// Convert probabilities to classifications
var raster = img.getRaster();
var nChannels = server.nChannels();
float[] output = new float[w * h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int maxInd = 0;
float maxVal = raster.getSampleFloat(x, y, 0);
for (int c = 1; c < nChannels; c++) {
float val = raster.getSampleFloat(x, y, c);
if (val > maxVal) {
maxInd = c;
maxVal = val;
}
output[y * w + x] = (float) maxInd;
}
}
}
image = SimpleImages.createFloatImage(output, w, h);
} else {
// Handle classifications
var raster = img.getRaster();
var pixels = raster.getSamples(0, 0, w, h, 0, (float[]) null);
image = SimpleImages.createFloatImage(pixels, w, h);
}
for (var threshold : thresholds) {
int c = threshold.getChannel();
Geometry geometry = ContourTracing.createTracedGeometry(image, c, c, tile);
if (geometry != null && !geometry.isEmpty()) {
if (clipArea != null) {
geometry = GeometryTools.attemptOperation(geometry, g -> g.intersection(clipArea));
geometry = GeometryTools.homogenizeGeometryCollection(geometry);
}
if (!geometry.isEmpty() && geometry.getArea() > 0) {
// Exclude lines/points that can sometimes arise
list.add(new GeometryWrapper(geometry, c));
}
}
}
} else {
// Apply the provided threshold to all channels
var raster = img.getRaster();
for (var threshold : thresholds) {
Geometry geometry = ContourTracing.createTracedGeometry(raster, threshold.getMinThreshold(), threshold.getMaxThreshold(), threshold.getChannel(), tile);
if (geometry != null) {
if (clipArea != null) {
geometry = GeometryTools.attemptOperation(geometry, g -> g.intersection(clipArea));
geometry = GeometryTools.homogenizeGeometryCollection(geometry);
}
if (!geometry.isEmpty() && geometry.getArea() > 0) {
// Exclude lines/points that can sometimes arise
list.add(new GeometryWrapper(geometry, threshold.getChannel()));
}
}
}
}
return list;
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class ContourTracing method traceGeometriesImpl.
@SuppressWarnings("unchecked")
private static Map<Integer, Geometry> traceGeometriesImpl(ImageServer<BufferedImage> server, Collection<TileRequest> tiles, Geometry clipArea, ChannelThreshold... thresholds) throws IOException {
if (thresholds.length == 0)
return Collections.emptyMap();
Map<Integer, Geometry> output = new LinkedHashMap<>();
var pool = Executors.newFixedThreadPool(ThreadTools.getParallelism());
try {
List<List<GeometryWrapper>> wrappers = invokeAll(pool, tiles, t -> traceGeometries(server, t, clipArea, thresholds));
var geometryMap = wrappers.stream().flatMap(p -> p.stream()).collect(Collectors.groupingBy(g -> g.label));
// Determine 'inter-tile boundaries' - union operations can be very slow, so we want to restrict them
// only to geometries that really require them.
var xBoundsSet = new TreeSet<Integer>();
var yBoundsSet = new TreeSet<Integer>();
for (var t : tiles) {
xBoundsSet.add(t.getImageX());
xBoundsSet.add(t.getImageX() + t.getImageWidth());
yBoundsSet.add(t.getImageY());
yBoundsSet.add(t.getImageY() + t.getImageHeight());
}
int[] xBounds = xBoundsSet.stream().mapToInt(x -> x).toArray();
int[] yBounds = yBoundsSet.stream().mapToInt(y -> y).toArray();
var futures = new LinkedHashMap<Integer, Future<Geometry>>();
// Merge objects with the same classification
for (var entry : geometryMap.entrySet()) {
var list = entry.getValue();
if (list.isEmpty())
continue;
futures.put(entry.getKey(), pool.submit(() -> mergeGeometryWrappers(list, xBounds, yBounds)));
}
for (var entry : futures.entrySet()) output.put(entry.getKey(), entry.getValue().get());
} catch (Exception e) {
throw new IOException(e);
} finally {
pool.shutdown();
}
return output;
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class QuPathGUI method refreshExtensions.
/**
* Check the extensions directory, loading any new extensions found there.
* @param showNotification if true, display a notification if a new extension has been loaded
*/
public void refreshExtensions(final boolean showNotification) {
boolean initializing = initializingMenus.get();
initializingMenus.set(true);
// Refresh the extensions
extensionClassLoader.refresh();
extensionLoader.reload();
// Sort the extensions by name, to ensure predictable loading order
// (also, menus are in a better order if ImageJ extension installed before OpenCV extension)
List<QuPathExtension> extensions = new ArrayList<>();
Iterator<QuPathExtension> iterator = extensionLoader.iterator();
while (iterator.hasNext()) {
try {
extensions.add(iterator.next());
} catch (Throwable e) {
if (getStage() != null && getStage().isShowing()) {
Dialogs.showErrorMessage("Extension error", "Error loading extension - check 'View -> Show log' for details.");
}
logger.error(e.getLocalizedMessage(), e);
}
}
Collections.sort(extensions, Comparator.comparing(QuPathExtension::getName));
Version qupathVersion = getVersion();
for (QuPathExtension extension : extensions) {
if (!loadedExtensions.containsKey(extension.getClass())) {
Version version = extension.getVersion();
try {
long startTime = System.currentTimeMillis();
extension.installExtension(this);
long endTime = System.currentTimeMillis();
logger.info("Loaded extension {} ({} ms)", extension.getName(), endTime - startTime);
if (version != null)
logger.debug("{} was written for QuPath {}", extension.getName(), version);
else
logger.debug("{} does not report a compatible QuPath version", extension.getName());
loadedExtensions.put(extension.getClass(), extension);
if (showNotification)
Dialogs.showInfoNotification("Extension loaded", extension.getName());
} catch (Exception | LinkageError e) {
String message = "Unable to load " + extension.getName();
if (showNotification)
Dialogs.showErrorNotification("Extension error", message);
logger.error("Error loading extension " + extension + ": " + e.getLocalizedMessage(), e);
if (!Objects.equals(qupathVersion, version)) {
if (version == null)
logger.warn("QuPath version for which the '{}' was written is unknown!", extension.getName());
else if (version.equals(qupathVersion))
logger.warn("'{}' reports that it is compatible with the current QuPath version {}", extension.getName(), qupathVersion);
else
logger.warn("'{}' was written for QuPath {} but current version is {}", extension.getName(), version, qupathVersion);
}
try {
logger.error("It is recommended that you delete {} and restart QuPath", URLDecoder.decode(extension.getClass().getProtectionDomain().getCodeSource().getLocation().toExternalForm(), StandardCharsets.UTF_8));
} catch (Exception e2) {
logger.debug("Error finding code source " + e2.getLocalizedMessage(), e2);
}
defaultActions.SHOW_LOG.handle(null);
}
}
}
// Set the ImageServer to also look on the same search path
List<ImageServerBuilder<?>> serverBuildersBefore = ImageServerProvider.getInstalledImageServerBuilders();
ImageServerProvider.setServiceLoader(ServiceLoader.load(ImageServerBuilder.class, extensionClassLoader));
if (showNotification) {
// A bit convoluted... but try to show new servers that have been loaded by comparing with the past
List<String> serverBuilders = serverBuildersBefore.stream().map(s -> s.getName()).collect(Collectors.toList());
List<String> serverBuildersUpdated = ImageServerProvider.getInstalledImageServerBuilders().stream().map(s -> s.getName()).collect(Collectors.toList());
serverBuildersUpdated.removeAll(serverBuilders);
for (String builderName : serverBuildersUpdated) {
Dialogs.showInfoNotification("Image server loaded", builderName);
}
}
initializingMenus.set(initializing);
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class QuPathViewer method setImageData.
/**
* Set the current image for this viewer.
* @param imageDataNew
*/
public void setImageData(ImageData<BufferedImage> imageDataNew) {
if (this.imageDataProperty.get() == imageDataNew)
return;
imageDataChanging.set(true);
// Remove listeners for previous hierarchy
ImageData<BufferedImage> imageDataOld = this.imageDataProperty.get();
if (imageDataOld != null) {
imageDataOld.getHierarchy().removePathObjectListener(this);
imageDataOld.getHierarchy().getSelectionModel().removePathObjectSelectionListener(this);
}
// Determine if the server has remained the same, so we can avoid shifting the viewer
boolean sameServer = false;
if (imageDataOld != null && imageDataNew != null && imageDataOld.getServerPath().equals(imageDataNew.getServerPath()))
sameServer = true;
this.imageDataProperty.set(imageDataNew);
ImageServer<BufferedImage> server = imageDataNew == null ? null : imageDataNew.getServer();
PathObjectHierarchy hierarchy = imageDataNew == null ? null : imageDataNew.getHierarchy();
long startTime = System.currentTimeMillis();
if (imageDisplay != null) {
boolean keepDisplay = PathPrefs.keepDisplaySettingsProperty().get();
// This is a bit of a hack to avoid calling internal methods for ImageDisplay
// See https://github.com/qupath/qupath/issues/601
boolean displaySet = false;
if (imageDataNew != null && keepDisplay) {
if (imageDisplay.getImageData() != null && serversCompatible(imageDataNew.getServer(), imageDisplay.getImageData().getServer())) {
imageDisplay.setImageData(imageDataNew, keepDisplay);
displaySet = true;
} else {
for (var viewer : QuPathGUI.getInstance().getViewers()) {
if (this == viewer || viewer.getImageData() == null)
continue;
var tempServer = viewer.getServer();
var currentServer = imageDataNew.getServer();
if (serversCompatible(tempServer, currentServer)) {
var json = viewer.getImageDisplay().toJSON(false);
imageDataNew.setProperty(ImageDisplay.class.getName(), json);
imageDisplay.setImageData(imageDataNew, false);
displaySet = true;
break;
}
}
}
}
if (!displaySet)
imageDisplay.setImageData(imageDataNew, keepDisplay);
// See https://github.com/qupath/qupath/issues/843
if (server != null && !server.isRGB()) {
var colors = imageDisplay.availableChannels().stream().filter(c -> c instanceof DirectServerChannelInfo).map(c -> c.getColor()).collect(Collectors.toList());
if (server.nChannels() == colors.size())
updateServerChannels(server, colors);
}
}
long endTime = System.currentTimeMillis();
logger.debug("Setting ImageData time: {} ms", endTime - startTime);
initializeForServer(server);
if (!sameServer) {
setDownsampleFactorImpl(getZoomToFitDownsampleFactor(), -1, -1);
centerImage();
}
fireImageDataChanged(imageDataOld, imageDataNew);
if (imageDataNew != null) {
// hierarchyPainter = new PathHierarchyPainter(hierarchy);
hierarchy.addPathObjectListener(this);
hierarchy.getSelectionModel().addPathObjectSelectionListener(this);
}
setSelectedObject(null);
// TODO: Consider shifting, fixing magnification, repainting etc.
if (isShowing())
repaint();
logger.info("Image data set to {}", imageDataNew);
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class ConvertCommand method run.
@Override
public void run() {
long startTime = System.currentTimeMillis();
try {
if (inputFile == null || outputFile == null)
throw new IOException("Incorrect given path(s)");
} catch (IOException e) {
logger.error(e.getLocalizedMessage());
return;
}
// Change name if not ending with .ome.tif
if (!outputFile.getAbsolutePath().toLowerCase().endsWith(".ome.tif"))
outputFile = new File(outputFile.getParentFile(), GeneralTools.getNameWithoutExtension(outputFile) + ".ome.tif");
if (outputFile.exists() && !overwrite) {
logger.error("Output file " + outputFile + " exists!");
return;
}
if (inputFile.equals(outputFile)) {
logger.error("Input and output files are the same!");
return;
}
String[] args;
if (series >= 0)
args = new String[] { "--classname", BioFormatsServerBuilder.class.getName(), "--series", Integer.toString(series) };
else
args = new String[0];
createTileCache();
try (ImageServer<BufferedImage> server = ImageServers.buildServer(inputFile.toURI(), args)) {
// Get compression from user (or CompressionType.DEFAULT)
// CompressionType compressionType = stringToCompressionType(compression);
CompressionType compressionType = compression;
// Check that compression is compatible with image
if (!Arrays.stream(CompressionType.values()).filter(c -> c.supportsImage(server)).anyMatch(c -> c == compressionType)) {
logger.error("Chosen compression " + compressionType.toString() + " is not compatible with the input image.");
}
if (tileSize > -1) {
tileWidth = tileSize;
tileHeight = tileSize;
}
Builder builder = new OMEPyramidWriter.Builder(server).compression(compressionType).tileSize(tileWidth, tileHeight).parallelize(parallelize);
if (bigTiff != null)
builder = builder.bigTiff(bigTiff.booleanValue());
// Make pyramidal, if requested
if (downsample < 1)
downsample = server.getDownsampleForResolution(0);
if (pyramid > 1)
builder.scaledDownsampling(downsample, pyramid);
else
builder.downsamples(downsample);
String patternRange = "(\\d+)-(\\d+)";
String patternInteger = "\\d+";
// Parse z-slices, remembering to convert from 1-based (inclusive) to 0-based (upper value exclusive) indexing
if (zSlices == null || zSlices.isBlank() || "all".equals(zSlices)) {
builder.allZSlices();
} else if (zSlices.matches(patternRange)) {
int zStart = Integer.parseInt(zSlices.substring(0, zSlices.indexOf("-")));
int zEnd = Integer.parseInt(zSlices.substring(zSlices.indexOf("-") + 1));
if (zEnd == zStart)
builder.zSlice(zStart - 1);
else if (zStart > zEnd) {
logger.error("Invalid range of --zslices (must be ascending): " + zSlices);
return;
} else
builder.zSlices(zStart - 1, zEnd);
} else if (zSlices.matches(patternInteger)) {
int z = Integer.parseInt(zSlices);
builder.zSlice(z - 1);
} else {
logger.error("Unknown value for --zslices: " + zSlices);
return;
}
// Parse timepoints, remembering to convert from 1-based (inclusive) to 0-based (upper value exclusive) indexing
if ("all".equals(timepoints)) {
builder.allTimePoints();
} else if (timepoints.matches(patternRange)) {
int tStart = Integer.parseInt(timepoints.substring(0, timepoints.indexOf("-")));
int tEnd = Integer.parseInt(timepoints.substring(timepoints.indexOf("-") + 1));
if (tStart == tEnd)
builder.timePoint(tStart - 1);
else if (tStart > tEnd) {
logger.error("Invalid range of --timepoints (must be ascending): " + timepoints);
return;
} else
builder.timePoints(tStart - 1, tEnd);
} else if (timepoints.matches(patternInteger)) {
int t = Integer.parseInt(timepoints);
builder.timePoint(t - 1);
} else {
logger.error("Unknown value for --timepoints: " + timepoints);
return;
}
// Parse the bounding box, if required
if (crop != null && !crop.isBlank()) {
var matcher = Pattern.compile("(\\d+),(\\d+),(\\d+),(\\d+)").matcher(crop);
if (matcher.matches()) {
int x = Integer.parseInt(matcher.group(1));
int y = Integer.parseInt(matcher.group(2));
int w = Integer.parseInt(matcher.group(3));
int h = Integer.parseInt(matcher.group(4));
builder.region(x, y, w, h);
} else {
logger.error("Unknown value for --crop: " + crop);
return;
}
}
builder.build().writeSeries(outputFile.getPath());
long duration = System.currentTimeMillis() - startTime;
logger.info(String.format("%s written in %.1f seconds", outputFile.getAbsolutePath(), duration / 1000.0));
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
}
Aggregations