Scripting examples
Some built-in examples of possibly-useful scripts are distributed with QuPath under Automate → Show sample scripts. These can then be opened up in a script editor and run or modified as required.
Several other sample scripts are included below.
Sometimes it can be useful to derive new measurements from existing ones.
This script calculates 'integrated density' values for all detected cells in an image, which are equal to the mean * area. In this case, a brightfield image with hematoxylin and DAB staining is assumed - although by changing the measurement name this could be adapted to other stainings.
// Loop through the detections (we assume cells...)
for (detection in getDetectionObjects()) {
// Request cell measurements & calculate integrated density
double cellMean = measurement(detection, "Cell: DAB OD mean")
double cellArea = measurement(detection, "Cell: Area")
double cellIntegratedDensity = cellMean * cellArea
// Only add measurement if it's not 'Not a Number' - this implies both mean & area were available
if (!Double.isNaN(cellIntegratedDensity)) {
// Add new measurement to the measurement list of the detection
detection.getMeasurementList().addMeasurement("Cell: DAB integrated density", cellIntegratedDensity)
// It's important for efficiency reasons to close the list
detection.getMeasurementList().closeList()
}
}
// Make sure to update the hierarchy
fireHierarchyUpdate()
print("Done!")
At the time of writing, QuPath does not make it easy to export cell locations for spatial analysis.
This may change... however, in the meantime the following script can be used:
// Export the centroids for all detections, along with their classifications
// Set this to true to use a nucleus ROI, if available
boolean useNucleusROI = true
// Start building a String with a header
sb = new StringBuilder("Class\ty\tx\n")
// Loop through detections
int n = 0
for (detection in getDetectionObjects()) {
def roi = detection.getROI()
// Use a Groovy metaClass trick to check if we can get a nucleus ROI... if we need to
// (could also use Java's instanceof qupath.lib.objects.PathCellObject)
if (useNucleusROI && detection.metaClass.respondsTo(detection, "getNucleusROI") && detection.getNucleusROI() != null)
roi = detection.getNucleusROI()
// ROI shouldn't be null... but still feel I should check...
if (roi == null)
continue
// Get class
def pathClass = detection.getPathClass()
def className = pathClass == null ? "" : pathClass.getName()
// Get centroid
double cx = roi.getCentroidX()
double cy = roi.getCentroidY()
// Append to String
sb.append(String.format("%s\t%.2f\t%.2f\n", className, cx, cy))
// Count
n++
}
// Don't print if you've many objects - might take too long...
if (n <= 5000)
println(sb)
else
println("Best not print " + n + " centroids - perhaps you should try writing them to a file instead?")
A common approach to handling whole slide images with 'general' image analysis software that can't handle large 2D images directory is to:
- Break the image into tiles of a fixed size, at a specified resolution
- Save the tiles as separate images
- Apply processing to each image tile as required
- Reassemble a result from the tiles
The following script shows one way in which the first of these steps can be accomplished by QuPath.
/**
* Script to split a whole slide image into tiles, saving each tile as a separate image.
*
* Two things are notable about this script:
* - The location from which each tile was obtained (in terms of pixel values in the full-resolution image)
* is encoded in the filename
* - ImageJ is used to write the output images
*
* The significance of using ImageJ to write TIFF images (rather than, say, ImageIO to write PNGs or JPEGs)
* is that this enables the storage of additional metadata, i.e. pixel sizes and coordinates.
*/
import ij.IJ
import ij.ImagePlus
import qupath.imagej.images.servers.ImagePlusServer
import qupath.imagej.images.servers.ImagePlusServerBuilder
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 = 1000 // Width of (final) output tile in pixels
int tileHeightPixels = tileWidthPixels // Width of (final) output tile in pixels
double downsample = 10 // 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 = null // BE SURE TO ADD AN OUTPUT DIRECTORY HERE!!!
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> serverOriginal = QP.getCurrentImageData().getServer()
// Get an ImagePlus server
ImagePlusServer server = ImagePlusServerBuilder.ensureImagePlusWholeSlideServer(serverOriginal)
// 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 = serverOriginal.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 {
// Read the image region
ImagePlus imp = server.readImagePlusRegion(request).getImage(false)
// Get a suitable file name
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)
// Create an output file
File file = new File(dirOutput, name)
// Save the image
IJ.save(imp, 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()
}
}
}
}
Starting with v0.1.2, file-related prompts within a script are easiest in QuPath by using an instance of the DialogHelper
class.
You can see all the available methods it offers here.
Here is an example of its use, to prompt the user to first select a directory, and then select a file inside that directory:
def directory = getQuPath().getDialogHelper().promptForDirectory(null)
print(directory)
def file = getQuPath().getDialogHelper().promptForFile(directory)
print(file)
These docs are for QuPath ≤ v0.1.2.
For more up-to-date information, see https://qupath.readthedocs.io
- Video tutorials
- First steps
- Viewing images
- Drawing regions
- Counting cells
- Projects
- Multiple images
- Preferences
- Getting help
- Object-oriented analysis
- Types of object
- Object measurements
- Object classifications
- Object hierarchies
- Working with objects
- Workflows
- From workflows to scripts
- Writing custom scripts
- Advanced scripting with IntelliJ
- Scripting examples