Skip to content

Scripting examples

Pete edited this page Jan 1, 2017 · 6 revisions

Built-in 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.

Additional examples

Several other sample scripts are included below.

Adding measurements to detections

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!")

Exporting detection centroids

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?")

Tiling a whole slide image

A common approach to handling whole slide images with 'general' image analysis software that can't handle large 2D images directory is to:

  1. Break the image into tiles of a fixed size, at a specified resolution
  2. Save the tiles as separate images
  3. Apply processing to each image tile as required
  4. 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()
            }
        }
    }
}

Prompt for files or directories

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)
Clone this wiki locally