Search in sources :

Example 11 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class ParallelTileObject method resolveOverlaps.

/**
 * Request that the tile object attempts to resolve overlaps with its neighboring tiles.
 */
public synchronized void resolveOverlaps() {
    // // If we don't have any children, notify that the test is complete
    // if (!hasChildren()) {
    // for (ParallelTileObject pto : map.keySet())
    // pto.notifyTestComplete(this);
    // map.clear();
    // checkAllTestsComplete();
    // return;
    // }
    long startTime = System.currentTimeMillis();
    int nRemoved = 0;
    boolean preferNucleus = false;
    // If we do have children, loop through & perform tests
    Iterator<Entry<ParallelTileObject, Rectangle2D>> iterMap = map.entrySet().iterator();
    while (iterMap.hasNext()) {
        Entry<ParallelTileObject, Rectangle2D> entry = iterMap.next();
        // If the parallel tile object hasn't been processed yet, then just continue - nothing to compare
        ParallelTileObject pto = entry.getKey();
        if (!pto.isComplete())
            continue;
        ParallelTileObject first, second;
        // Choose a consistent order for the comparison
        if (this.getROI().getBoundsX() > pto.getROI().getBoundsX() || this.getROI().getBoundsY() > pto.getROI().getBoundsY()) {
            first = this;
            second = pto;
        } else {
            first = pto;
            second = this;
        }
        // ROI firstBounds = first.getROI();
        // ROI secondBounds = second.getROI();
        // Compare this object's lists with that object's list
        List<PathObject> listFirst = first.getObjectsForRegion(entry.getValue());
        List<PathObject> listSecond = second.getObjectsForRegion(entry.getValue());
        // Only need to compare potential overlaps if both lists are non-empty
        if (!listFirst.isEmpty() && !listSecond.isEmpty()) {
            Map<ROI, Geometry> cache = new HashMap<>();
            Iterator<PathObject> iterFirst = listFirst.iterator();
            while (iterFirst.hasNext()) {
                PathObject firstObject = iterFirst.next();
                ROI firstROI = PathObjectTools.getROI(firstObject, preferNucleus);
                ImageRegion firstRegion = ImageRegion.createInstance(firstROI);
                Geometry firstGeometry = null;
                double firstArea = Double.NaN;
                Iterator<PathObject> iterSecond = listSecond.iterator();
                while (iterSecond.hasNext()) {
                    PathObject secondObject = iterSecond.next();
                    ROI secondROI = PathObjectTools.getROI(secondObject, preferNucleus);
                    // Do quick overlap test
                    if (!firstRegion.intersects(secondROI.getBoundsX(), secondROI.getBoundsY(), secondROI.getBoundsWidth(), secondROI.getBoundsHeight()))
                        continue;
                    // Get geometries
                    if (firstGeometry == null) {
                        firstGeometry = firstROI.getGeometry();
                        firstArea = firstGeometry.getArea();
                    }
                    Geometry secondGeometry = cache.get(secondROI);
                    if (secondGeometry == null) {
                        secondGeometry = secondROI.getGeometry();
                        cache.put(secondROI, secondGeometry);
                    }
                    Geometry intersection;
                    try {
                        // Get the intersection
                        if (!firstGeometry.intersects(secondGeometry))
                            continue;
                        intersection = firstGeometry.intersection(secondGeometry);
                    } catch (Exception e) {
                        logger.warn("Error resolving overlaps: {}", e.getLocalizedMessage());
                        logger.debug(e.getLocalizedMessage(), e);
                        continue;
                    }
                    if (intersection.isEmpty())
                        continue;
                    // Check areas
                    double intersectionArea = intersection.getArea();
                    double secondArea = secondGeometry.getArea();
                    double threshold = 0.1;
                    if (firstArea >= secondArea) {
                        if (intersectionArea / secondArea > threshold) {
                            second.removePathObject(secondObject);
                            nRemoved++;
                        }
                    } else {
                        if (intersectionArea / firstArea > threshold) {
                            first.removePathObject(firstObject);
                            nRemoved++;
                            break;
                        }
                    }
                }
            }
        }
        // Remove the neighbor from the map
        iterMap.remove();
        pto.notifyTestComplete(this);
    }
    checkAllTestsComplete();
    long endTime = System.currentTimeMillis();
    logger.debug(String.format("Resolved %d overlaps: %.2f seconds", nRemoved, (endTime - startTime) / 1000.));
// logger.info(String.format("Resolved %d possible overlaps with %d iterations (tested %d of %d): %.2f seconds", nOverlaps, counter, detectedCounter-skipCounter, detectedCounter, (endTime2 - startTime2) / 1000.));
}
Also used : HashMap(java.util.HashMap) Rectangle2D(java.awt.geom.Rectangle2D) ImageRegion(qupath.lib.regions.ImageRegion) ROI(qupath.lib.roi.interfaces.ROI) Geometry(org.locationtech.jts.geom.Geometry) Entry(java.util.Map.Entry) PathObject(qupath.lib.objects.PathObject)

Example 12 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PathObject method objectCountPostfix.

protected synchronized String objectCountPostfix() {
    ROI pathROI = getROI();
    if (pathROI != null && pathROI.isPoint()) {
        int nPoints = pathROI.getNumPoints();
        if (nPoints == 1)
            return " (1 point)";
        else
            return String.format(" (%d points)", nPoints);
    }
    if (!hasChildren())
        return "";
    int nChildren = nChildObjects();
    int nDescendants = PathObjectTools.countDescendants(this);
    if (nChildren == nDescendants)
        return " (" + nChildren + " objects)";
    return " (" + (nChildren) + "/" + nDescendants + " objects)";
// if (nDescendants == 1)
// return " - 1 descendant";
// else
// return " - " + nDescendants + " descendant";
// 
// if (childList.size() == 1)
// return " - 1 object";
// else
// return " - " + childList.size() + " objects";
}
Also used : ROI(qupath.lib.roi.interfaces.ROI)

Example 13 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class TMADataIO method writeTMAData.

/**
 * Write TMA data in a human-readable (and viewable) way, with JPEGs and TXT/CSV files.
 *
 * @param file
 * @param imageData
 * @param overlayOptions
 * @param downsampleFactor The downsample factor used for the TMA cores. If NaN, an automatic downsample value will be selected (&gt;= 1).  If &lt;= 0, no cores are exported.
 */
public static void writeTMAData(File file, final ImageData<BufferedImage> imageData, OverlayOptions overlayOptions, final double downsampleFactor) {
    if (imageData == null || imageData.getHierarchy() == null || imageData.getHierarchy().getTMAGrid() == null) {
        logger.error("No TMA data available to save!");
        return;
    }
    final ImageServer<BufferedImage> server = imageData.getServer();
    String coreExt = imageData.getServer().isRGB() ? ".jpg" : ".tif";
    if (file == null) {
        file = Dialogs.promptToSaveFile("Save TMA data", null, ServerTools.getDisplayableImageName(server), "TMA data", "qptma");
        if (file == null)
            return;
    } else if (file.isDirectory() || (!file.exists() && file.getAbsolutePath().endsWith(File.pathSeparator))) {
        // Put inside the specified directory
        file = new File(file, ServerTools.getDisplayableImageName(server) + TMA_DEARRAYING_DATA_EXTENSION);
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
    }
    final File dirData = new File(file + ".data");
    if (!dirData.exists())
        dirData.mkdir();
    // Write basic file info
    String delimiter = "\t";
    TMAGrid tmaGrid = imageData.getHierarchy().getTMAGrid();
    try {
        PrintWriter writer = new PrintWriter(file);
        writer.println(server.getPath());
        writer.println(ServerTools.getDisplayableImageName(server));
        writer.println();
        writer.println("TMA grid width: " + tmaGrid.getGridWidth());
        writer.println("TMA grid height: " + tmaGrid.getGridHeight());
        writer.println("Core name" + delimiter + "X" + delimiter + "Y" + delimiter + "Width" + delimiter + "Height" + delimiter + "Present" + delimiter + TMACoreObject.KEY_UNIQUE_ID);
        for (int row = 0; row < tmaGrid.getGridHeight(); row++) {
            for (int col = 0; col < tmaGrid.getGridWidth(); col++) {
                TMACoreObject core = tmaGrid.getTMACore(row, col);
                if (!core.hasROI()) {
                    writer.println(core.getName() + delimiter + delimiter + delimiter + delimiter);
                    continue;
                }
                ROI pathROI = core.getROI();
                int x = (int) pathROI.getBoundsX();
                int y = (int) pathROI.getBoundsY();
                int w = (int) Math.ceil(pathROI.getBoundsWidth());
                int h = (int) Math.ceil(pathROI.getBoundsHeight());
                String id = core.getUniqueID() == null ? "" : core.getUniqueID();
                writer.println(core.getName() + delimiter + x + delimiter + y + delimiter + w + delimiter + h + delimiter + !core.isMissing() + delimiter + id);
            }
        }
        writer.close();
    } catch (Exception e) {
        logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
        return;
    }
    // Save the summary results
    ObservableMeasurementTableData tableData = new ObservableMeasurementTableData();
    tableData.setImageData(imageData, tmaGrid.getTMACoreList());
    SummaryMeasurementTableCommand.saveTableModel(tableData, new File(dirData, "TMA results - " + ServerTools.getDisplayableImageName(server) + ".txt"), Collections.emptyList());
    boolean outputCoreImages = Double.isNaN(downsampleFactor) || downsampleFactor > 0;
    if (outputCoreImages) {
        // Create new overlay options, if we don't have some already
        if (overlayOptions == null) {
            overlayOptions = new OverlayOptions();
            overlayOptions.setFillDetections(true);
        }
        final OverlayOptions options = overlayOptions;
        // Write an overall TMA map (for quickly checking if the dearraying is ok)
        File fileTMAMap = new File(dirData, "TMA map - " + ServerTools.getDisplayableImageName(server) + ".jpg");
        double downsampleThumbnail = Math.max(1, (double) Math.max(server.getWidth(), server.getHeight()) / 1024);
        RegionRequest request = RegionRequest.createInstance(server.getPath(), downsampleThumbnail, 0, 0, server.getWidth(), server.getHeight());
        OverlayOptions optionsThumbnail = new OverlayOptions();
        optionsThumbnail.setShowTMAGrid(true);
        optionsThumbnail.setShowGrid(false);
        optionsThumbnail.setShowAnnotations(false);
        optionsThumbnail.setShowDetections(false);
        try {
            var renderedServer = new RenderedImageServer.Builder(imageData).layers(new TMAGridOverlay(overlayOptions)).downsamples(downsampleThumbnail).build();
            ImageWriterTools.writeImageRegion(renderedServer, request, fileTMAMap.getAbsolutePath());
        // ImageWriters.writeImageRegionWithOverlay(imageData.getServer(), Collections.singletonList(new TMAGridOverlay(overlayOptions, imageData)), request, fileTMAMap.getAbsolutePath());
        } catch (IOException e) {
            logger.warn("Unable to write image overview: " + e.getLocalizedMessage(), e);
        }
        final double downsample = Double.isNaN(downsampleFactor) ? (server.getPixelCalibration().hasPixelSizeMicrons() ? ServerTools.getDownsampleFactor(server, preferredExportPixelSizeMicrons) : 1) : downsampleFactor;
        // Creating a plugin makes it possible to parallelize & show progress easily
        var renderedImageServer = new RenderedImageServer.Builder(imageData).layers(new HierarchyOverlay(null, options, imageData)).downsamples(downsample).build();
        ExportCoresPlugin plugin = new ExportCoresPlugin(dirData, renderedImageServer, downsample, coreExt);
        PluginRunner<BufferedImage> runner;
        var qupath = QuPathGUI.getInstance();
        if (qupath == null || qupath.getImageData() != imageData) {
            runner = new CommandLinePluginRunner<>(imageData);
            plugin.runPlugin(runner, null);
        } else {
            try {
                qupath.runPlugin(plugin, null, false);
            } catch (Exception e) {
                logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
            }
        // new Thread(() -> qupath.runPlugin(plugin, null, false)).start();
        // runner = new PluginRunnerFX(QuPathGUI.getInstance());
        // new Thread(() -> plugin.runPlugin(runner, null)).start();
        }
    }
}
Also used : TMACoreObject(qupath.lib.objects.TMACoreObject) DefaultTMAGrid(qupath.lib.objects.hierarchy.DefaultTMAGrid) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) IOException(java.io.IOException) ROI(qupath.lib.roi.interfaces.ROI) RenderedImageServer(qupath.lib.gui.images.servers.RenderedImageServer) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) HierarchyOverlay(qupath.lib.gui.viewer.overlays.HierarchyOverlay) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) File(java.io.File) RegionRequest(qupath.lib.regions.RegionRequest) TMAGridOverlay(qupath.lib.gui.viewer.overlays.TMAGridOverlay) PrintWriter(java.io.PrintWriter)

Example 14 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class GuiTools method promptToSetActiveAnnotationProperties.

/**
 * Prompt the user to set properties for the currently-selected annotation(s).
 *
 * @param hierarchy current hierarchy
 * @return true if changes to annotation properties were made, false otherwise.
 */
public static boolean promptToSetActiveAnnotationProperties(final PathObjectHierarchy hierarchy) {
    PathObject currentObject = hierarchy.getSelectionModel().getSelectedObject();
    if (currentObject == null || !currentObject.isAnnotation())
        return false;
    ROI roi = currentObject.getROI();
    if (roi == null)
        return false;
    Collection<PathAnnotationObject> otherAnnotations = hierarchy.getSelectionModel().getSelectedObjects().stream().filter(p -> p.isAnnotation() && p != currentObject).map(p -> (PathAnnotationObject) p).collect(Collectors.toList());
    if (promptToSetAnnotationProperties((PathAnnotationObject) currentObject, otherAnnotations)) {
        hierarchy.fireObjectsChangedEvent(null, Collections.singleton(currentObject));
        // Ensure the object is still selected
        hierarchy.getSelectionModel().setSelectedObject(currentObject);
        return true;
    }
    return false;
}
Also used : CheckComboBox(org.controlsfx.control.CheckComboBox) UnaryOperator(java.util.function.UnaryOperator) TextFormatter(javafx.scene.control.TextFormatter) PathRootObject(qupath.lib.objects.PathRootObject) ParameterList(qupath.lib.plugins.parameters.ParameterList) PointsROI(qupath.lib.roi.PointsROI) Matcher(java.util.regex.Matcher) Action(java.awt.Desktop.Action) ColorTools(qupath.lib.common.ColorTools) GraphicsContext(javafx.scene.canvas.GraphicsContext) Canvas(javafx.scene.canvas.Canvas) Screen(javafx.stage.Screen) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) Platform(javafx.application.Platform) CountDownLatch(java.util.concurrent.CountDownLatch) Transparency(java.awt.Transparency) Clipboard(javafx.scene.input.Clipboard) SimpleDoubleProperty(javafx.beans.property.SimpleDoubleProperty) StringProperty(javafx.beans.property.StringProperty) Callable(java.util.concurrent.Callable) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) DefaultColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains.DefaultColorDeconvolutionStains) Bindings(javafx.beans.binding.Bindings) NumberFormat(java.text.NumberFormat) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) Slider(javafx.scene.control.Slider) SplitAnnotationsPlugin(qupath.lib.plugins.objects.SplitAnnotationsPlugin) GridPane(javafx.scene.layout.GridPane) Color(javafx.scene.paint.Color) GeneralTools(qupath.lib.common.GeneralTools) Commands(qupath.lib.gui.commands.Commands) Node(javafx.scene.Node) CheckBox(javafx.scene.control.CheckBox) IOException(java.io.IOException) File(java.io.File) PathObjectTools(qupath.lib.objects.PathObjectTools) Menu(javafx.scene.control.Menu) ROI(qupath.lib.roi.interfaces.ROI) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) ObservableValue(javafx.beans.value.ObservableValue) Image(javafx.scene.image.Image) Button(javafx.scene.control.Button) ImageServer(qupath.lib.images.servers.ImageServer) CombineOp(qupath.lib.roi.RoiTools.CombineOp) DoubleBinding(javafx.beans.binding.DoubleBinding) ListCell(javafx.scene.control.ListCell) CheckMenuItem(javafx.scene.control.CheckMenuItem) LoggerFactory(org.slf4j.LoggerFactory) RenderingHints(java.awt.RenderingHints) Side(javafx.geometry.Side) ContextMenu(javafx.scene.control.ContextMenu) URI(java.net.URI) QuPathGUI(qupath.lib.gui.QuPathGUI) TextField(javafx.scene.control.TextField) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Collection(java.util.Collection) Spinner(javafx.scene.control.Spinner) Collectors(java.util.stream.Collectors) StainVector(qupath.lib.color.StainVector) PathObject(qupath.lib.objects.PathObject) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) List(java.util.List) Robot(javafx.scene.robot.Robot) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) Pattern(java.util.regex.Pattern) ClipboardContent(javafx.scene.input.ClipboardContent) Scene(javafx.scene.Scene) ListView(javafx.scene.control.ListView) TextArea(javafx.scene.control.TextArea) ParsePosition(java.text.ParsePosition) DoubleProperty(javafx.beans.property.DoubleProperty) ColorDeconvolutionHelper(qupath.lib.color.ColorDeconvolutionHelper) Function(java.util.function.Function) Dialogs(qupath.lib.gui.dialogs.Dialogs) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains) SwingUtilities(javax.swing.SwingUtilities) Graphics2D(java.awt.Graphics2D) ActionTools(qupath.lib.gui.ActionTools) Tooltip(javafx.scene.control.Tooltip) ColorPicker(javafx.scene.control.ColorPicker) ImageData(qupath.lib.images.ImageData) Desktop(java.awt.Desktop) ObjectProperty(javafx.beans.property.ObjectProperty) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) WritableImage(javafx.scene.image.WritableImage) TMACoreObject(qupath.lib.objects.TMACoreObject) DoubleSpinnerValueFactory(javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) Stage(javafx.stage.Stage) SpinnerValueFactory(javafx.scene.control.SpinnerValueFactory) SwingFXUtils(javafx.embed.swing.SwingFXUtils) Window(javafx.stage.Window) Collections(java.util.Collections) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) PointsROI(qupath.lib.roi.PointsROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 15 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PathHierarchyPaintingHelper method paintObject.

/**
 * Paint an object (or, more precisely, its ROI), optionally along with the ROIs of any child objects.
 *
 * This is subject to the OverlayOptions, and therefore may not actually end up painting anything
 * (if the settings are such that objects of the class provided are not to be displayed)
 *
 * @param pathObject
 * @param paintChildren
 * @param g
 * @param boundsDisplayed
 * @param overlayOptions
 * @param selectionModel
 * @param downsample
 * @return true if anything was painted, false otherwise
 */
public static boolean paintObject(PathObject pathObject, boolean paintChildren, Graphics2D g, Rectangle boundsDisplayed, OverlayOptions overlayOptions, PathObjectSelectionModel selectionModel, double downsample) {
    if (pathObject == null)
        return false;
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    // Always paint the selected object
    // Note: this makes the assumption that child ROIs are completely contained within their parents;
    // this probably should be the case, but isn't guaranteed
    boolean isSelected = (selectionModel != null && selectionModel.isSelected(pathObject)) && (PathPrefs.useSelectedColorProperty().get() || !PathObjectTools.hasPointROI(pathObject));
    boolean isDetectedObject = pathObject.isDetection() || (pathObject.isTile() && pathObject.hasMeasurements());
    // Check if the PathClass isn't being shown
    PathClass pathClass = pathObject.getPathClass();
    if (!isSelected && overlayOptions != null && overlayOptions.isPathClassHidden(pathClass))
        return false;
    boolean painted = false;
    // See if we need to check the children
    ROI pathROI = pathObject.getROI();
    if (pathROI != null) {
        double roiBoundsX = pathROI.getBoundsX();
        double roiBoundsY = pathROI.getBoundsY();
        double roiBoundsWidth = pathROI.getBoundsWidth();
        double roiBoundsHeight = pathROI.getBoundsHeight();
        if (PathObjectTools.hasPointROI(pathObject) || boundsDisplayed == null || pathROI instanceof LineROI || boundsDisplayed.intersects(roiBoundsX, roiBoundsY, Math.max(roiBoundsWidth, 1), Math.max(roiBoundsHeight, 1))) {
            // Paint the ROI, if necessary
            if (isSelected || (overlayOptions.getShowDetections() && isDetectedObject) || (overlayOptions.getShowAnnotations() && pathObject.isAnnotation()) || (overlayOptions.getShowTMAGrid() && pathObject.isTMACore())) {
                boolean doFill = overlayOptions.getFillDetections() || pathObject instanceof ParallelTileObject;
                boolean doOutline = true;
                Color color = null;
                boolean useMapper = false;
                double fillOpacity = .75;
                if (isSelected && PathPrefs.useSelectedColorProperty().get() && PathPrefs.colorSelectedObjectProperty().getValue() != null)
                    color = ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get());
                else {
                    MeasurementMapper mapper = overlayOptions.getMeasurementMapper();
                    useMapper = mapper != null && mapper.isValid() && pathObject.isDetection();
                    if (useMapper) {
                        if (pathObject.hasMeasurements()) {
                            Integer rgb = mapper.getColorForObject(pathObject);
                            // If the mapper returns null, the object shouldn't be painted
                            if (rgb == null)
                                return false;
                            // , mapper.getColorMapper().hasAlpha());
                            color = ColorToolsAwt.getCachedColor(rgb);
                        } else
                            color = null;
                        // System.out.println(color + " - " + pathObject.getMeasurementList().getMeasurementValue(mapper.));
                        fillOpacity = 1.0;
                        // Outlines are not so helpful with the measurement mapper
                        if (doFill)
                            doOutline = doOutline && !pathObject.isTile();
                    } else {
                        Integer rgb = ColorToolsFX.getDisplayedColorARGB(pathObject);
                        color = ColorToolsAwt.getCachedColor(rgb);
                    }
                // color = PathObjectHelpers.getDisplayedColor(pathObject);
                }
                // Check if we have only one or two pixels to draw - if so, we can be done quickly
                if (isDetectedObject && downsample > 4 && roiBoundsWidth / downsample < 3 && roiBoundsHeight / downsample < 3) {
                    int x = (int) roiBoundsX;
                    int y = (int) roiBoundsY;
                    // Prefer rounding up, lest we lose a lot of regions unnecessarily
                    int w = (int) (roiBoundsWidth + .9);
                    int h = (int) (roiBoundsHeight + .9);
                    if (w > 0 && h > 0) {
                        g.setColor(color);
                        // g.setColor(DisplayHelpers.getMoreTranslucentColor(color));
                        // g.setStroke(getCachedStroke(overlayOptions.strokeThinThicknessProperty().get()));
                        g.fillRect(x, y, w, h);
                    }
                    painted = true;
                } else {
                    Stroke stroke = null;
                    // Decide whether to fill or not
                    Color colorFill = doFill && (isDetectedObject || PathObjectTools.hasPointROI(pathObject)) ? color : null;
                    if (colorFill != null && fillOpacity != 1) {
                        if (pathObject instanceof ParallelTileObject)
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        else if (pathObject instanceof PathCellObject && overlayOptions.getShowCellBoundaries() && overlayOptions.getShowCellNuclei()) {
                            // if (isSelected)
                            // colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                            // else
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        } else if (pathObject.getParent() instanceof PathDetectionObject) {
                            colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                        } else if (pathObject instanceof PathTileObject && pathClass == null && color != null && color.getRGB() == PathPrefs.colorTileProperty().get()) {
                            // Don't fill in empty, unclassified tiles
                            // DisplayHelpers.getMoreTranslucentColor(colorFill);
                            colorFill = null;
                        }
                    }
                    // Color colorStroke = doOutline ? (colorFill == null ? color : (downsample > overlayOptions.strokeThinThicknessProperty().get() ? null : DisplayHelpers.darkenColor(color))) : null;
                    Color colorStroke = doOutline ? (colorFill == null ? color : ColorToolsAwt.darkenColor(color)) : null;
                    // For thick lines, antialiasing is very noticeable... less so for thin lines (of which there may be a huge number)
                    if (isDetectedObject) {
                        // Detections inside detections get half the line width
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get() / 2.0);
                        else
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get());
                    } else {
                        double thicknessScale = downsample * (isSelected && !PathPrefs.useSelectedColorProperty().get() ? 1.6 : 1);
                        float thickness = (float) (PathPrefs.annotationStrokeThicknessProperty().get() * thicknessScale);
                        if (isSelected && pathObject.getParent() == null && PathPrefs.selectionModeProperty().get()) {
                            stroke = getCachedStrokeDashed(thickness);
                        } else {
                            stroke = getCachedStroke(thickness);
                        }
                    }
                    g.setStroke(stroke);
                    boolean paintSymbols = overlayOptions.getDetectionDisplayMode() == DetectionDisplayMode.CENTROIDS && pathObject.isDetection() && !pathObject.isTile();
                    if (paintSymbols) {
                        pathROI = PathObjectTools.getROI(pathObject, true);
                        double x = pathROI.getCentroidX();
                        double y = pathROI.getCentroidY();
                        double radius = PathPrefs.detectionStrokeThicknessProperty().get() * 2.0;
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            radius /= 2.0;
                        Shape shape;
                        int nSubclasses = 0;
                        if (pathClass != null) {
                            nSubclasses = PathClassTools.splitNames(pathClass).size();
                        }
                        switch(nSubclasses) {
                            case 0:
                                var ellipse = localEllipse2D.get();
                                ellipse.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = ellipse;
                                break;
                            case 1:
                                var rect = localRect2D.get();
                                rect.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = rect;
                                break;
                            case 2:
                                var triangle = localPath2D.get();
                                double sqrt3 = Math.sqrt(3.0);
                                triangle.reset();
                                triangle.moveTo(x, y - radius * 2.0 / sqrt3);
                                triangle.lineTo(x - radius, y + radius / sqrt3);
                                triangle.lineTo(x + radius, y + radius / sqrt3);
                                triangle.closePath();
                                shape = triangle;
                                break;
                            case 3:
                                var plus = localPath2D.get();
                                plus.reset();
                                plus.moveTo(x, y - radius);
                                plus.lineTo(x, y + radius);
                                plus.moveTo(x - radius, y);
                                plus.lineTo(x + radius, y);
                                shape = plus;
                                break;
                            default:
                                var cross = localPath2D.get();
                                cross.reset();
                                radius /= Math.sqrt(2);
                                cross.moveTo(x - radius, y - radius);
                                cross.lineTo(x + radius, y + radius);
                                cross.moveTo(x + radius, y - radius);
                                cross.lineTo(x - radius, y + radius);
                                shape = cross;
                                break;
                        }
                        paintShape(shape, g, colorStroke, stroke, colorFill);
                    } else if (pathObject instanceof PathCellObject) {
                        PathCellObject cell = (PathCellObject) pathObject;
                        if (overlayOptions.getShowCellBoundaries())
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        if (overlayOptions.getShowCellNuclei())
                            paintROI(cell.getNucleusROI(), g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    } else {
                        if ((overlayOptions.getFillAnnotations() && pathObject.isAnnotation() && pathObject.getPathClass() != PathClassFactory.getPathClass(StandardPathClasses.REGION) && (pathObject.getPathClass() != null || !pathObject.hasChildren())) || (pathObject.isTMACore() && overlayOptions.getShowTMACoreLabels()))
                            paintROI(pathROI, g, colorStroke, stroke, ColorToolsAwt.getMoreTranslucentColor(colorStroke), downsample);
                        else
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    }
                }
            }
        }
    }
    // Paint the children, if necessary
    if (paintChildren) {
        for (PathObject childObject : pathObject.getChildObjectsAsArray()) {
            // Only call the painting method if required
            ROI childROI = childObject.getROI();
            if ((childROI != null && boundsDisplayed.intersects(childROI.getBoundsX(), childROI.getBoundsY(), childROI.getBoundsWidth(), childROI.getBoundsHeight())) || childObject.hasChildren())
                painted = paintObject(childObject, paintChildren, g, boundsDisplayed, overlayOptions, selectionModel, downsample) | painted;
        }
    }
    return painted;
}
Also used : BasicStroke(java.awt.BasicStroke) Stroke(java.awt.Stroke) PathDetectionObject(qupath.lib.objects.PathDetectionObject) Shape(java.awt.Shape) RectangularShape(java.awt.geom.RectangularShape) Color(java.awt.Color) EllipseROI(qupath.lib.roi.EllipseROI) PointsROI(qupath.lib.roi.PointsROI) RectangleROI(qupath.lib.roi.RectangleROI) LineROI(qupath.lib.roi.LineROI) ROI(qupath.lib.roi.interfaces.ROI) ParallelTileObject(qupath.lib.plugins.ParallelTileObject) PathClass(qupath.lib.objects.classes.PathClass) PathTileObject(qupath.lib.objects.PathTileObject) PathObject(qupath.lib.objects.PathObject) MeasurementMapper(qupath.lib.gui.tools.MeasurementMapper) LineROI(qupath.lib.roi.LineROI) PathCellObject(qupath.lib.objects.PathCellObject)

Aggregations

ROI (qupath.lib.roi.interfaces.ROI)87 PathObject (qupath.lib.objects.PathObject)61 ArrayList (java.util.ArrayList)31 BufferedImage (java.awt.image.BufferedImage)24 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)24 IOException (java.io.IOException)20 RegionRequest (qupath.lib.regions.RegionRequest)19 List (java.util.List)17 Collectors (java.util.stream.Collectors)17 RectangleROI (qupath.lib.roi.RectangleROI)17 Logger (org.slf4j.Logger)16 LoggerFactory (org.slf4j.LoggerFactory)16 PolygonROI (qupath.lib.roi.PolygonROI)16 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)15 Point2D (java.awt.geom.Point2D)14 Collection (java.util.Collection)14 Collections (java.util.Collections)14 Geometry (org.locationtech.jts.geom.Geometry)14 PathClass (qupath.lib.objects.classes.PathClass)14 ImagePlane (qupath.lib.regions.ImagePlane)13