Creating objects in QuPath
Collections of scripts harvested mainly from Pete, but also picked up from the forums | |
TOC | |
Annotation subtraction example.groovy - An example of creating and subtracing ROIs through scripting | |
Create annotation of fixed size.groovy - see https://petebankhead.github.io/qupath/scripting/2018/03/09/script-create-fixed-size-region.html | |
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. | |
TMA Dearrayer from a script.groovy - More options for the TMA dearrayer than through the GUI. Read through the script carefully. | |
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 | |
//Adjust class names within the script | |
//0.2.0 | |
double firstRadius = -750 | |
double secondRadius = -850 | |
import qupath.lib.roi.* | |
import qupath.lib.objects.* | |
def imageData = getCurrentImageData() | |
def hierarchy = imageData.getHierarchy() | |
//EDIT THIS LINE TO YOUR TISSUE DETECTION SETTINGS | |
//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}'); | |
selectAnnotations() | |
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. | |
getCurrentHierarchy().getSelectionModel().setSelectedObject(outer) | |
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)} | |
inner.setName("Medulla") | |
def toAdd = [] | |
def toRemove = [] | |
outerRing = PathROIToolsAwt.combineROIs(outer.getROI(), middle.getROI(), PathROIToolsAwt.CombineOp.SUBTRACT) | |
toRemove << middle | |
def orObject=new PathAnnotationObject(outerRing, outer.getPathClass()) | |
orObject.setName("Cortex") | |
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 | |
hierarchy.addPathObjects(toAdd,false) | |
hierarchy.removeObjects(toRemove,true) | |
print "Done" |
/** | |
* Create a region annotation with a fixed size in QuPath, based on the current viewer location. | |
* 0.1.2 | |
* @author Pete Bankhead | |
*/ | |
import qupath.lib.objects.PathAnnotationObject | |
import qupath.lib.objects.classes.PathClassFactory | |
import qupath.lib.roi.RectangleROI | |
import qupath.lib.scripting.QPEx | |
// Define the size of the region to create | |
double sizeMicrons = 200.0 | |
// Get main data structures | |
def imageData = QPEx.getCurrentImageData() | |
def server = imageData.getServer() | |
// Convert size in microns to pixels - QuPath ROIs are defined in pixel units of the full-resolution image | |
int sizePixels = Math.round(sizeMicrons / server.getAveragedPixelSizeMicrons()) | |
// Get the current viewer & the location of the pixel currently in the center | |
def viewer = QPEx.getCurrentViewer() | |
double cx = viewer.getCenterPixelX() | |
double cy = viewer.getCenterPixelY() | |
// Create a new Rectangle ROI | |
def roi = new RectangleROI(cx-sizePixels/2, cy-sizePixels/2, sizePixels, sizePixels) | |
// Create & new annotation & add it to the object hierarchy | |
def annotation = new PathAnnotationObject(roi, PathClassFactory.getPathClass("Region")) | |
imageData.getHierarchy().addPathObject(annotation, false) |
//0.1.2 and 0.2.0 | |
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) | |
addObject(annotation) |
//For those times when the automatic TMA dearrayer is having trouble picking up cores (too light/sparse) | |
// from https://github.com/qupath/qupath/issues/77 | |
//0.1.2, metadata would need to be changed for 0.2.0 | |
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) | |
getCurrentHierarchy().setTMAGrid(tmaGrid) |
//Set TMA cores where the tissue area is below a threshold to Missing so as to avoid inclusion in future calculations. | |
//0.1.2, but should work with 0.2.0 if the metadata section at the top is adjusted. | |
MINIMUM_AREA_um2 = 30000 | |
def imageData = getCurrentImageData() | |
def server = imageData.getServer() | |
def pixelSize = server.getPixelHeightMicrons() | |
getTMACoreList().each{ | |
it.setMissing(false) | |
} | |
selectTMACores(); | |
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}'); | |
getTMACoreList().each{ | |
it.setMissing(true) | |
list = it.getChildObjects() | |
println(list) | |
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 ){ | |
it.setMissing(false) | |
} else {removeObjects(list, true)} | |
} | |
} | |
fireHierarchyUpdate() | |
println("done") |
/* 0.2.3, https://forum.image.sc/t/tma-dearrayer-problem/48234/4 | |
* Run QuPath's TMA dearrayer at a different resolution. | |
* This can give a bit more control over the output if the original dearraying fails. | |
* | |
* @author Pete Bankhead | |
*/ | |
// Change this to adjust the resolution at which cores are detected | |
// Lower values mean the dearraying is done on a larger image (and will be slower) | |
double requestedPixelSize = 10 | |
// Adjust these for other detection parameters | |
double coreDiameter = 1200 | |
double roiScaleFactor = 1.05 | |
def horizontalLabels = 'A-J' | |
def verticalLabels = '1-16' | |
boolean horizontalLabelFirst = 100 | |
double densityThreshold = 0.05 | |
boolean isFluorescence = false | |
// Actually do the dearraying | |
import qupath.imagej.detect.dearray.TMADearrayerPluginIJ.Dearrayer | |
def dearrayer = new Dearrayer() | |
def server = getCurrentServer() | |
double downsample = requestedPixelSize / server.getPixelCalibration().getAveragedPixelSize() | |
double fullCoreDiameterPx = coreDiameter / server.getPixelCalibration().getAveragedPixelSize() | |
def request = RegionRequest.createInstance(server, downsample) | |
dearrayer.ip = IJTools.convertToImagePlus(server, request).getImage().getProcessor() | |
def tmaGrid = dearrayer.doDearraying( | |
fullCoreDiameterPx, | |
downsample, | |
densityThreshold, | |
roiScaleFactor, | |
isFluorescence, | |
PathObjectTools.parseTMALabelString(horizontalLabels), | |
PathObjectTools.parseTMALabelString(verticalLabels), | |
horizontalLabelFirst | |
) | |
getCurrentHierarchy().setTMAGrid(tmaGrid) | |
println 'Done! ' + println tmaGrid |
/** | |
* Script to help with annotating tumor regions, chopping increasing chunks into the tumor. | |
* SEE THREAD HERE FOR DESCRIPTION ON USE: | |
* 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: https://petebankhead.github.io/qupath/scripts/2018/08/08/three-regions.html | |
* | |
* @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!' | |
return | |
} | |
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!' | |
return | |
} | |
// We need one selected annotation as a starting point; if we have other annotations, they will constrain the output | |
annotations.remove(selected) | |
// 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' | |
return | |
} | |
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))} | |
areaOuter.subtract(areaTumor) | |
areaOuter.intersect(areaTissue) | |
def roiOuter = PathROIToolsAwt.getShapeROI(areaOuter, roiOriginal.getC(), roiOriginal.getZ(), roiOriginal.getT()) | |
def annotationOuter = new PathAnnotationObject(roiOuter) | |
annotationOuter.setName("Outer margin") | |
annotationOuter.setColorRGB(colorOuterMargin) | |
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))} | |
areaExpansion.intersect(areaTumor) | |
areaExpansion.intersect(areaTissue) | |
if(i>=1){ | |
for (k=1; k<=i;k++){ | |
areaExpansion.subtract(PathROIToolsAwt.getArea(innerAnnotations[innerAnnotations.size()-k].getROI())) | |
} | |
} | |
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.getSelectionModel().clearSelection() | |
//hierarchy.removeObject(selected, true) | |
def annotationsToAdd = innerAnnotations; | |
annotationsToAdd.each {it.setLocked(lockAnnotations)} | |
if (tissueAnnotation == null) { | |
hierarchy.addPathObjects(annotationsToAdd, false) | |
} else { | |
tissueAnnotation.addPathObjects(annotationsToAdd) | |
hierarchy.fireHierarchyChangedEvent(this, tissueAnnotation) | |
if (lockAnnotations) | |
tissueAnnotation.setLocked(true) | |
} | |
println("Done! Wheeeee!") |
//0.1.2 only, create detections from points. | |
//See 0.2.3 below | |
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) | |
points.each{ | |
//Cycle through all points within a points object | |
it.getROI().getPointList().each{ | |
//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) | |
addObject(aCell) | |
} | |
} | |
//remove points if desired. | |
removeObjects(points, false) | |
//0.2.3 version | |
import qupath.lib.objects.PathDetectionObject | |
points = getAnnotationObjects().findAll{it.getROI().isPoint() } | |
//Cycle through each points object (which is a collection of points) | |
points.each{ | |
plane = it.getROI().getImagePlane() | |
pathClass = it.getPathClass() | |
//Cycle through all points within a points object | |
it.getROI().getAllPoints().each{ | |
//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 = ROIs.createEllipseROI(x-size/2,y-size/2,size,size, plane) | |
//pathClass = getPathClass("FakeCell") | |
def aCell = new PathDetectionObject(roi, pathClass) | |
addObject(aCell) | |
} | |
} | |
resolveHierarchy() | |
//remove points if desired. | |
removeObjects(points, false) |
//0.1.2 | |
//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){ | |
0.upto(server.nZSlices()-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, 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){ | |
0.upto(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); | |
} | |
} | |
} |
//See below for 0.2.3 version | |
//0.1.2 | |
//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() | |
clearAllObjects() | |
if (server.nZSlices() >0){ | |
0.upto(server.nZSlices()-1){ | |
frame = new PathAnnotationObject(new RectangleROI(0,0,xdist,ydist,-1,it,0)); | |
addObject(frame); | |
} | |
} | |
if (server.nTimepoints() >0){ | |
0.upto(server.nTimepoints()-1){ | |
frame = new PathAnnotationObject(new RectangleROI(0,0,xdist,ydist,-1,0,it)); | |
addObject(frame); | |
} | |
} | |
//0.2.3 version | |
//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() | |
clearAllObjects() | |
if (server.nZSlices() >0){ | |
0.upto(server.nZSlices()-1){ | |
frame = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0,0,xdist,ydist,ImagePlane.getPlane(it,0))); | |
addObject(frame); | |
} | |
} | |
if (server.nTimepoints() >0){ | |
0.upto(server.nTimepoints()-1){ | |
frame = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0,0,xdist,ydist,ImagePlane.getPlane(0,it))); | |
addObject(frame); | |
} | |
} | |
selectAnnotations() | |
//cell detection here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment