Problem running script from publication

Hey all,

In the paper “Automated Quantification of Immunohistochemical Staining of Large Animal Brain Tissue Using QuPath Software” Morriss 2020 et al., use scripting inQuPath version 0.2.0 m2 to analyse brain histopathology images (https://doi.org/10.1016/j.neuroscience.2020.01.006).

I want to run the “Data Exportation and Heatmap Creation and Exportation” scripts for my dataset. I have installed the older 0.2.0 m2 version and included the modifications made by Pete to the original scripts (https://github.com/qupath/qupath/issues/309).

This creates a folder for annotation data just fine but then only exports an image of the slide (or tiles if I change the downsample value) with the annotation and the superpixels but not the heatmap. I was wondering if I should be getting output images like the ones in the paper or should this be done manually by creating measurement maps?

I am not sure if I am doing something wrong or if the script is not supposed to do what I wish it would. It would help my project a lot if I could do this, so any help would be much appreciated!

I paste the script I am running now, the message that script editor outputs (having made manual annotations in advance) and the examples from the paper below.

//runs superpixel analysis. Pay attention to comments- delineates what measurements likely need to be changed
selectAnnotations ();
//get superpixels. Adjust spacingMicrons as necessary
runPlugin('qupath.imagej.superpixels.SLICSuperpixelsPlugin', '{"sigmaMicrons": 5.0, "spacingMicrons": 50.0, "maxIterations": 10, "regularization": 0.25, "adaptRegularization": false, "useDeconvolved": false}');
runPlugin('qupath.lib.analysis.objects.TileClassificationsToAnnotationsPlugin', '{"pathClass": "All classes", "deleteTiles": false, "clearAnnotations": true, "splitAnnotations": false}');
selectDetections();
runPlugin('qupath.lib.algorithms.IntensityFeaturesPlugin', '{"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 200, "colorOD": false, "colorStain1": false, "colorStain2": true, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": true, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": true, "haralickDistance": 1, "haralickBins": 32}');
def roi = 'ROI: 2.00 ' + qupath.lib.common.GeneralTools.micrometerSymbol() + ' per pixel: DAB: Mean' // seperate superpixels into 1+, 2+, 3+
setIntensityClassifications(qupath.lib.objects.PathTileObject, roi , 0.22, 0.25, 0.33);
def name1 = getProjectEntry().getImageName() + '.txt'
def path1 = buildFilePath(PROJECT_BASE_DIR, 'annotation results') 
mkdirs(path1)
path1 = buildFilePath(path1, name1)
saveAnnotationMeasurements(path1) 
print 'Results exported to ' + path1
selectAnnotations();
import qupath.lib.gui.helpers.MeasurementMapper
import qupath.lib.gui.scripting.QPEx

// Define measurement & display range
def measure = 'ROI: 2.00 ' + qupath.lib.common.GeneralTools.micrometerSymbol() + ' per pixel: DAB: Mean'
double minValue = -0.04 
double maxValue = 0.6
// Request current viewer & objects
def viewer = getCurrentViewer()
def options = viewer.getOverlayOptions() 
def detections = getDetectionObjects()
// Update the display 
if (measure) {
print String.format('Setting measurement map: %s (%.2f - %.2f)', measure, minValue, maxValue) 
def mapper = new MeasurementMapper(measure, detections) 
mapper.setDisplayMinValue(minValue)
mapper.setDisplayMaxValue(maxValue)
options.setMeasurementMapper(mapper)
} 
else {
print 'Resetting measurement map'
options.setMeasurementMapper(null) 
}

//Exporting Image as .tif files
import qupath.lib.gui.ImageWriterTools

import qupath.lib.images.servers.ImageServer
import qupath.lib.regions.RegionRequest
import qupath.lib.scripting.QP

import java.awt.image.BufferedImage

/*
 * Adjustable parameters
 */
int tileWidthPixels =1500  // Width of (final) output tile in pixels
int tileHeightPixels = tileWidthPixels // Width of (final) output tile in pixels
double downsample = 70      // Downsampling used when extracting tiles
String format = "tif"       // Format of the output image - TIFF or ZIP is best for ImageJ to preserve pixel sizes
String dirOutput = buildFilePath(PROJECT_BASE_DIR, 'something else')     // BE SURE TO ADD AN OUTPUT DIRECTORY HERE!!!
mkdirs(dirOutput)

int maxErrors = 20          // Maximum number of errors... to avoid trying something doomed forever
int minImageDimension = 16  // If a tile will have a width or height < minImageDimension, it will be skipped
                            // This is needed to avoid trying to read/write images that are too tiny to be useful (and may even cause errors)

//-------------------------------------------------------

/*
 * Processing
 */

// Check we have an output directory
if (dirOutput == null) {
    println("Be sure to set the 'dirOutput' variable!")
    return
}

// Initialize error counter
int nErrors = 0

// Get the image server
ImageServer<BufferedImage> server = QP.getCurrentImageData().getServer()

// Ensure convert the format to a file extension
String ext
if (format.startsWith("."))
    ext = format.substring(1).toLowerCase()
else
    ext = format.toLowerCase()

// Extract useful variables
String path = server.getPath()
String serverName = server.getShortServerName()
double tileWidth = tileWidthPixels * downsample
double tileHeight = tileHeightPixels * downsample

// Loop through the image - including z-slices (even though there's normally only one...)
int counter = 0;
for (int z = 0; z < server.nZSlices(); z++) {
    for (double y = 0; y < server.getHeight(); y += tileHeight) {

        // Compute integer y coordinates
        int yi = (int)(y + 0.5)
        int y2i = (int)Math.min((int)(y + tileHeight + 0.5), server.getHeight());
        int hi = y2i - yi

        // Check if we requesting a region that is too small
        if (hi / downsample < minImageDimension) {
            println("Image dimension < " + minImageDimension + " - skipping row")
            continue
        }

        for (double x = 0; x < server.getWidth(); x += tileWidth) {

            // Compute integer x coordinates
            int xi = (int)(x + 0.5)
            int x2i = (int)Math.min((int)(x + tileWidth + 0.5), server.getWidth());
            int wi = x2i - xi

            // Create request
            RegionRequest request = RegionRequest.createInstance(path, downsample, xi, yi, wi, hi, z, 0)

            // Check if we requesting a region that is too small
            if (wi / downsample < minImageDimension) {
                // Only print warning if we've not skipped this before
                if (y > 0)
                    println("Image dimension < " + minImageDimension + " - skipping column")
                continue
            }

            // Surround with try/catch in case the server gives us trouble
            try {
                // Put at top of file for neater code...
                ext = ".jpg"
                imageData = getCurrentImageData()
                overlayOptions = getCurrentViewer().getOverlayOptions()
                
                // Write out the region with overlay
                String name = String.format("%s (d=%.2f, x=%d, y=%d, w=%d, h=%d, z=%d).%s", serverName, downsample, xi, yi, wi, hi, z, ext)
                File file = new File(dirOutput, name)
                ImageWriterTools.writeImageRegionWithOverlay(imageData, overlayOptions, request, file.getAbsolutePath())

                // Print progress
                counter++
                println("Written tile " + counter + " to " + file.getAbsolutePath())
            }
             catch (Exception e) {
                // Check if we have had a sufficient number of errors to just give up
                nErrors++;
                if (nErrors > maxErrors) {
                    println("Maximum number of errors exceeded - aborting...")
                    return
                }
                e.printStackTrace()
            }
        }
    }
}

Messages printed in the scripting window:

INFO: 118 tiles created (processing time: 0.11 seconds)
INFO: Processing complete in 0.11 seconds
INFO: Completed!
INFO: 
qupath.imagej.superpixels.SLICSuperpixelsPlugin  {"sigmaMicrons": 5.0, "spacingMicrons": 50.0, "maxIterations": 10, "regularization": 0.25, "adaptRegularization": false, "useDeconvolved": false}
INFO: Rectangle - 118 objects processed in 0.00 seconds
INFO: Processing complete in 0.03 seconds
INFO: Completed!
INFO: 
qupath.lib.analysis.objects.TileClassificationsToAnnotationsPlugin  {"pathClass": "All classes", "deleteTiles": false, "clearAnnotations": true, "splitAnnotations": false}
INFO: Processing complete in 0.05 seconds
INFO: Completed!
ERROR: QuPath exception
    at java.base/java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)
    at java.base/java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:741)
    at java.base/java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1045)
    at qupath.lib.gui.viewer.PathHierarchyPaintingHelper.paintSpecifiedObjects(PathHierarchyPaintingHelper.java:161)
    at qupath.lib.gui.viewer.overlays.HierarchyOverlay.paintOverlay(HierarchyOverlay.java:219)
    at qupath.lib.gui.viewer.QuPathViewer.paintViewer(QuPathViewer.java:1670)
    at qupath.lib.gui.viewer.QuPathViewer.paintCanvas(QuPathViewer.java:413)
    at qupath.lib.gui.viewer.QuPathViewerPlus.paintCanvas(QuPathViewerPlus.java:249)
    at qupath.lib.gui.viewer.QuPathViewer.lambda$repaint$4(QuPathViewer.java:501)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
INFO: 
qupath.lib.algorithms.IntensityFeaturesPlugin  {"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 200, "colorOD": false, "colorStain1": false, "colorStain2": true, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": true, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": true, "haralickDistance": 1, "haralickBins": 32}
INFO: Results exported to /Users/toneyan/Desktop/rotation/test_paper_script/annotation results/ALS_IHC_JHU86_IBA1.tif.txt
INFO: Setting measurement map: ROI: 2.00 µm per pixel: DAB: Mean (-0.04 - 0.60)
INFO: Measurement mapper limits for ROI: 2.00 µm per pixel: DAB: Mean: Infinity, -Infinity
INFO: SVSReader initializing /Users/toneyan/Desktop/rotation/ALS_IHC_JHU86_IBA1.tif
INFO: Reading IFDs
INFO: Populating metadata
INFO: Populating OME metadata
INFO: No memoization file generated for /Users/toneyan/Desktop/rotation/ALS_IHC_JHU86_IBA1.tif
INFO: Written tile 1 to /Users/toneyan/Desktop/rotation/test_paper_script/something else/ALS_IHC_JHU86_IBA1.tif (d=70.00, x=0, y=0, w=102442, h=69694, z=0)..jpg

Paper result examples:


Looks like the bug is described in https://github.com/qupath/qupath/issues/307

I don’t know if this is one of those things where forcing it to run on a single thread might help? Or force a delay?

If you can update the code to work in a later version I think the error should not show up, based on the lack of activity in that linked issue.

Hey, thanks for reacting so quickly!
When I try it in the newest m9 version I get this:

ERROR: It looks like you have tried to import a class 'qupath.lib.gui.helpers.MeasurementMapper' that doesn't exist!

I’m not really sure how I would upgrade this to be able to run it in the newer version. Do you know if something was changed in this class by any chance?
Thanks again!

1 Like

Quite a few things have probably changed since M2, though I don’t know much about that class specifically. It looks like the last time I used a script with that class it was written with the same import.
https://gist.github.com/Svidro/e00021dff92ea1173e535008854be72e#file-measurement-maps-color-lock-groovy
Looks to be one of Pete’s scripts that they built off of.
A bunch of changes are listed here as far as scripting if you want to dig into it.


But I don’t see anything about the measurementmapper there.

Going to the code: https://github.com/qupath/qupath
and searching for MeasurementMapper shows this:

d

As a final note, if you do get it all up and running for M9, it would probably be helpful to others to post the completed code! Good luck :slight_smile:

I hope I will get there and will definitely post the code if I do. I have changed a couple of things and now I get this error:

ERROR: GroovyRuntimeException at line 34: Could not find matching constructor for: qupath.lib.gui.tools.MeasurementMapper(String, ArrayList)

If I get it correctly, this is the line causing the issue:

def mapper = new MeasurementMapper(measure, detections) 

I had a look at the MeasurementMapper class and it does seem to have the constructor (see below) but I’m very confused with that the arguments should be and therefore what I should change in the script.

	public MeasurementMapper(ColorMapper mapper, String measurement, Collection<PathObject> pathObjects) {
		this.colorMapper = mapper;
		this.measurement = measurement;
		isClassProbability = measurement.toLowerCase().trim().equals("class probability");
		
		// Initialize max & min values
		minValueData = Double.POSITIVE_INFINITY;
		maxValueData = Double.NEGATIVE_INFINITY;
		for (PathObject pathObject : pathObjects) {
			double value = getUsefulValue(pathObject, Double.NaN);
			if (Double.isNaN(value) || Double.isInfinite(value))
				continue;
			if (value > maxValueData)
				maxValueData = value;
			if (value < minValueData)
				minValueData = value;
			valid = true;
		}
		// Set display range to match the data
		minValue = minValueData;
		maxValue = maxValueData;
		logger.debug("Measurement mapper limits for " + measurement + ": " + minValueData + ", " + maxValueData);
	}

Thanks again for the help so far and would be happy to hear any ideas of what I can try!

It looks like you are missing passing the ColorMapper… whatever that is. =/
*actually, that might be the various color maps that are now available. It used to be that there was just one color map, so you wouldn’t need to pass that. Partially my fault, I guess. But now things like Viridis and such are available!

I’m not really familiar with the publication or what the scripts do (even if I may have written some of them…), but here’s some minimal-ish code to create a MeasurementMapper in v0.2.0-m9

import qupath.lib.gui.tools.*

// Print the names (just to check which you want)
println MeasurementMapper.loadDefaultColorMaps()

// Choose one of them
def colorMapper = MeasurementMapper.loadDefaultColorMaps().find {it.getName() == 'Magma'}

// Create a measurement mapper
def detections = getDetectionObjects()
def measurementMapper = new MeasurementMapper(colorMapper, 'Nucleus: DAB OD mean', detections)

// Show the measurement mapper in the current viewer
def viewer = getCurrentViewer()
def overlayOptions = viewer.getOverlayOptions()
overlayOptions.setMeasurementMapper(measurementMapper)

It’s a bit ugly and subject to change, as none of the code around measurement mapping is really intended for scripting at this point.

If you describe what exactly you would like to achieve at the end, there might be a more direct way to get there than by modifying the older scripts included with that publication. Unless, of course, what you want to achieve is to replicate the paper exactly… in which case you have at least replicated the error reported by the first author, so must certainly be on the right track :slight_smile: .

1 Like

Hey Pete,
Thanks for your reply and for the code snippet!
I need to make SLIC superpixels, turn superpixels into annotations, set a threshold for superpixel classification and then export a measurement mapper overlay of the image to be used as a heatmap of where positive superpixels are.

If I get it correctly that’s also what the scripts from the paper do, and I thought it would be nice if I could take their scripts and see if they work without much further changes (for the sake of reproducibility and knowing if I can adapt other people’s scripts which I apparently can’t). At this point you are right though, I would also be happy to start from scratch as long as I get the script ready.

This is what I have at the moment:

//runs superpixel analysis. Pay attention to comments- delineates what measurements likely need to be changed
selectAnnotations ();
//get superpixels. Adjust spacingMicrons as necessary
runPlugin('qupath.imagej.superpixels.SLICSuperpixelsPlugin', '{"sigmaMicrons": 5.0, "spacingMicrons": 50.0, "maxIterations": 10, "regularization": 0.25, "adaptRegularization": false, "useDeconvolved": false}');
runPlugin('qupath.lib.plugins.objects.TileClassificationsToAnnotationsPlugin', '{"pathClass": "All classes",  "deleteTiles": false,  "clearAnnotations": true,  "splitAnnotations": false}');
selectDetections();
runPlugin('qupath.lib.algorithms.IntensityFeaturesPlugin', '{"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 200, "colorOD": false, "colorStain1": false, "colorStain2": true, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": true, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": true, "haralickDistance": 1, "haralickBins": 32}');
import qupath.lib.gui.tools.*

// Print the names (just to check which you want)
println MeasurementMapper.loadDefaultColorMaps()
// Request current viewer & objects
// Choose one of them
def colorMapper = MeasurementMapper.loadDefaultColorMaps().find {it.getName() == 'Viridis'}

// Create a measurement mapper
def detections = getDetectionObjects()
def measurementMapper = new MeasurementMapper(colorMapper, 'Cytoplasm: DAB OD mean', detections)

// Show the measurement mapper in the current viewer
def viewer = getCurrentViewer()
def overlayOptions = viewer.getOverlayOptions()
overlayOptions.setMeasurementMapper(measurementMapper)

When I run this the measurement mapper does not do anything though. I am not sure if it is even compatible with tiles/superpixels in scripts (I know it works when I get measurement maps though the GUI though). As always, any help in figuring out what I am doing wrong would be much appreciated!

Just a quick reply from my phone, I expect you need to change ‘Cytoplasm: DAB OD mean’ to one of your actual measurement names for the superpixels.

Oh my bad, completely forgot about that difference. This is great, it does work this way! Now for the last step of exporting this with the overlay of the measurement mapper, would you recommend trying to use the script from the paper or is there an easier way of either exporting the annotated region with the overlay (preferably) or the whole image with the overlay?

Many thanks for your time!!!

1 Like

If you are in M9, have you taken a look at any of these:
https://qupath.readthedocs.io/en/latest/docs/advanced/exporting_images.html

The last option in the first box writes out the viewer, though I think that is the equivalent of the File, Export snapshot, Current viewer command, and you would need to keep the magnification the same manually (or in a script) in order to make the images comparable.

If you have few enough images that you are willing to put up with that… but it won’t give you similar to what the above script was doing, which is writing out a lot of tiles. This would be useful if you want an overview image.

@petebankhead I am guessing anything further would need a RenderedImageServer.Builder to use the ImageWriterTools in M9?

@Shushan_Toneyan it should be possible to replace a lot of the writing code with something like this:

import qupath.lib.gui.images.servers.RenderedImageServer

// Parameters you might want to change
def path = buildFilePath(PROJECT_BASE_DIR, 'my image.png')
double downsample = 4.0
def roi = getSelectedObject().getROI()

// Create an ImageServer that reflects the current viewer appearance
def viewer = getCurrentViewer()
def renderedServer = RenderedImageServer.createRenderedServer(viewer)

// Write the image
def request = RegionRequest.createInstance(renderedServer.getPath(), downsample, roi)
writeImageRegion(renderedServer, request, path)

although the details will matter (if your image is small enough to fit in a PNG, if you want to export separate image tiles etc.). It will require the image to be open in the viewer, but it shouldn’t matter what the magnification is within the viewer (since the script here defines the ‘downsample’ factor itself).

Hey Pete,
Thanks for this! For some reason I get a NullPointerException when I run this with the script I had. This is what I run:

//runs superpixel analysis. Pay attention to comments- delineates what measurements likely need to be changed
selectAnnotations ();
//get superpixels. Adjust spacingMicrons as necessary
runPlugin('qupath.imagej.superpixels.SLICSuperpixelsPlugin', '{"sigmaMicrons": 5.0, "spacingMicrons": 50.0, "maxIterations": 10, "regularization": 0.25, "adaptRegularization": false, "useDeconvolved": false}');
runPlugin('qupath.lib.plugins.objects.TileClassificationsToAnnotationsPlugin', '{"pathClass": "All classes",  "deleteTiles": false,  "clearAnnotations": true,  "splitAnnotations": false}');
selectDetections();
runPlugin('qupath.lib.algorithms.IntensityFeaturesPlugin', '{"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 200, "colorOD": false, "colorStain1": false, "colorStain2": true, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": true, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": true, "haralickDistance": 1, "haralickBins": 32}');
import qupath.lib.gui.tools.*

// Print the names (just to check which you want)
println MeasurementMapper.loadDefaultColorMaps()
// Request current viewer & objects
// Choose one of them
def colorMapper = MeasurementMapper.loadDefaultColorMaps().find {it.getName() == 'Viridis'}

// Create a measurement mapper
def detections = getDetectionObjects()
def measurementMapper = new MeasurementMapper(colorMapper, 'ROI: 2.00 µm per pixel: DAB: Mean', detections)

// Show the measurement mapper in the current viewer
def viewer1 = getCurrentViewer()
def overlayOptions = viewer1.getOverlayOptions()
overlayOptions.setMeasurementMapper(measurementMapper)

import qupath.lib.gui.images.servers.RenderedImageServer

// Parameters you might want to change
def path = buildFilePath('my image.png')
double downsample = 4.0
def roi = getSelectedObject().getROI()

// Create an ImageServer that reflects the current viewer appearance
def viewer = getCurrentViewer()
def renderedServer = RenderedImageServer.createRenderedServer(viewer)

// Write the image
def request = RegionRequest.createInstance(renderedServer.getPath(), downsample, roi)
writeImageRegion(renderedServer, request, path)

And this is the error message I get:

INFO: 483 tiles created (processing time: 0.56 seconds)
INFO: Processing complete in 0.56 seconds
INFO: Completed!
INFO: 
qupath.imagej.superpixels.SLICSuperpixelsPlugin  {"sigmaMicrons": 5.0, "spacingMicrons": 50.0, "maxIterations": 10, "regularization": 0.25, "adaptRegularization": false, "useDeconvolved": false}
INFO: Annotation (Rectangle) (483 objects) processed in 0.00 seconds
INFO: Processing complete in 0.04 seconds
INFO: Completed!
INFO: 
qupath.lib.plugins.objects.TileClassificationsToAnnotationsPlugin  {"pathClass": "All classes",  "deleteTiles": false,  "clearAnnotations": true,  "splitAnnotations": false}
INFO: Processing complete in 0.07 seconds
INFO: Completed!
INFO: 
qupath.lib.algorithms.IntensityFeaturesPlugin  {"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 200, "colorOD": false, "colorStain1": false, "colorStain2": true, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": true, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": true, "haralickDistance": 1, "haralickBins": 32}
INFO: [Inferno, Svidro2, Viridis, Plasma, Magma]
ERROR: NullPointerException at line 30: Cannot invoke method getROI() on null object

ERROR: Script error (NullPointerException)
    at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:43)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:34)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
    at Script29.run(Script29.groovy:31)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:317)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:155)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:800)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:734)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:714)
    at qupath.lib.gui.scripting.DefaultScriptEditor$2.run(DefaultScriptEditor.java:1130)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

I tried changing some things (e.g. selecting annotations before getting the roi) but nothing helped. As always, any directions are much appreciated!

1 Like

getSelectedObject is looking for a single object. If you have multiple annotations, you might need to merge them to use this script exactly as written. This will ruin any measurements you have generated for the individual annotation bits until you split them apart again. Use with care, make sure you save before running!

selectAnnotations()
mergeSelectedAnnotations()

That script was, I think, intended to be run as a stand alone script where you have selected the object you want manually. I also don’t think it will work in a Run for Project setting as it requires the viewer.

You could try defining the roi with

which will grab only the first annotation object, and a single ROI.

Hello,
I was wondering if you were able to get the script to work with m9? I am using the script that was published and trying to manipulate it with what I have seen in this feed and cannot get it to work