use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class BrushTool method getUpdatedObject.
private PathObject getUpdatedObject(MouseEvent e, ROI shapeROI, PathObject currentObject, double flatness) {
Point2D p = mouseLocationToImage(e, true, requestPixelSnapping());
var viewer = getViewer();
ImagePlane plane = shapeROI == null ? ImagePlane.getPlane(viewer.getZPosition(), viewer.getTPosition()) : shapeROI.getImagePlane();
Geometry shapeNew;
boolean subtractMode = isSubtractMode(e);
Geometry shapeCurrent = shapeROI == null ? null : shapeROI.getGeometry();
Geometry shapeDrawn = createShape(e, p.getX(), p.getY(), PathPrefs.useTileBrushProperty().get() && !e.isShiftDown(), subtractMode ? null : shapeCurrent);
if (shapeDrawn == null)
return currentObject;
// Do our pixel snapping now, with the simpler geometry (rather than latter when things are already complex)
if (requestPixelSnapping())
shapeDrawn = GeometryTools.roundCoordinates(shapeDrawn);
lastPoint = p;
try {
if (shapeROI != null) {
// Check to see if any changes are required at all
if (shapeDrawn == null || (subtractMode && !shapeCurrent.intersects(shapeDrawn)) || (!subtractMode && shapeCurrent.covers(shapeDrawn)))
return currentObject;
// TODO: Consider whether a preference should be used rather than the shift key?
// Anyhow, this will switch to 'dodge' mode, and avoid overlapping existing annotations
boolean avoidOtherAnnotations = requestParentClipping(e);
if (subtractMode) {
// If subtracting... then just subtract
shapeNew = shapeROI.getGeometry().difference(shapeDrawn);
} else if (avoidOtherAnnotations) {
shapeNew = shapeCurrent.union(shapeDrawn);
shapeNew = refineGeometryByParent(shapeNew);
} else {
// Just add, regardless of whether there are other annotations below or not
var temp = shapeROI.getGeometry();
try {
shapeNew = temp.union(shapeDrawn);
} catch (Exception e2) {
shapeNew = shapeROI.getGeometry();
}
}
} else {
shapeNew = shapeDrawn;
}
// If we aren't snapping, at least remove some vertices
if (!requestPixelSnapping()) {
try {
shapeNew = VWSimplifier.simplify(shapeNew, 0.1);
} catch (Exception e2) {
logger.error("Error simplifying ROI: " + e2.getLocalizedMessage(), e2);
}
}
// Make sure we fit inside the image
shapeNew = GeometryTools.constrainToBounds(shapeNew, 0, 0, viewer.getServerWidth(), viewer.getServerHeight());
// Sometimes we can end up with a GeometryCollection containing lines/non-areas... if so, remove these
if (shapeNew instanceof GeometryCollection) {
shapeNew = GeometryTools.ensurePolygonal(shapeNew);
}
ROI roiNew = GeometryTools.geometryToROI(shapeNew, plane);
if (currentObject instanceof PathAnnotationObject) {
((PathAnnotationObject) currentObject).setROI(roiNew);
return currentObject;
}
// shapeNew = new PathAreaROI(new Area(shapeNew.getShape()));
PathObject pathObjectNew = PathObjects.createAnnotationObject(roiNew, PathPrefs.autoSetAnnotationClassProperty().get());
if (currentObject != null) {
pathObjectNew.setName(currentObject.getName());
pathObjectNew.setColorRGB(currentObject.getColorRGB());
pathObjectNew.setPathClass(currentObject.getPathClass());
}
return pathObjectNew;
} catch (Exception ex) {
logger.error("Error updating ROI", ex);
return currentObject;
}
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class MoveTool method mouseReleased.
@Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
if (e.isConsumed())
return;
var viewer = getViewer();
RoiEditor editor = viewer.getROIEditor();
if (editor != null && (editor.hasActiveHandle() || editor.isTranslating())) {
boolean roiChanged = (editor.isTranslating() && editor.finishTranslation()) || editor.hasActiveHandle();
editor.resetActiveHandle();
// if (editor.isTranslating())
// editor.finishTranslation();
e.consume();
PathObject pathObject = viewer.getSelectedObject();
if (requestParentClipping(e) && pathObject instanceof PathAnnotationObject) {
ROI roiNew = refineROIByParent(pathObject.getROI());
((PathAnnotationObject) pathObject).setROI(roiNew);
}
if (pathObject != null && pathObject.hasROI() && pathObject.getROI().isEmpty()) {
if (pathObject.getParent() != null)
viewer.getHierarchy().removeObject(pathObject, true);
viewer.setSelectedObject(null);
} else {
PathObjectHierarchy hierarchy = viewer.getHierarchy();
if (pathObject instanceof TMACoreObject) {
hierarchy.fireHierarchyChangedEvent(pathObject);
} else if (pathObject != null) {
// Handle ROI changes only if required
if (roiChanged) {
var updatedROI = editor.getROI();
if (pathObject.getROI() != updatedROI && pathObject instanceof PathROIObject)
((PathROIObject) pathObject).setROI(updatedROI);
// PathObject parentPrevious = pathObject.getParent();
hierarchy.removeObjectWithoutUpdate(pathObject, true);
if (getCurrentParent() == null || !PathPrefs.clipROIsForHierarchyProperty().get() || e.isShiftDown())
hierarchy.addPathObject(pathObject);
else
hierarchy.addPathObjectBelowParent(getCurrentParent(), pathObject, true);
// PathObject parentNew = pathObject.getParent();
// if (parentPrevious == parentNew)
// hierarchy.fireHierarchyChangedEvent(this, parentPrevious);
// else
// hierarchy.fireHierarchyChangedEvent(this);
}
}
viewer.setSelectedObject(pathObject);
}
}
// Optionally continue a dragging movement until the canvas comes to a standstill
if (pDragging != null && requestDynamicDragging && System.currentTimeMillis() - lastDragTimestamp < 100 && (dx * dx + dy * dy > viewer.getDownsampleFactor())) {
mover = new ViewerMover(viewer);
mover.startMoving(dx, dy, false);
} else
viewer.setDoFasterRepaint(false);
// Make sure we don't have a previous point (to prevent weird dragging artefacts)
pDragging = null;
// // If we were translating, stop
// if (editor.isTranslating()) {
// editor.finishTranslation();
// // TODO: Make this more efficient!
// viewer.getPathObjectHierarchy().fireHierarchyChangedEvent();
// return;
// }
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class ImageJMacroRunner method runPlugin.
@Override
public boolean runPlugin(final PluginRunner<BufferedImage> runner, final String arg) {
if (!parseArgument(runner.getImageData(), arg))
return false;
if (dialog == null) {
dialog = new Stage();
dialog.initOwner(qupath.getStage());
dialog.setTitle("ImageJ macro runner");
BorderPane pane = new BorderPane();
if (arg != null)
macroText = arg;
// Create text area
final TextArea textArea = new TextArea();
textArea.setPrefRowCount(12);
textArea.setPrefSize(400, 400);
textArea.setWrapText(true);
textArea.setFont(Font.font("Courier"));
if (macroText != null)
textArea.setText(macroText);
BorderPane panelMacro = new BorderPane();
// panelMacro.setBorder(BorderFactory.createTitledBorder("Macro"));
panelMacro.setCenter(textArea);
ParameterPanelFX parameterPanel = new ParameterPanelFX(getParameterList(runner.getImageData()));
panelMacro.setBottom(parameterPanel.getPane());
// Create button panel
Button btnRun = new Button("Run");
btnRun.setOnAction(e -> {
macroText = textArea.getText().trim();
if (macroText.length() == 0)
return;
PathObjectHierarchy hierarchy = getHierarchy(runner);
PathObject pathObject = hierarchy.getSelectionModel().singleSelection() ? hierarchy.getSelectionModel().getSelectedObject() : null;
if (pathObject instanceof PathAnnotationObject || pathObject instanceof TMACoreObject) {
SwingUtilities.invokeLater(() -> {
runMacro(params, qupath.getViewer().getImageData(), qupath.getViewer().getImageDisplay(), pathObject, macroText);
});
} else {
// DisplayHelpers.showErrorMessage(getClass().getSimpleName(), "Sorry, ImageJ macros can only be run for single selected images");
// logger.warn("ImageJ macro being run in current thread");
// runPlugin(runner, arg); // TODO: Consider running in a background thread?
// Run in a background thread
Collection<? extends PathObject> parents = getParentObjects(runner);
if (parents.isEmpty()) {
Dialogs.showErrorMessage("ImageJ macro runner", "No annotation or TMA core objects selected!");
return;
}
List<Runnable> tasks = new ArrayList<>();
for (PathObject parent : parents) addRunnableTasks(qupath.getViewer().getImageData(), parent, tasks);
qupath.submitShortTask(() -> runner.runTasks(tasks, true));
// runner.runTasks(tasks);
// Runnable r = new Runnable() {
// public void run() {
// runPlugin(runner, arg);
// }
// };
// new Thread(r).start();
}
});
Button btnClose = new Button("Close");
btnClose.setOnAction(e -> dialog.hide());
GridPane panelButtons = PaneTools.createRowGridControls(btnRun, btnClose);
pane.setCenter(panelMacro);
pane.setBottom(panelButtons);
panelButtons.setPadding(new Insets(5, 0, 0, 0));
pane.setPadding(new Insets(10, 10, 10, 10));
dialog.setScene(new Scene(pane));
}
dialog.show();
return true;
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class IntensityFeaturesPlugin method processObject.
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageData<BufferedImage> imageData) throws IOException {
// Determine amount to downsample
var server = imageData.getServer();
var stains = imageData.getColorDeconvolutionStains();
PixelCalibration cal = server.getPixelCalibration();
double downsample = calculateDownsample(cal, params);
if (downsample <= 0) {
logger.warn("Effective downsample must be > 0 (requested value {})", downsample);
}
// Determine region shape
RegionType regionType = (RegionType) params.getChoiceParameterValue("region");
// Try to get ROI
boolean useROI = regionType == RegionType.ROI || regionType == RegionType.NUCLEUS;
ROI roi = null;
if (regionType == RegionType.NUCLEUS) {
if (pathObject instanceof PathCellObject)
roi = ((PathCellObject) pathObject).getNucleusROI();
} else
roi = pathObject.getROI();
// pathROI = ((PathCellObject)pathObject).getNucleusROI();
if (roi == null)
return false;
// Create a map - this is useful for occasions when tiling is needed
Map<FeatureColorTransform, List<FeatureComputer>> map = new LinkedHashMap<>();
if (server.isRGB()) {
for (FeatureColorTransform transform : FeatureColorTransformEnum.values()) {
List<FeatureComputer> list = new ArrayList<>();
map.put(transform, list);
for (FeatureComputerBuilder builder : builders) {
list.add(builder.build());
}
}
} else {
for (FeatureColorTransform transform : getBasicChannelTransforms(server.nChannels())) {
List<FeatureComputer> list = new ArrayList<>();
map.put(transform, list);
for (FeatureComputerBuilder builder : builders) {
list.add(builder.build());
}
}
}
String prefix = getDiameterString(server, params);
// Create tiled ROIs, if required
ImmutableDimension sizePreferred = ImmutableDimension.getInstance((int) (2000 * downsample), (int) (2000 * downsample));
// ImmutableDimension sizePreferred = new ImmutableDimension((int)(200*downsample), (int)(200*downsample));
Collection<? extends ROI> rois = RoiTools.computeTiledROIs(roi, sizePreferred, sizePreferred, false, 0);
if (rois.size() > 1)
logger.info("Splitting {} into {} tiles for intensity measurements", roi, rois.size());
for (ROI pathROI : rois) {
if (Thread.currentThread().isInterrupted()) {
logger.warn("Measurement skipped - thread interrupted!");
return false;
}
// Get bounds
RegionRequest region;
if (useROI) {
region = RegionRequest.createInstance(server.getPath(), downsample, pathROI);
} else {
ImmutableDimension size = getPreferredTileSizePixels(server, params);
// RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
// Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
int xStart = (int) (Math.round(pathROI.getCentroidX() / downsample) * downsample) - size.width / 2;
int yStart = (int) (Math.round(pathROI.getCentroidY() / downsample) * downsample) - size.height / 2;
int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
}
// // Check image large enough to do *anything* of value
// if (region.getWidth() / downsample < 1 || region.getHeight() / downsample < 1) {
// logger.trace("Requested region is too small! {}", region);
// return false;
// }
// System.out.println(bounds);
// System.out.println("Size: " + size);
BufferedImage img = server.readBufferedImage(region);
if (img == null) {
logger.error("Could not read image - unable to compute intensity features for {}", pathObject);
return false;
}
// Create mask ROI if necessary
// If we just have 1 pixel, we want to use it so that the mean/min/max measurements are valid (even if nothing else is)
byte[] maskBytes = null;
if (useROI && img.getWidth() * img.getHeight() > 1) {
BufferedImage imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), pathROI, region);
maskBytes = ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData();
}
boolean isRGB = server.isRGB();
List<FeatureColorTransform> transforms;
if (isRGB)
transforms = Arrays.asList(FeatureColorTransformEnum.values());
else
transforms = getBasicChannelTransforms(server.nChannels());
int w = img.getWidth();
int h = img.getHeight();
int[] rgbBuffer = isRGB ? img.getRGB(0, 0, w, h, null, 0, w) : null;
float[] pixels = null;
for (FeatureColorTransform transform : transforms) {
// Check if the color transform is requested
if (params.containsKey(transform.getKey()) && Boolean.TRUE.equals(params.getBooleanParameterValue(transform.getKey()))) {
// Transform the pixels
pixels = transform.getTransformedPixels(img, rgbBuffer, stains, pixels);
// Create the simple image
SimpleModifiableImage pixelImage = SimpleImages.createFloatImage(pixels, w, h);
// Apply any arbitrary mask
if (maskBytes != null) {
for (int i = 0; i < pixels.length; i++) {
if (maskBytes[i] == (byte) 0)
pixelImage.setValue(i % w, i / w, Float.NaN);
}
} else if (regionType == RegionType.CIRCLE) {
// Apply circular tile mask
double cx = (w - 1) / 2;
double cy = (h - 1) / 2;
double radius = Math.max(w, h) * .5;
double distThreshold = radius * radius;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if ((cx - x) * (cx - x) + (cy - y) * (cy - y) > distThreshold)
pixelImage.setValue(x, y, Float.NaN);
}
}
}
// Do the computations
for (FeatureComputer computer : map.get(transform)) {
computer.updateFeatures(pixelImage, transform, params);
}
}
}
}
// Add measurements to the parent object
for (Entry<FeatureColorTransform, List<FeatureComputer>> entry : map.entrySet()) {
String name = prefix + ": " + entry.getKey().getName(imageData, false) + ":";
for (FeatureComputer computer : entry.getValue()) computer.addMeasurements(pathObject, name, params);
}
pathObject.getMeasurementList().close();
// Lock any measurements that require it
if (pathObject instanceof PathAnnotationObject)
((PathAnnotationObject) pathObject).setLocked(true);
else if (pathObject instanceof TMACoreObject)
((TMACoreObject) pathObject).setLocked(true);
return true;
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class PathObjectListCell method updateTooltip.
void updateTooltip(final PathObject pathObject) {
if (tooltip == null) {
if (pathObject == null || !pathObject.isAnnotation())
return;
tooltip = new Tooltip();
label.setTooltip(tooltip);
} else if (pathObject == null || !pathObject.isAnnotation()) {
label.setTooltip(null);
return;
}
PathAnnotationObject annotation = (PathAnnotationObject) pathObject;
String description = annotation.getDescription();
if (description == null)
description = label.getText();
if (description == null) {
label.setTooltip(null);
} else {
tooltip.setText(description);
label.setTooltip(tooltip);
}
}
Aggregations