use of qupath.lib.objects.TMACoreObject 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.objects.TMACoreObject in project qupath by qupath.
the class QuPathGUI method setTMACoreMissing.
/**
* Set selected TMA cores to have the specified 'missing' status.
*
* @param hierarchy
* @param setToMissing
*/
private static void setTMACoreMissing(final PathObjectHierarchy hierarchy, final boolean setToMissing) {
if (hierarchy == null)
return;
PathObject pathObject = hierarchy.getSelectionModel().getSelectedObject();
List<PathObject> changed = new ArrayList<>();
if (pathObject instanceof TMACoreObject) {
TMACoreObject core = (TMACoreObject) pathObject;
core.setMissing(setToMissing);
changed.add(core);
// Update any other selected cores to have the same status
for (PathObject pathObject2 : hierarchy.getSelectionModel().getSelectedObjects()) {
if (pathObject2 instanceof TMACoreObject) {
core = (TMACoreObject) pathObject2;
if (core.isMissing() != setToMissing) {
core.setMissing(setToMissing);
changed.add(core);
}
}
}
}
if (!changed.isEmpty())
hierarchy.fireObjectsChangedEvent(getInstance(), changed);
}
use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class ImageJMacroRunner method runMacro.
static void runMacro(final ParameterList params, final ImageData<BufferedImage> imageData, final ImageDisplay imageDisplay, final PathObject pathObject, final String macroText) {
// Don't try if interrupted
if (Thread.currentThread().isInterrupted()) {
logger.warn("Skipping macro for {} - thread interrupted", pathObject);
return;
}
PathImage<ImagePlus> pathImage;
// Extract parameters
double downsampleFactor = params.getDoubleParameterValue("downsampleFactor");
boolean sendROI = params.getBooleanParameterValue("sendROI");
boolean sendOverlay = params.getBooleanParameterValue("sendOverlay");
ROI pathROI = pathObject.getROI();
ImageDisplay imageDisplay2 = params.containsKey("useTransform") && Boolean.TRUE.equals(params.getBooleanParameterValue("useTransform")) ? imageDisplay : null;
ImageServer<BufferedImage> server = imageDisplay2 == null || imageDisplay2.availableChannels().isEmpty() ? imageData.getServer() : ChannelDisplayTransformServer.createColorTransformServer(imageData.getServer(), imageDisplay.availableChannels());
RegionRequest region = RegionRequest.createInstance(imageData.getServer().getPath(), downsampleFactor, pathROI);
// Check the size of the region to extract - abort if it is too large of if ther isn't enough RAM
try {
IJTools.isMemorySufficient(region, imageData);
} catch (Exception e1) {
Dialogs.showErrorMessage("ImageJ macro error", e1.getMessage());
return;
}
try {
if (sendOverlay)
pathImage = IJExtension.extractROIWithOverlay(server, pathObject, imageData.getHierarchy(), region, sendROI, null);
else
pathImage = IJExtension.extractROI(server, pathObject, region, sendROI);
} catch (IOException e) {
logger.error("Unable to extract image region " + region, e);
return;
}
// IJHelpers.getImageJInstance();
// ImageJ ij = IJHelpers.getImageJInstance();
// if (ij != null && WindowManager.getIDList() == null)
// ij.setVisible(false);
// Determine a sensible argument to pass
String argument;
if (pathObject instanceof TMACoreObject || !pathObject.hasROI())
argument = pathObject.getDisplayedName();
else
argument = String.format("Region (%d, %d, %d, %d)", region.getX(), region.getY(), region.getWidth(), region.getHeight());
// Check if we have an image already - if so, we need to be more cautious so we don't accidentally use it...
// boolean hasImage = WindowManager.getCurrentImage() != null;
// Actually run the macro
final ImagePlus imp = pathImage.getImage();
imp.setProperty("QuPath region", argument);
WindowManager.setTempCurrentImage(imp);
// Ensure we've requested an instance, since this also loads any required extra plugins
IJExtension.getImageJInstance();
// TODO: Pay attention to how threading should be done... I think Swing EDT ok?
try {
// SwingUtilities.invokeAndWait(() -> {
boolean cancelled = false;
ImagePlus impResult = null;
try {
IJ.redirectErrorMessages();
Interpreter interpreter = new Interpreter();
impResult = interpreter.runBatchMacro(macroText, imp);
// If we had an error, return
if (interpreter.wasError()) {
Thread.currentThread().interrupt();
return;
}
// Get the resulting image, if available
if (impResult == null)
impResult = WindowManager.getCurrentImage();
} catch (RuntimeException e) {
logger.error(e.getLocalizedMessage());
// DisplayHelpers.showErrorMessage("ImageJ macro error", e.getLocalizedMessage());
Thread.currentThread().interrupt();
cancelled = true;
} finally {
// IJ.runMacro(macroText, argument);
WindowManager.setTempCurrentImage(null);
// IJ.run("Close all");
}
if (cancelled)
return;
// Get the current image when the macro has finished - which may or may not be the same as the original
if (impResult == null)
impResult = imp;
boolean changes = false;
if (params.getBooleanParameterValue("clearObjects") && pathObject.hasChildren()) {
pathObject.clearPathObjects();
changes = true;
}
if (params.getBooleanParameterValue("getROI") && impResult.getRoi() != null) {
Roi roi = impResult.getRoi();
Calibration cal = impResult.getCalibration();
PathObject pathObjectNew = roi == null ? null : IJTools.convertToAnnotation(roi, cal.xOrigin, cal.yOrigin, downsampleFactor, region.getPlane());
if (pathObjectNew != null) {
// If necessary, trim any returned annotation
if (pathROI != null && !(pathROI instanceof RectangleROI) && pathObjectNew.isAnnotation() && RoiTools.isShapeROI(pathROI) && RoiTools.isShapeROI(pathObjectNew.getROI())) {
ROI roiNew = RoiTools.combineROIs(pathROI, pathObjectNew.getROI(), CombineOp.INTERSECT);
((PathAnnotationObject) pathObjectNew).setROI(roiNew);
}
// Only add if we have something
if (pathObjectNew.getROI() instanceof LineROI || !pathObjectNew.getROI().isEmpty()) {
pathObject.addPathObject(pathObjectNew);
// imageData.getHierarchy().addPathObject(IJHelpers.convertToPathObject(imp, imageData.getServer(), imp.getRoi(), downsampleFactor, false), true);
changes = true;
}
}
}
boolean exportAsDetection = ((String) params.getChoiceParameterValue("getOverlayAs")).equals("Detections") ? true : false;
if (params.getBooleanParameterValue("getOverlay") && impResult.getOverlay() != null) {
var overlay = impResult.getOverlay();
List<PathObject> childObjects = QuPath_Send_Overlay_to_QuPath.createObjectsFromROIs(imp, Arrays.asList(overlay.toArray()), downsampleFactor, exportAsDetection, true, region.getPlane());
if (!childObjects.isEmpty()) {
pathObject.addPathObjects(childObjects);
changes = true;
}
// for (Roi roi : impResult.getOverlay().toArray()) {
// pathObject.addPathObject(IJTools.convertToPathObject(imp, imageData.getServer(), roi, downsampleFactor, true));
// changes = true;
// }
}
if (changes) {
Platform.runLater(() -> imageData.getHierarchy().fireHierarchyChangedEvent(null));
}
// });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class TMADataImporter method importTMAData.
public static void importTMAData(ImageData<?> imageData) {
if (imageData == null) {
Dialogs.showNoImageError(TITLE);
return;
}
PathObjectHierarchy hierarchy = imageData.getHierarchy();
if (hierarchy.getTMAGrid() == null) {
Dialogs.showErrorMessage(TITLE, "No TMA grid has been set for the selected image!");
return;
}
// Show a GUI with the current TMA grid status
TMAGrid grid = hierarchy.getTMAGrid();
CoreInfoGrid infoGrid = new CoreInfoGrid(grid);
// Create the table
TableView<CoreInfoRow> table = new TableView<>();
table.getItems().setAll(infoGrid.getRows());
for (int c = 0; c < grid.getGridWidth(); c++) {
final int col = c;
TableColumn<CoreInfoRow, TMACoreObject> tableColumn = new TableColumn<>();
tableColumn.setCellValueFactory(column -> new ReadOnlyObjectWrapper<>(column.getValue().get(col)));
tableColumn.setCellFactory(column -> new CoreInfoTableCell());
tableColumn.setResizable(false);
table.getColumns().add(tableColumn);
}
table.widthProperty().addListener(c -> {
Pane headerRow = (Pane) table.lookup("TableHeaderRow");
headerRow.setMaxHeight(0);
headerRow.setMinHeight(0);
headerRow.setPrefHeight(0);
headerRow.setVisible(false);
});
table.getSelectionModel().setCellSelectionEnabled(true);
BorderPane pane = new BorderPane();
pane.setCenter(table);
// table.setPadding(new Insets(5));
// Label label = new Label("Import TMA map or unique identifiers for TMA cores.");
// pane.setTop(label);
Button btnImportData = new Button("Import data");
btnImportData.setTooltip(new Tooltip("Import TMA core data from a tab-delimited or .csv file"));
btnImportData.setOnAction(e -> {
if (handleImportDataFromFile(infoGrid))
table.refresh();
});
Button btnPasteData = new Button("Paste data");
btnPasteData.setTooltip(new Tooltip("Paste tab-delimited TMA core data from the clipboard"));
btnPasteData.setOnAction(e -> {
if (handleImportDataFromClipboard(infoGrid))
table.refresh();
});
Button btnPasteGrid = new Button("Paste grid");
btnPasteGrid.setTooltip(new Tooltip("Paste a tab-delimited grid containing TMA core names from the clipboard"));
btnPasteGrid.setOnAction(e -> {
if (handlePasteGrid(infoGrid)) {
table.refresh();
}
});
Button btnLoadGrid = new Button("Import grid");
btnLoadGrid.setTooltip(new Tooltip("Import a grid containing TMA core names from a tab-delimited or .csv file"));
btnLoadGrid.setOnAction(e -> {
if (handleLoadGridFromFile(infoGrid))
table.refresh();
});
GridPane buttonPane = PaneTools.createColumnGridControls(btnImportData, btnPasteData, btnLoadGrid, btnPasteGrid);
buttonPane.setHgap(10);
buttonPane.setPadding(new Insets(5, 0, 5, 0));
pane.setBottom(buttonPane);
if (Dialogs.builder().title(TITLE).content(pane).buttons(ButtonType.APPLY, ButtonType.CANCEL).showAndWait().orElse(ButtonType.CANCEL) == ButtonType.APPLY) {
infoGrid.synchronizeTMAGridToInfo();
hierarchy.fireObjectsChangedEvent(infoGrid, new ArrayList<>(grid.getTMACoreList()));
return;
}
}
use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class QP method clearSelectedObjects.
/**
* Delete the selected objects from the current hierarchy, optionally keeping their child (descendant) objects.
*
* @param keepChildren
*/
public static void clearSelectedObjects(boolean keepChildren) {
PathObjectHierarchy hierarchy = getCurrentHierarchy();
if (hierarchy == null)
return;
Collection<PathObject> selectedRaw = hierarchy.getSelectionModel().getSelectedObjects();
List<PathObject> selected = selectedRaw.stream().filter(p -> !(p instanceof TMACoreObject)).collect(Collectors.toList());
hierarchy.removeObjects(selected, keepChildren);
hierarchy.getSelectionModel().clearSelection();
}
Aggregations