use of qupath.lib.roi.RectangleROI in project qupath by qupath.
the class IconFactory method createROIIcon.
/**
* Create an icon depicting a ROI.
* @param roi the region of interest
* @param width the preferred icon width
* @param height the preferred icon height
* @param color the icon (line) color
* @return a node that may be used as an icon resembling the shape of the ROI
*/
public static Node createROIIcon(ROI roi, int width, int height, Color color) {
double scale = Math.min(width / roi.getBoundsWidth(), height / roi.getBoundsHeight());
if (roi instanceof RectangleROI) {
Rectangle rect = new Rectangle(0, 0, roi.getBoundsWidth() * scale, roi.getBoundsHeight() * scale);
rect.setStroke(color);
rect.setFill(null);
return rect;
} else if (roi instanceof EllipseROI) {
double w = roi.getBoundsWidth() * scale;
double h = roi.getBoundsHeight() * scale;
Ellipse ellipse = new Ellipse(w / 2, height / 2, w / 2, h / 2);
ellipse.setStroke(color);
ellipse.setFill(null);
return ellipse;
} else if (roi instanceof LineROI) {
LineROI l = (LineROI) roi;
double xMin = Math.min(l.getX1(), l.getX2());
double yMin = Math.min(l.getY1(), l.getY2());
Line line = new Line((l.getX1() - xMin) * scale, (l.getY1() - yMin) * scale, (l.getX2() - xMin) * scale, (l.getY2() - yMin) * scale);
line.setStroke(color);
line.setFill(null);
return line;
} else if (roi.isPoint()) {
// Just show generic points
Node node = IconFactory.createNode(Math.min(width, height), Math.min(width, height), IconFactory.PathIcons.POINTS_TOOL);
if (node instanceof Glyph) {
var glyph = (Glyph) node;
glyph.textFillProperty().unbind();
glyph.setColor(color);
}
return node;
} else {
var path = pathCache.getOrDefault(roi, null);
if (path == null) {
var shape = roi.isArea() ? RoiTools.getArea(roi) : RoiTools.getShape(roi);
if (shape != null) {
var transform = new AffineTransform();
transform.translate(-roi.getBoundsX(), -roi.getBoundsY());
transform.scale(scale, scale);
PathIterator iterator = shape.getPathIterator(transform, Math.max(0.5, 1.0 / scale));
path = createShapeIcon(iterator, color);
pathCache.put(roi, path);
}
} else {
path = new Path(path.getElements());
path.setStroke(color);
}
if (path != null)
return path;
}
logger.warn("Unable to create icon for ROI: {}", roi);
return null;
}
use of qupath.lib.roi.RectangleROI in project qupath by qupath.
the class IJTools method convertToIJRoi.
/**
* Convert a QuPath ROI to an ImageJ Roi.
* @param <T>
* @param pathROI
* @param xOrigin x-origin indicating relationship of ImagePlus to the original image, as stored in ImageJ Calibration object
* @param yOrigin y-origin indicating relationship of ImagePlus to the original image, as stored in ImageJ Calibration object
* @param downsampleFactor downsample factor at which the ImagePlus was extracted from the full-resolution image
* @return
*/
public static <T extends PathImage<ImagePlus>> Roi convertToIJRoi(ROI pathROI, double xOrigin, double yOrigin, double downsampleFactor) {
if (pathROI instanceof PolygonROI)
return ROIConverterIJ.convertToPolygonROI((PolygonROI) pathROI, xOrigin, yOrigin, downsampleFactor);
if (pathROI instanceof RectangleROI)
return ROIConverterIJ.getRectangleROI((RectangleROI) pathROI, xOrigin, yOrigin, downsampleFactor);
if (pathROI instanceof EllipseROI)
return ROIConverterIJ.convertToOvalROI((EllipseROI) pathROI, xOrigin, yOrigin, downsampleFactor);
if (pathROI instanceof LineROI)
return ROIConverterIJ.convertToLineROI((LineROI) pathROI, xOrigin, yOrigin, downsampleFactor);
if (pathROI instanceof PolylineROI)
return ROIConverterIJ.convertToPolygonROI((PolylineROI) pathROI, xOrigin, yOrigin, downsampleFactor);
if (pathROI instanceof PointsROI)
return ROIConverterIJ.convertToPointROI((PointsROI) pathROI, xOrigin, yOrigin, downsampleFactor);
// If we have any other kind of shape, create a general shape roi
if (pathROI != null && pathROI.isArea()) {
// TODO: Deal with non-AWT area ROIs!
Shape shape = RoiTools.getArea(pathROI);
// "scaleX", "shearY", "shearX", "scaleY", "translateX", "translateY"
shape = new AffineTransform(1.0 / downsampleFactor, 0, 0, 1.0 / downsampleFactor, xOrigin, yOrigin).createTransformedShape(shape);
return ROIConverterIJ.setIJRoiProperties(new ShapeRoi(shape), pathROI);
}
// TODO: Integrate ROI not supported exception...?
return null;
}
use of qupath.lib.roi.RectangleROI in project qupath by qupath.
the class BrushTool method createShape.
/**
* Create a new Geometry using the specified tool, assuming a user click/drag at the provided x & y coordinates.
* @param e
*
* @param x
* @param y
* @param useTiles If true, request generating a shape from existing tile objects.
* @param addToShape If provided, it can be assumed that any new shape ought to be added to this one.
* The purpose is that this method may (optionally) use the shape to refine the one it will generate,
* e.g. to avoid having isolated or jagged boundaries.
* @return
*/
protected Geometry createShape(MouseEvent e, double x, double y, boolean useTiles, Geometry addToShape) {
// See if we're on top of a tile
if (useTiles) {
List<PathObject> listSelectable = getSelectableObjectList(x, y);
for (PathObject temp : listSelectable) {
// if ((temp instanceof PathDetectionObject) && temp.getROI() instanceof PathArea)
if (temp instanceof PathTileObject && temp.hasROI() && temp.getROI().isArea() && !(temp.getROI() instanceof RectangleROI)) {
creatingTiledROI = true;
return temp.getROI().getGeometry();
}
}
// If we're currently creating a tiled, ROI, but now not clicked on a tile, just return
if (creatingTiledROI)
return null;
}
// Compute a diameter scaled according to the pressure being applied
double diameter = Math.max(1, getBrushDiameter());
Geometry geometry;
if (lastPoint == null) {
var shapeFactory = new GeometricShapeFactory(getGeometryFactory());
shapeFactory.setCentre(new Coordinate(x, y));
shapeFactory.setSize(diameter);
// shapeFactory.setCentre(new Coordinate(x-diameter/2, y-diameter/2));
geometry = shapeFactory.createEllipse();
} else {
if (lastPoint.distanceSq(x, y) == 0)
return null;
var factory = getGeometryFactory();
geometry = factory.createLineString(new Coordinate[] { new Coordinate(lastPoint.getX(), lastPoint.getY()), new Coordinate(x, y) }).buffer(diameter / 2.0);
}
return geometry;
}
use of qupath.lib.roi.RectangleROI in project qupath by qupath.
the class ImageDetailsPane method editStainVector.
void editStainVector(Object value) {
if (!(value instanceof StainVector || value instanceof double[]))
return;
// JOptionPane.showMessageDialog(null, "Modifying stain vectors not yet implemented...");
ImageData<BufferedImage> imageData = qupath.getImageData();
if (imageData == null)
return;
ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
// Default to background values
int num = -1;
String name = null;
String message = null;
if (value instanceof StainVector) {
StainVector stainVector = (StainVector) value;
if (stainVector.isResidual() && imageData.getImageType() != ImageType.BRIGHTFIELD_OTHER) {
logger.warn("Cannot set residual stain vector - this is computed from the known vectors");
return;
}
num = stains.getStainNumber(stainVector);
if (num <= 0) {
logger.error("Could not identify stain vector " + stainVector + " inside " + stains);
return;
}
name = stainVector.getName();
message = "Set stain vector from ROI?";
} else
message = "Set color deconvolution background values from ROI?";
ROI pathROI = imageData.getHierarchy().getSelectionModel().getSelectedROI();
boolean wasChanged = false;
String warningMessage = null;
boolean editableName = imageData.getImageType() == ImageType.BRIGHTFIELD_OTHER;
if (pathROI != null) {
if ((pathROI instanceof RectangleROI) && !pathROI.isEmpty() && ((RectangleROI) pathROI).getArea() < 500 * 500) {
if (Dialogs.showYesNoDialog("Color deconvolution stains", message)) {
ImageServer<BufferedImage> server = imageData.getServer();
BufferedImage img = null;
try {
img = server.readBufferedImage(RegionRequest.createInstance(server.getPath(), 1, pathROI));
} catch (IOException e) {
Dialogs.showErrorMessage("Set stain vector", "Unable to read image region");
logger.error("Unable to read region", e);
}
int rgb = ColorDeconvolutionHelper.getMedianRGB(img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()));
if (num >= 0) {
StainVector vectorValue = ColorDeconvolutionHelper.generateMedianStainVectorFromPixels(name, img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()), stains.getMaxRed(), stains.getMaxGreen(), stains.getMaxBlue());
if (!Double.isFinite(vectorValue.getRed() + vectorValue.getGreen() + vectorValue.getBlue())) {
Dialogs.showErrorMessage("Set stain vector", "Cannot set stains for the current ROI!\n" + "It might be too close to the background color.");
return;
}
value = vectorValue;
} else {
// Update the background
value = new double[] { ColorTools.red(rgb), ColorTools.green(rgb), ColorTools.blue(rgb) };
}
wasChanged = true;
}
} else {
warningMessage = "Note: To set stain values from an image region, draw a small, rectangular ROI first";
}
}
// Prompt to set the name / verify stains
ParameterList params = new ParameterList();
String title;
String nameBefore = null;
String valuesBefore = null;
String collectiveNameBefore = stains.getName();
String suggestedName;
if (collectiveNameBefore.endsWith("default"))
suggestedName = collectiveNameBefore.substring(0, collectiveNameBefore.lastIndexOf("default")) + "modified";
else
suggestedName = collectiveNameBefore;
params.addStringParameter("collectiveName", "Collective name", suggestedName, "Enter collective name for all 3 stains (e.g. H-DAB Scanner A, H&E Scanner B)");
if (value instanceof StainVector) {
nameBefore = ((StainVector) value).getName();
valuesBefore = ((StainVector) value).arrayAsString(Locale.getDefault(Category.FORMAT));
params.addStringParameter("name", "Name", nameBefore, "Enter stain name").addStringParameter("values", "Values", valuesBefore, "Enter 3 values (red, green, blue) defining color deconvolution stain vector, separated by spaces");
title = "Set stain vector";
} else {
nameBefore = "Background";
valuesBefore = GeneralTools.arrayToString(Locale.getDefault(Category.FORMAT), (double[]) value, 2);
params.addStringParameter("name", "Stain name", nameBefore);
params.addStringParameter("values", "Stain values", valuesBefore, "Enter 3 values (red, green, blue) defining background, separated by spaces");
params.setHiddenParameters(true, "name");
title = "Set background";
}
if (warningMessage != null)
params.addEmptyParameter(warningMessage);
// Disable editing the name if it should be fixed
ParameterPanelFX parameterPanel = new ParameterPanelFX(params);
parameterPanel.setParameterEnabled("name", editableName);
;
if (!Dialogs.showConfirmDialog(title, parameterPanel.getPane()))
return;
// Check if anything changed
String collectiveName = params.getStringParameterValue("collectiveName");
String nameAfter = params.getStringParameterValue("name");
String valuesAfter = params.getStringParameterValue("values");
if (collectiveName.equals(collectiveNameBefore) && nameAfter.equals(nameBefore) && valuesAfter.equals(valuesBefore) && !wasChanged)
return;
double[] valuesParsed = ColorDeconvolutionStains.parseStainValues(Locale.getDefault(Category.FORMAT), valuesAfter);
if (valuesParsed == null) {
logger.error("Input for setting color deconvolution information invalid! Cannot parse 3 numbers from {}", valuesAfter);
return;
}
if (num >= 0) {
try {
stains = stains.changeStain(StainVector.createStainVector(nameAfter, valuesParsed[0], valuesParsed[1], valuesParsed[2]), num);
} catch (Exception e) {
logger.error("Error setting stain vectors", e);
Dialogs.showErrorMessage("Set stain vectors", "Requested stain vectors are not valid!\nAre two stains equal?");
}
} else {
// Update the background
stains = stains.changeMaxValues(valuesParsed[0], valuesParsed[1], valuesParsed[2]);
}
// Set the collective name
stains = stains.changeName(collectiveName);
imageData.setColorDeconvolutionStains(stains);
qupath.getViewer().repaintEntireImage();
}
use of qupath.lib.roi.RectangleROI in project qupath by qupath.
the class PixelClassifierPane method showOutput.
private boolean showOutput() {
if (overlay == null) {
Dialogs.showErrorMessage("Show output", "No pixel classifier has been trained yet!");
return false;
}
var viewer = qupath.getViewer();
var imageData = viewer.getImageData();
var server = imageData == null ? null : overlay.getPixelClassificationServer(imageData);
if (server == null)
return false;
var selected = viewer.getSelectedObject();
var roi = selected == null ? null : selected.getROI();
double downsample = server.getDownsampleForResolution(0);
RegionRequest request;
if (roi == null) {
request = RegionRequest.createInstance(server.getPath(), downsample, 0, 0, server.getWidth(), server.getHeight(), viewer.getZPosition(), viewer.getTPosition());
} else {
request = RegionRequest.createInstance(server.getPath(), downsample, selected.getROI());
}
long estimatedPixels = (long) Math.ceil(request.getWidth() / request.getDownsample()) * (long) Math.ceil(request.getHeight() / request.getDownsample());
double estimatedMB = (estimatedPixels * server.nChannels() * (server.getPixelType().getBytesPerPixel())) / (1024.0 * 1024.0);
if (estimatedPixels >= Integer.MAX_VALUE - 16) {
Dialogs.showErrorMessage("Extract output", "Requested region is too big! Try selecting a smaller region.");
return false;
} else if (estimatedMB >= 200.0) {
if (!Dialogs.showConfirmDialog("Extract output", String.format("Extracting this region will require approximately %.1f MB - are you sure you want to try this?", estimatedMB)))
return false;
}
try {
// var imp = IJExtension.extractROI(server, selected, request, true, null).getImage();
var pathImage = IJTools.convertToImagePlus(server, request);
var imp = pathImage.getImage();
if (imp instanceof CompositeImage && server.getMetadata().getChannelType() != ChannelType.CLASSIFICATION)
((CompositeImage) imp).setDisplayMode(CompositeImage.GRAYSCALE);
if (roi != null && !(roi instanceof RectangleROI)) {
imp.setRoi(IJTools.convertToIJRoi(roi, pathImage));
}
IJExtension.getImageJInstance();
imp.show();
return true;
} catch (IOException e) {
logger.error("Error showing output", e);
}
return false;
}
Aggregations