use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class SubcellularDetection method processObject.
/**
* Initial version of subcellular detection processing.
*
* @param pathObject
* @param params
* @param imageWrapper
* @return
* @throws InterruptedException
* @throws IOException
*/
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageWrapper imageWrapper) throws InterruptedException, IOException {
// Get the base classification for the object as it currently stands
PathClass baseClass = PathClassTools.getNonIntensityAncestorClass(pathObject.getPathClass());
// Variable to hold estimated spot count
double estimatedSpots;
// We assume that after this processing, any previous sub-cellular objects should be removed
pathObject.clearPathObjects();
// Ensure we have no existing subcellular detection measurements - if we do, remove them
String[] existingMeasurements = pathObject.getMeasurementList().getMeasurementNames().stream().filter(n -> n.startsWith("Subcellular:")).toArray(n -> new String[n]);
if (existingMeasurements.length > 0) {
pathObject.getMeasurementList().removeMeasurements(existingMeasurements);
pathObject.getMeasurementList().close();
}
// // If we're part of a TMA core, request the whole core...
// if (pathObject.getParent() instanceof TMACoreObject && pathObject.getParent().hasROI()) {
// regionStore.getImage(server, RegionRequest.createInstance(server.getPath(), 1, pathObject.getParent().getROI()), 25, true);
// }
ROI pathROI = pathObject.getROI();
if (pathROI == null || pathROI.isEmpty())
return false;
// double downsample = 0.5;
double downsample = 1;
// Determine spot size
ImageServer<BufferedImage> server = imageWrapper.getServer();
PixelCalibration cal = server.getPixelCalibration();
double minSpotArea, maxSpotArea, singleSpotArea;
double pixelWidth, pixelHeight;
if (cal.hasPixelSizeMicrons()) {
double spotSizeMicrons = params.getDoubleParameterValue("spotSizeMicrons");
double minSpotSizeMicrons = params.getDoubleParameterValue("minSpotSizeMicrons");
double maxSpotSizeMicrons = params.getDoubleParameterValue("maxSpotSizeMicrons");
pixelWidth = cal.getPixelWidthMicrons() * downsample;
pixelHeight = cal.getPixelHeightMicrons() * downsample;
singleSpotArea = spotSizeMicrons / (pixelWidth * pixelHeight);
minSpotArea = minSpotSizeMicrons / (pixelWidth * pixelHeight);
maxSpotArea = maxSpotSizeMicrons / (pixelWidth * pixelHeight);
} else {
singleSpotArea = params.getDoubleParameterValue("spotSizePixels");
minSpotArea = params.getDoubleParameterValue("minSpotSizePixels");
maxSpotArea = params.getDoubleParameterValue("maxSpotSizePixels");
pixelWidth = downsample;
pixelHeight = downsample;
}
boolean includeClusters = Boolean.TRUE.equals(params.getBooleanParameterValue("includeClusters"));
boolean doSmoothing = Boolean.TRUE.equals(params.getBooleanParameterValue("doSmoothing"));
boolean splitByIntensity = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByIntensity"));
boolean splitByShape = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByShape"));
// Get region to request - give a pixel as border
int xStart = (int) Math.max(0, pathROI.getBoundsX() - 1);
int yStart = (int) Math.max(0, pathROI.getBoundsY() - 1);
int width = (int) Math.min(server.getWidth() - 1, pathROI.getBoundsX() + pathROI.getBoundsWidth() + 1.5) - xStart;
int height = (int) Math.min(server.getHeight() - 1, pathROI.getBoundsY() + pathROI.getBoundsHeight() + 1.5) - yStart;
if (width <= 0 || height <= 0) {
logger.error("Negative ROI size for {}", pathROI);
pathObject.setPathClass(baseClass);
return false;
}
int z = pathROI.getZ();
int t = pathROI.getT();
// Don't associate with channel
int c = -1;
RegionRequest region = RegionRequest.createInstance(server.getPath(), 1.0, xStart, yStart, width, height, z, t);
// Mask to indicate pixels within the cell
byte[] cellMask = null;
for (String channelName : imageWrapper.getChannelNames(true, true)) {
double detectionThreshold = params.getDoubleParameterValue("detection[" + channelName + "]");
if (Double.isNaN(detectionThreshold) || detectionThreshold < 0)
continue;
// // TODO: Consider whether to use channel numbers for non-brightfield images
// if (!imageWrapper.imageData.isBrightfield())
// c++;
SimpleImage img = imageWrapper.getRegion(region, channelName);
// Get an ImageJ-friendly calibration for ROI conversion
Calibration calIJ = new Calibration();
calIJ.xOrigin = -xStart / downsample;
calIJ.yOrigin = -yStart / downsample;
// Create a cell mask
if (cellMask == null) {
BufferedImage imgMask = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = imgMask.createGraphics();
if (downsample != 1)
g2d.scale(1.0 / downsample, 1.0 / downsample);
g2d.translate(-xStart, -yStart);
Shape shape = RoiTools.getShape(pathROI);
g2d.setColor(Color.WHITE);
g2d.fill(shape);
g2d.dispose();
cellMask = (byte[]) ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData(0);
}
// Get a buffer containing the image pixels
int w = img.getWidth();
int h = img.getHeight();
// Identify (& try to separate) spots
// Mask out non-cell areas as we go
FloatProcessor fpDetection = new FloatProcessor(w, h);
if (doSmoothing) {
for (int i = 0; i < w * h; i++) fpDetection.setf(i, img.getValue(i % w, i / w));
fpDetection.smooth();
for (int i = 0; i < w * h; i++) {
if (cellMask[i] == (byte) 0)
fpDetection.setf(i, 0f);
}
} else {
for (int i = 0; i < w * h; i++) {
if (cellMask[i] == (byte) 0)
fpDetection.setf(i, 0f);
else
fpDetection.setf(i, img.getValue(i % w, i / w));
}
}
ByteProcessor bpSpots;
if (splitByIntensity)
bpSpots = new MaximumFinder().findMaxima(fpDetection, detectionThreshold / 10.0, detectionThreshold, MaximumFinder.SEGMENTED, false, false);
else
bpSpots = SimpleThresholding.thresholdAboveEquals(fpDetection, (float) detectionThreshold);
if (splitByShape) {
new EDM().toWatershed(bpSpots);
}
// Loop through spot ROIs & make a decision
bpSpots.setThreshold(1, ImageProcessor.NO_THRESHOLD, ImageProcessor.NO_LUT_UPDATE);
List<PolygonRoi> possibleSpotRois = RoiLabeling.getFilledPolygonROIs(bpSpots, Wand.FOUR_CONNECTED);
List<PathObject> spotObjects = new ArrayList<>();
List<PathObject> clusterObjects = new ArrayList<>();
estimatedSpots = 0;
for (PolygonRoi spotRoi : possibleSpotRois) {
fpDetection.setRoi(spotRoi);
ImageStatistics stats = fpDetection.getStatistics();
// In v0.2
// ImagePlane plane = ImagePlane.getPlaneWithChannel(spotRoi.getCPosition(), spotRoi.getZPosition(), spotRoi.getTPosition());
// In v0.3
ImagePlane plane = ImagePlane.getPlaneWithChannel(c, z, t);
PathObject spotOrCluster = null;
if (stats.pixelCount >= minSpotArea && stats.pixelCount <= maxSpotArea) {
ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
// cluster = new SubcellularObject(roi, 1);
spotOrCluster = createSubcellularObject(roi, 1);
estimatedSpots += 1;
} else if (includeClusters && stats.pixelCount >= minSpotArea) {
// Add a cluster
ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
double nSpots = stats.pixelCount / singleSpotArea;
estimatedSpots += nSpots;
// cluster = new SubcellularObject(roi, nSpots);
spotOrCluster = createSubcellularObject(roi, nSpots);
}
if (spotOrCluster != null) {
boolean isCluster = spotOrCluster.getMeasurementList().getMeasurementValue("Num spots") > 1;
int rgb = imageWrapper.getChannelColor(channelName);
rgb = isCluster ? ColorTools.makeScaledRGB(rgb, 0.5) : ColorTools.makeScaledRGB(rgb, 1.5);
PathClass pathClass = PathClassFactory.getDerivedPathClass(spotOrCluster.getPathClass(), channelName + " object", rgb);
spotOrCluster.setPathClass(pathClass);
spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Area", stats.pixelCount * pixelWidth * pixelHeight);
spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Mean channel intensity", stats.mean);
// cluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Max channel intensity", stats.max);
spotOrCluster.getMeasurementList().close();
if (isCluster)
clusterObjects.add(spotOrCluster);
else
spotObjects.add(spotOrCluster);
}
}
// Add measurements
MeasurementList measurementList = pathObject.getMeasurementList();
measurementList.putMeasurement("Subcellular: " + channelName + ": Num spots estimated", estimatedSpots);
measurementList.putMeasurement("Subcellular: " + channelName + ": Num single spots", spotObjects.size());
measurementList.putMeasurement("Subcellular: " + channelName + ": Num clusters", clusterObjects.size());
// Add spots
pathObject.addPathObjects(spotObjects);
pathObject.addPathObjects(clusterObjects);
}
return true;
}
use of qupath.lib.images.servers.ImageServer 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;
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class PixelClassifierTools method classifyObjectsByCentroid.
/**
* Apply classification from a server to a collection of objects.
*
* @param classifierServer an {@link ImageServer} with output type
* @param pathObjects
* @param preferNucleusROI
*/
public static void classifyObjectsByCentroid(ImageServer<BufferedImage> classifierServer, Collection<PathObject> pathObjects, boolean preferNucleusROI) {
var labels = classifierServer.getMetadata().getClassificationLabels();
var reclassifiers = pathObjects.parallelStream().map(p -> {
try {
var roi = PathObjectTools.getROI(p, preferNucleusROI);
int x = (int) roi.getCentroidX();
int y = (int) roi.getCentroidY();
int ind = getClassification(classifierServer, x, y, roi.getZ(), roi.getT());
return new Reclassifier(p, labels.getOrDefault(ind, null), false);
} catch (Exception e) {
return new Reclassifier(p, null, false);
}
}).collect(Collectors.toList());
reclassifiers.parallelStream().forEach(r -> r.apply());
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class ServerSelector method promptToSelectServer.
@SuppressWarnings("unchecked")
public ImageServer<BufferedImage> promptToSelectServer() {
if (serverList.isEmpty()) {
logger.warn("No series available!");
return null;
} else if (serverList.size() == 1) {
logger.warn("Only one server available!");
return serverList.get(0);
}
// Get thumbnails in separate thread
ExecutorService executor = Executors.newSingleThreadExecutor(ThreadTools.createThreadFactory("thumbnail-loader", true));
ListView<ImageServer<BufferedImage>> listSeries = new ListView<>();
listSeries.setPrefWidth(480);
listSeries.setMinHeight(100);
// thumbnailBank is the map for storing thumbnails
Map<String, BufferedImage> thumbnailBank = new HashMap<String, BufferedImage>();
for (ImageServer<BufferedImage> server : serverList) {
executor.submit(() -> {
try {
thumbnailBank.put(server.getMetadata().getName(), ProjectCommands.getThumbnailRGB(server));
Platform.runLater(() -> listSeries.refresh());
} catch (IOException e) {
logger.warn("Error loading thumbnail: " + e.getLocalizedMessage(), e);
}
});
}
;
double thumbnailSize = 80;
listSeries.setCellFactory(v -> new ImageAndNameListCell(thumbnailBank, thumbnailSize, thumbnailSize));
listSeries.getItems().setAll(serverList);
// Info table - Changes according to selected series
String[] attributes = new String[] { "Full Path", "Server Type", "Width", "Height", "Pixel Width", "Pixel Height", "Pixel Type", "Number of Channels", "Number of Resolutions" };
Integer[] indices = new Integer[9];
for (int index = 0; index < 9; index++) indices[index] = index;
ObservableList<Integer> indexList = FXCollections.observableArrayList(indices);
TableView<Integer> tableInfo = new TableView<>();
tableInfo.setMinHeight(200);
tableInfo.setMinWidth(500);
// First column (attribute names)
TableColumn<Integer, String> attributeCol = new TableColumn<Integer, String>("Attribute");
attributeCol.setMinWidth(242);
attributeCol.setResizable(false);
attributeCol.setCellValueFactory(cellData -> {
return new ReadOnlyObjectWrapper<String>(attributes[cellData.getValue()]);
});
// Second column (attribute values)
TableColumn<Integer, String> valueCol = new TableColumn<Integer, String>("Value");
valueCol.setMinWidth(242);
valueCol.setResizable(false);
valueCol.setCellValueFactory(cellData -> {
if (selectedSeries != null)
return getSeriesQuickInfo(selectedSeries, cellData.getValue());
else
return null;
});
// Adding the values on hover over the info table
tableInfo.setRowFactory(tableView -> {
final TableRow<Integer> row = new TableRow<>();
row.hoverProperty().addListener((observable) -> {
final var element = row.getItem();
if (row.isHover() && selectedSeries != null) {
ObservableValue<String> value = getSeriesQuickInfo(selectedSeries, element);
Tooltip tooltip = new Tooltip(value.getValue());
Tooltip.install(row, tooltip);
}
});
return row;
});
// Set items to info table
tableInfo.setItems(indexList);
tableInfo.getColumns().addAll(attributeCol, valueCol);
// Pane structure
BorderPane paneSelector = new BorderPane();
BorderPane paneSeries = new BorderPane(listSeries);
BorderPane paneInfo = new BorderPane(tableInfo);
paneInfo.setMaxHeight(100);
paneSelector.setCenter(paneSeries);
paneSelector.setBottom(paneInfo);
BorderPane pane = new BorderPane();
pane.setCenter(paneSelector);
Dialog<ButtonType> dialog = new Dialog<>();
var qupath = QuPathGUI.getInstance();
if (qupath != null)
dialog.initOwner(qupath.getStage());
dialog.setTitle("Open image");
ButtonType typeImport = new ButtonType("Open", ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(typeImport, ButtonType.CANCEL);
dialog.getDialogPane().setContent(pane);
listSeries.getSelectionModel().selectedItemProperty().addListener((obs, previousSelectedRow, selectedRow) -> {
if (selectedRow != null) {
selectedSeries = selectedRow;
indexList.removeAll(indexList);
indexList.addAll(indices);
}
});
listSeries.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent click) {
ImageServer<BufferedImage> selectedItem = listSeries.getSelectionModel().getSelectedItem();
if (click.getClickCount() == 2 && selectedItem != null) {
Button okButton = (Button) dialog.getDialogPane().lookupButton(typeImport);
okButton.fire();
}
}
});
Optional<ButtonType> result = dialog.showAndWait();
var selectedToReturn = listSeries.getSelectionModel().getSelectedItem();
try {
executor.shutdownNow();
} catch (Exception e) {
logger.warn(e.getLocalizedMessage(), e);
} finally {
try {
for (ImageServer<BufferedImage> server : serverList) {
if (server != selectedToReturn)
server.close();
}
} catch (Exception e) {
logger.debug(e.getLocalizedMessage(), e);
}
selectedSeries = null;
}
if (!result.isPresent() || result.get() != typeImport || result.get() == ButtonType.CANCEL)
return null;
return selectedToReturn;
}
use of qupath.lib.images.servers.ImageServer in project qupath by qupath.
the class OMEPyramidWriterCommand method run.
@Override
public void run() {
if (currentTask != null && !currentTask.isDone()) {
if (!Dialogs.showConfirmDialog("OME Pyramid writer", "Do you want to stop the current export?"))
// TODO: Delete exporting file?
return;
else {
currentTask.cancel(true);
}
}
QuPathViewer viewer = qupath.getViewer();
int zPos = viewer.getZPosition();
int tPos = viewer.getTPosition();
ImageData<BufferedImage> imageData = viewer.getImageData();
if (imageData == null) {
Dialogs.showNoImageError("OME Pyramid writer");
return;
}
ImageServer<BufferedImage> server = imageData.getServer();
// Region
PathObject selected = imageData.getHierarchy().getSelectionModel().getSelectedObject();
ImageRegion region = null;
int width, height;
if (selected == null || !selected.hasROI() || !selected.getROI().isArea()) {
width = server.getWidth();
height = server.getHeight();
} else {
region = ImageRegion.createInstance(selected.getROI());
width = region.getWidth();
height = region.getHeight();
}
// Set compression - with a sanity check for validity, defaulting to another comparable method if necessary
CompressionType compression = getDefaultPyramidCompression();
List<String> compatibleCompression = Arrays.stream(CompressionType.values()).filter(c -> c.supportsImage(server)).map(c -> c.toFriendlyString()).collect(Collectors.toList());
if (!compatibleCompression.contains(compression.toFriendlyString()))
compression = CompressionType.DEFAULT;
var params = new ParameterList().addChoiceParameter("compression", "Compression type", compression.toFriendlyString(), compatibleCompression).addIntParameter("scaledDownsample", "Pyramidal downsample", scaledDownsample.get(), "", 1, 8, "Amount to downsample each consecutive pyramidal level; use 1 to indicate the image should not be pyramidal").addIntParameter("tileSize", "Tile size", getDefaultTileSize(), "px", "Tile size for export (should be between 128 and 8192)").addBooleanParameter("parallelize", "Parallelize export", parallelizeTiling.get(), "Export image tiles in parallel - " + "this should be faster, best keep it on unless you encounter export problems").addBooleanParameter("allZ", "All z-slices", allZ.get(), "Include all z-slices in the stack").addBooleanParameter("allT", "All timepoints", allT.get(), "Include all timepoints in the time-series");
boolean singleTile = server.getTileRequestManager().getTileRequests(RegionRequest.createInstance(server)).size() == 1;
params.setHiddenParameters(server.nZSlices() == 1, "allZ");
params.setHiddenParameters(server.nTimepoints() == 1, "allT");
params.setHiddenParameters(singleTile, "tileSize", "parallelize");
if (!Dialogs.showParameterDialog("Export OME-TIFF", params))
return;
compression = CompressionType.fromFriendlyString((String) params.getChoiceParameterValue("compression"));
defaultPyramidCompression.set(compression);
int downsampleScale = params.getIntParameterValue("scaledDownsample");
scaledDownsample.set(downsampleScale);
int tileSize = params.getIntParameterValue("tileSize");
boolean parallelize = params.getBooleanParameterValue("parallelize");
if (!singleTile) {
tileSize = GeneralTools.clipValue(tileSize, 128, 8192);
defaultTileSize.set(tileSize);
parallelizeTiling.set(parallelize);
}
boolean doAllZ = false;
boolean doAllT = false;
if (server.nZSlices() > 1) {
doAllZ = params.getBooleanParameterValue("allZ");
allZ.set(doAllZ);
}
if (server.nTimepoints() > 1) {
doAllT = params.getBooleanParameterValue("allT");
allT.set(doAllT);
}
OMEPyramidWriter.Builder builder = new OMEPyramidWriter.Builder(server);
if (region != null) {
builder = builder.region(region);
} else {
if (server.nZSlices() > 1 && !doAllZ)
builder.zSlice(zPos);
if (server.nTimepoints() > 1 && !doAllT)
builder.timePoint(tPos);
}
builder.compression(compression);
if (downsampleScale <= 1 || Math.max(width, height) / server.getDownsampleForResolution(0) < minSizeForTiling.get())
builder.downsamples(server.getDownsampleForResolution(0));
else
builder.scaledDownsampling(server.getDownsampleForResolution(0), downsampleScale);
// Set tile size; if we just have one tile, use the image width & height
if (singleTile)
builder = builder.tileSize(width, height);
else
builder = builder.tileSize(tileSize).parallelize(parallelize);
if (server.nZSlices() > 1 && doAllZ)
builder.allZSlices();
if (server.nTimepoints() > 1 && doAllT)
builder.allTimePoints();
// Prompt for file
File fileOutput = Dialogs.promptToSaveFile("Write pyramid", null, null, "OME TIFF pyramid", ".ome.tif");
if (fileOutput == null)
return;
String name = fileOutput.getName();
// We can have trouble with only the '.tif' part of the name being included
if (name.endsWith(".tif") && !name.endsWith(".ome.tif"))
fileOutput = new File(fileOutput.getParentFile(), name.substring(0, name.length() - 4) + ".ome.tif");
OMEPyramidSeries writer = builder.build();
if (pool == null) {
pool = Executors.newSingleThreadExecutor(ThreadTools.createThreadFactory("ome-pyramid-export", false));
}
currentTask = pool.submit(new WriterTask(OMEPyramidWriter.createWriter(writer), fileOutput.getAbsolutePath()));
}
Aggregations