Importing binary masks

I have generated binary masks and am trying to import it to qupath. I have the mask in two formats: .png patches(basically the wholeslide split up) and .tif wholeslide.
I was looking at Pete’s code (https://petebankhead.github.io/qupath/scripting/2018/03/13/script-export-import-binary-masks.html) and I couldn’t figure out how to fix this error:

ERROR: MissingMethodException at line 63: No signature of method: qupath.lib.objects.hierarchy.PathObjectHierarchy.addPathObjects() is applicable for argument types: (ArrayList, Boolean) values: [, false]
Possible solutions: addPathObjects(java.util.Collection), addPathObject(qupath.lib.objects.PathObject)

The error comes up when I add annotations to image with

hierarchy.addPathObjects(annotations, false)

Any help on how to fix this would be appreciated. On a separate note, is there an easier way to do this by using my wholeslide mask? Maybe I can directly import it from ImageJ? I have attached the full code just in case, thank you in advance!!

/**
 * Script to import binary masks & create annotations, adding them to the current object hierarchy.
 *
 * It is assumed that each mask is stored in a PNG file in a project subdirectory called 'masks'.
 * Each file name should be of the form:
 *   [Short original image name]_[Classification name]_([downsample],[x],[y],[width],[height])-mask.png
 *
 * Note: It's assumed that the classification is a simple name without underscores, i.e. not a 'derived' classification
 * (so 'Tumor' is ok, but 'Tumor: Positive' is not)
 *
 * The x, y, width & height values should be in terms of coordinates for the full-resolution image.
 *
 * By default, the image name stored in the mask filename has to match that of the current image - but this check can be turned off.
 *
 * @author Pete Bankhead
 */


import ij.measure.Calibration
import ij.plugin.filter.ThresholdToSelection
import ij.process.ByteProcessor
import ij.process.ImageProcessor
import qupath.imagej.tools.IJTools
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.objects.classes.PathClassFactory
import qupath.lib.gui.scripting.QPEx

import javax.imageio.ImageIO

// Get the main QuPath data structures
def imageData = QPEx.getCurrentImageData()
def hierarchy = imageData.getHierarchy()
def server = imageData.getServer()

// Only parse files that contain the specified text; set to '' if all files should be included
// (This is used to avoid adding masks intended for a different image)


// Get a list of image files, stopping early if none can be found
def pathOutput = QPEx.buildFilePath(QPEx.PROJECT_BASE_DIR, 'output')
def dirOutput = new File(pathOutput)
if (!dirOutput.isDirectory()) {
    print dirOutput + ' is not a valid directory!'
    return
}
def files = dirOutput.listFiles({f -> f.getName().endsWith('.png') } as FileFilter) as List
if (files.isEmpty()) {
    print 'No mask files found in ' + dirOutput
    return
}

// Create annotations for all the files
def annotations = []
files.each {
    try {
        annotations << parseAnnotation(it)
    } catch (Exception e) {
        print 'Unable to parse annotation from ' + it.getName() + ': ' + e.getLocalizedMessage()
    }
}

// Add annotations to image
hierarchy.addPathObjects(annotations, false)

/**
 * Create a new annotation from a binary image, parsing the classification & region from the file name.
 *
 * Note: this code doesn't bother with error checking or handling potential issues with formatting/blank images.
 * If something is not quite right, it is quite likely to throw an exception.
 *
 * @param file File containing the PNG image mask.  The image name must be formatted as above.
 * @return The PathAnnotationObject created based on the mask & file name contents.
 */
def parseAnnotation(File file) {
    // Read the image
    def img = ImageIO.read(file)

    // Split the file name into parts: [Image name, Classification, Region]
    def parts = file.getName().split(',')

    // Discard all but the last 2 parts - it's possible that the original name contained underscores,
    // so better to work from the end of the list and not the start
    def classificationString = 'Tumor'

    // Create a classification, if necessary
    def pathClass = null
    if (classificationString != 'None')
        pathClass = PathClassFactory.getPathClass(classificationString)

    // Parse the x, y coordinates of the region - width & height not really needed
    // (but could potentially be used to estimate the downsample value, if we didn't already have it)


    int x = parts[1] as int
    int y = parts[2] as int

    // To create the ROI, travel into ImageJ
    def bp = new ByteProcessor(img)
    bp.setThreshold(127.5, Double.MAX_VALUE, ImageProcessor.NO_LUT_UPDATE)
    def roiIJ = new ThresholdToSelection().convert(bp)

    // Convert ImageJ ROI to a QuPath ROI
    // This assumes we have a single 2D image (no z-stack, time series)
    // Currently, we need to create an ImageJ Calibration object to store the origin
    // (this might be simplified in a later version)
    def cal = new Calibration()
    cal.xOrigin = -x
    cal.yOrigin = -y
    def roi = IJTools.convertToIJRoi(roiIJ, cal, 0, -1, 0, 0)

    // Create & return the object
    return new PathAnnotationObject(roi, pathClass)
 }
1 Like

Have you tried removing the , false as suggested? I think the input for that function changed at some point.

Thanks for the suggestion. I tried it and it’s still giving me an error:

INFO: Unable to parse annotation from 2B_(3.04,15200,34960,1520,1520)-slides_class.png: No signature of method: static qupath.imagej.tools.IJTools.convertToIJRoi() is applicable for argument types: (ij.gui.ShapeRoi, ij.measure.Calibration, Integer, Integer, Integer…) values: [Roi[Composite, x=0, y=158, width=225, height=342], w=1.0, h=1.0, d=1.0, unit=pixel, f=20, nc=null, table=null, vunit=Gray Value, bd=0, …]
Possible solutions: convertToIJRoi(qupath.lib.roi.interfaces.ROI, ij.measure.Calibration, double)
INFO: Result: false

Is it possible that this is a parsing issue from reading the values from the filename?

The script you linked was for 0.1.2, are you sure you are using QuPath 0.1.2?

And if you are trying to adapt the code for a different version of QuPath are your images named correctly? The input requirements listed in the comment at the top would still be necessary unless you are tinkering with the file name inputs as well.

It mostly looks like the input needs to be changed once again, though.

Much of what you seem to be doing has already been covered here:

And here

Also, here is probably the most recent version, written for 0.2.0M6, though it may need more modification.

1 Like

Oh okay, I should’ve checked the Qupath version. However on 0.1.2 it for some reason has trouble opening my tif file (810MB) while it opens fine on 0.2.1. I’m getting out of memory error, is there a workaround to this issue? I’m finding some discussion on converting big images to pyramidal, would that be the right approach?

Awesome, thanks so much! I’ll see if I can make it work!

1 Like

In 0.2.0 non-pyramidal TIFFs are automatically pyramidalized. So maybe.

I used Pete’s code and it ran fine, but I don’t see any annotations on the image. All the annotations’ area is set to 0 or NaN, what would be the reason?

Note that the last script/link was an export, and intended as an example of the kind of coding changes that are necessary. This would be more of an example of someone modifying a script for import, rather recently.


There aren’t many details about the overall script, though.

Otherwise, I’m not sure which script you are using, which set of input files you are using, or what the exact code looks like after you have modified it.