Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Creating objects in QuPath
Collections of scripts harvested mainly from Pete, but also picked up from the forums
Annotation subtraction.groovy - An example of creating and subtracing ROIs through scripting
Create object based on Viewer position.groovy - Creates an object at the Viewer position. In this case a rectangle. Useful if you want
to create the exact same size object multiple times and then move it to an area of interest for subsampling.
Creating a TMA from script.groovy - Creates a basic TMA, which you would need to then manually position. Easiest to start small and then
add rows and columns through the QuPath TMA menu.
Creating TMA annotations and set missing by area.groovy - Takes a grid, runs a tissue detection (edit this to your own), and then set
missing and delete annotations in any cores below a threshold.
Tumor invasion areas.groovy - Generates annotation bands from the surface of the tumor inward. Can be used on tumors on the
surface of the tissue (bordering whitespace), but check the link included in the script.
Use points to place cells.groovy - mark locations using the Points tool, then place cells of a given class/size on top of each point.
Zstack or Time series full image annotations.groovy - Go through all layers of a Z stack or time series and place a full image annotation
in each.
Zstack or time series annotation copy.groovy - Similar to previous, but take all current annotations, and copy them to ALL OTHER time
or Z frames.
//Courtesy of Sara McArdle from the La Jolla Institute
//Create multiple bands within tissue, with the distance from the tissue surface (um) determined by the variables below
double firstRadius = -750
double secondRadius = -850
import qupath.lib.roi.*
import qupath.lib.objects.*
def imageData = getCurrentImageData()
def hierarchy = imageData.getHierarchy()
//runPlugin('qupath.imagej.detect.tissue.SimpleTissueDetection2', '{"threshold": 155, "requestedPixelSizeMicrons": 20.0, "minAreaMicrons": 10000.0, "maxHoleAreaMicrons": 1000000.0, "darkBackground": false, "smoothImage": true, "medianCleanup": true, "dilateBoundaries": false, "smoothCoordinates": true, "excludeOnBoundary": false, "singleAnnotation": true}');
def outer=getAnnotationObjects().find{p -> (p.getLevel()==1) && (p.isAnnotation() == true)}
runPlugin('qupath.lib.plugins.objects.DilateAnnotationPlugin', '{"radiusMicrons": '+firstRadius+', "removeInterior": false, "constrainToParent": true}')
def middle=getAnnotationObjects().find{p -> (p.getLevel()==2) && (p.isAnnotation() == true)}
//Reselecting the outer object turns out to be very important before running the DilateAnnotationPlugin a second (or third) time.
runPlugin('qupath.lib.plugins.objects.DilateAnnotationPlugin', '{"radiusMicrons": '+secondRadius+', "removeInterior": false, "constrainToParent": true}')
//write some stuff if you have more than 1 outer object for odd shapes
def inner=getAnnotationObjects().find{p -> (p.getLevel()==3) && (p.isAnnotation() == true)}
def toAdd = []
def toRemove = []
outerRing = PathROIToolsAwt.combineROIs(outer.getROI(), middle.getROI(), PathROIToolsAwt.CombineOp.SUBTRACT)
toRemove << middle
def orObject=new PathAnnotationObject(outerRing, outer.getPathClass())
toAdd << orObject
innerRing = PathROIToolsAwt.combineROIs(middle.getROI(), inner.getROI(), PathROIToolsAwt.CombineOp.SUBTRACT)
def irObject = new PathAnnotationObject(innerRing, outer.getPathClass())
irObject.setName("Middle Zone")
toAdd << irObject
toRemove << outer
print "Done"
import qupath.lib.roi.RectangleROI
import qupath.lib.objects.PathAnnotationObject
// Size in pixels at the base resolution
// note that the actual size will be one pixel larger in each dimension
int size = 255
// Get center pixel
def viewer = getCurrentViewer()
int cx = viewer.getCenterPixelX()
int cy = viewer.getCenterPixelY()
// Create & add annotation
def roi = new RectangleROI(cx-size/2, cy-size/2, size, size)
def rgb = getColorRGB(50, 50, 200)
def pathClass = getPathClass('Other', rgb)
def annotation = new PathAnnotationObject(roi, pathClass)
//For those times when the automatic TMA dearrayer is having trouble picking up cores (too light/sparse)
// from
import qupath.lib.objects.TMACoreObject
import qupath.lib.objects.hierarchy.DefaultTMAGrid
// Enter the number of horizontal & vertical cores here
int numHorizontal = 12
int numVertical = 9
// Enter the core diameter, in millimetres
double diameterMM = 1.2
// Convert diameter to pixels
double diameterPixels = (diameterMM * 1000) / getCurrentImageData().getServer().getAveragedPixelSizeMicrons()
// Get the current ROI
def roi = getSelectedROI()
// Create the cores
def cores = []
double xSpacing = roi.getBoundsWidth() / numHorizontal
double ySpacing = roi.getBoundsHeight() / numVertical
for (int i = 0; i < numVertical; i++) {
for (int j = 0; j < numHorizontal; j++) {
double x = roi.getBoundsX() + xSpacing / 2 + xSpacing * j
double y = roi.getBoundsY() + ySpacing / 2 + ySpacing * i
cores << new TMACoreObject(x, y, diameterPixels, false)
// Create & set the grid
def tmaGrid = new DefaultTMAGrid(cores, numHorizontal)
//Set TMA cores where the tissue area is below a threshold to Missing so as to avoid inclusion in future calculations.
MINIMUM_AREA_um2 = 30000
def imageData = getCurrentImageData()
def server = imageData.getServer()
def pixelSize = server.getPixelHeightMicrons()
runPlugin('qupath.imagej.detect.tissue.SimpleTissueDetection2', '{"threshold": 233, "requestedPixelSizeMicrons": 10.0, "minAreaMicrons": 10000.0, "maxHoleAreaMicrons": 1000000.0, "darkBackground": false, "smoothImage": true, "medianCleanup": true, "dilateBoundaries": false, "smoothCoordinates": true, "excludeOnBoundary": false, "singleAnnotation": true}');
//Potentially alter script to run off of mean intensity in core?
//runPlugin('qupath.lib.algorithms.IntensityFeaturesPlugin', '{"pixelSizeMicrons": 2.0, "region": "ROI", "tileSizeMicrons": 25.0, "colorOD": true, "colorStain1": false, "colorStain2": false, "colorStain3": false, "colorRed": false, "colorGreen": false, "colorBlue": false, "colorHue": false, "colorSaturation": false, "colorBrightness": false, "doMean": false, "doStdDev": false, "doMinMax": false, "doMedian": false, "doHaralick": false, "haralickDistance": 1, "haralickBins": 32}');
list = it.getChildObjects()
if ( list.size() > 0){
double total = 0
for (object in list) {
total = total+object.getROI().getArea()
total = total*pixelSize*pixelSize
println(it.getName()+ " list "+list.size()+ "total "+total)
if ( total > MINIMUM_AREA_um2 ){
} else {removeObjects(list, true)}
* Script to help with annotating tumor regions, chopping increasing chunks into the tumor.
* Here, each of the margin regions is approximately 100 microns in width.
* Should work with both 1.2 and 0.2.0m2 due to code from Thomas Kilvaer found here:
* @author Pete Bankhead
* @mangled by Svidro
import qupath.lib.common.GeneralTools
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.objects.PathObject
import qupath.lib.roi.PathROIToolsAwt
import java.awt.Rectangle
import java.awt.geom.Area
// Some things you might want to change
// How much to expand each region
double expandMarginMicrons = 100.0
// How many times you want to chop into your annotation. Edit color script around line 115 if you go over 5
int howManyTimes = 4
// Define the colors
// Inner layers are given scripted colors, but gretaer than 6 or 7 layers may require adjustments
def colorOuterMargin = getColorRGB(0, 200, 0)
// Choose whether to lock the annotations or not (it's generally a good idea to avoid accidentally moving them)
def lockAnnotations = true
// Extract the main info we need
def imageData = getCurrentImageData()
def hierarchy = imageData.getHierarchy()
def server = imageData.getServer()
// We need the pixel size
if (!server.hasPixelSizeMicrons()) {
print 'We need the pixel size information here!'
if (!GeneralTools.almostTheSame(server.getPixelWidthMicrons(), server.getPixelHeightMicrons(), 0.0001)) {
print 'Warning! The pixel width & height are different; the average of both will be used'
// Get annotation & detections
def annotations = getAnnotationObjects()
def selected = getSelectedObject()
if (selected == null || !selected.isAnnotation()) {
print 'Please select an annotation object!'
// We need one selected annotation as a starting point; if we have other annotations, they will constrain the output
// If we have at most one other annotation, it represents the tissue
Area areaTissue
PathObject tissueAnnotation
if (annotations.isEmpty()) {
areaTissue = new Area(new Rectangle(0, 0, server.getWidth(), server.getHeight()))
} else if (annotations.size() == 1) {
tissueAnnotation = annotations.get(0)
areaTissue = PathROIToolsAwt.getArea(tissueAnnotation.getROI())
} else {
print 'Sorry, this script only support one selected annotation for the tumor region, and at most one other annotation to constrain the expansion'
println("Working, give it some time")
// Calculate how much to expand
double expandPixels = expandMarginMicrons / server.getAveragedPixelSizeMicrons()
def roiOriginal = selected.getROI()
def areaTumor = PathROIToolsAwt.getArea(roiOriginal)
// Get the outer margin area
if (getQuPath().getBuildString().split()[1]<"0.2.0-m2"){
def areaOuter = PathROIToolsAwt.shapeMorphology(areaTumor, expandPixels)
}else {areaOuter = PathROIToolsAwt.getArea(PathROIToolsAwt.roiMorphology(roiOriginal, expandPixels))}
def roiOuter = PathROIToolsAwt.getShapeROI(areaOuter, roiOriginal.getC(), roiOriginal.getZ(), roiOriginal.getT())
def annotationOuter = new PathAnnotationObject(roiOuter)
annotationOuter.setName("Outer margin")
innerAnnotations = []
innerAnnotations << annotationOuter
for (i=0; i<howManyTimes;i++){
//select the current expansion, which the first time is outside of the tumor, then expand it and intersect it
currentArea = PathROIToolsAwt.getArea(innerAnnotations[innerAnnotations.size()-1].getROI())
if (getQuPath().getBuildString().split()[1]<"0.2.0-m2"){
areaExpansion = PathROIToolsAwt.shapeMorphology(currentArea, expandPixels)
}else {areaExpansion = PathROIToolsAwt.getArea(PathROIToolsAwt.roiMorphology(innerAnnotations[innerAnnotations.size()-1].getROI(), expandPixels))}
for (k=1; k<=i;k++){
roiExpansion = PathROIToolsAwt.getShapeROI(areaExpansion, roiOriginal.getC(), roiOriginal.getZ(), roiOriginal.getT())
j = i+1
annotationExpansion = new PathAnnotationObject(roiExpansion)
int nameValue = j*expandMarginMicrons
annotationExpansion.setName("Inner margin "+nameValue+" microns")
annotationExpansion.setColorRGB(getColorRGB(20*i, 40*i, 200-30*i))
innerAnnotations << annotationExpansion
// Add the annotations
//hierarchy.removeObject(selected, true)
def annotationsToAdd = innerAnnotations;
annotationsToAdd.each {it.setLocked(lockAnnotations)}
if (tissueAnnotation == null) {
hierarchy.addPathObjects(annotationsToAdd, false)
} else {
hierarchy.fireHierarchyChangedEvent(this, tissueAnnotation)
if (lockAnnotations)
println("Done! Wheeeee!")
import qupath.lib.roi.EllipseROI;
import qupath.lib.objects.PathDetectionObject
points = getAnnotationObjects().findAll{it.isPoint() }
//Cycle through each points object (which is a collection of points)
//Cycle through all points within a points object
//for each point, create a circle on top of it that is "size" pixels in diameter
x = it.getX()
y = it.getY()
size = 5
def roi = new EllipseROI(x-size/2,y-size/2,size,size, 0,0,0)
pathClass = getPathClass("FakeCell")
def aCell = new PathDetectionObject(roi, pathClass)
//remove points if desired.
removeObjects(points, false)
//should work for Zstacks OR time series
//Copies ALL existing annotations to ALL other T or Z slices. Use getAnnotationObjects().findAll{it-> if(something)} to limit this
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.roi.PathROIToolsAwt
hierarchy = getCurrentHierarchy()
def imageData = getCurrentImageData()
def server = imageData.getServer()
def xdist = server.getWidth()
def ydist = server.getHeight()
def annotations = getAnnotationObjects()
if (server.nZSlices() >0){
for (annotation in annotations){
def roi = annotation.getROI()
def shape = PathROIToolsAwt.getShape(roi)
// There is a method to create a ROI from a shape which allows us to (finally) set the Z (or T)
def roi2 = PathROIToolsAwt.getShapeROI(shape, -1, it, roi.getT(), 0.5)
// We can now make a new annotation
def annotation2 = new PathAnnotationObject(roi2)
// Add it to the current hierarchy. When we move in Z to the desired slice, we should see the annotation
if (roi2.getZ() != roi.getZ())
hierarchy.addPathObject(annotation2, false);
if (server.nTimepoints() >1){
for (annotation in annotations){
def roi = annotation.getROI()
def shape = PathROIToolsAwt.getShape(roi)
// There is a method to create a ROI from a shape which allows us to (finally) set the Z (or T)
def roi2 = PathROIToolsAwt.getShapeROI(shape, -1, roi.getZ(), it)
// We can now make a new annotation
def annotation2 = new PathAnnotationObject(roi2)
// Add it to the current hierarchy. When we move in T to the desired slice, we should see the annotation
if (roi2.getT() != roi.getT())
hierarchy.addPathObject(annotation2, false);
//should work for Zstacks OR time series
//Creates a full image annotation in each frame, which can then be used to generate detections.
import qupath.lib.roi.RectangleROI
import qupath.lib.objects.PathAnnotationObject
hierarchy = getCurrentHierarchy()
def imageData = getCurrentImageData()
def server = imageData.getServer()
def xdist = server.getWidth()
def ydist = server.getHeight()
if (server.nZSlices() >0){
frame = new PathAnnotationObject(new RectangleROI(0,0,xdist,ydist,-1,it,0));
if (server.nTimepoints() >0){
frame = new PathAnnotationObject(new RectangleROI(0,0,xdist,ydist,-1,0,it));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.