Saving Large Annotation Masks

308 views
Skip to first unread message

emir konuk

unread,
Mar 12, 2018, 11:34:45 AM3/12/18
to QuPath users
Hello,

I have modified Pete's script given in this question to create annotation masks for user generated labels and saving these masks as different images for each label. Here is my version:

import static qupath.lib.roi.PathROIToolsAwt.getShape;
import java.awt.image.BufferedImage
import java.awt.Color
import javax.imageio.ImageIO
import qupath.lib.projects.ProjectIO
import qupath.lib.scripting.QPEx

// select the folder to save the annotations
def directory = getQuPath().getDialogHelper().promptForDirectory(null)

// List of annotations
def annotationNames = []

for (annotation in getAnnotationObjects()) {
    //print(annotation.getPathClass())
    annotationNames.push(annotation.getPathClass())
    annotationNames.unique()
}

// Loop over each annotation type
for (def annotationName in annotationNames)
{
    // Get the annotation objects of this type
    def annotationType = getPathClass( annotationName.toString())
    def annotations = getAnnotationObjects()
    def annotationsToSave = annotations.findAll({annotation -> annotation.getPathClass() == annotationType})
    
    def shapes = annotationsToSave.collect {getShape(it.getROI())}
        
    // Create a grayscale image, here it's 10% of the full image size
    double downsample = 10.0
    def server = getCurrentImageData().getServer()
    int w = (server.getWidth() / downsample) as int
    int h = (server.getHeight() / downsample) as int
    def img = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY)
   
    // Paint the shapes (this is just 'standard' Java - you might want to modify)
    def g2d = img.createGraphics()
    g2d.scale(1.0/downsample, 1.0/downsample)
    g2d.setColor(Color.WHITE)
    for (shape in shapes)
        g2d.fill(shape)
    g2d.dispose()
    
    // Save the result
    def imageName = server.getShortServerName()
    String name = String.format("%s_%s.png", imageName, annotationName)
    def outputFile = new File(directory, name)
    ImageIO.write(img, 'PNG', outputFile)


}

However, if I try to create a mask having the same size with my original image (91287 * 206613) I get an error since the BufferedImage class can't handle huge images. Is there a way around this?

Best Regards,
Emir Konuk

Pete

unread,
Mar 12, 2018, 11:45:55 AM3/12/18
to QuPath users
Not directly, in that QuPath can't write whole slide images itself.

But there might be alternatives.  For example, you could export vertices for the annotations, or alternatively individual tiles along with the coordinates of the regions that were exported.

I'm not sure which approach would make most sense in your case.  It really depends upon what you want to do with the information afterwards (e.g. import it elsewhere using MATLAB or Python, visualize it as an overlay in other software).

I was already thinking of including a sample script for exporting over on the blog at https://petebankhead.github.io - if you describe what you are looking for, I could think some more about trying to come up with an example that also fits with what you need.

The use case I can think of is to use the exported annotations as training data elsewhere, in which case I would expect that you would either need masks for the full image and low resolution, or individual regions at high resolution - but (hopefully!) not masks for the full image at high resolution.  If you *do* need the whole image at full resolution, exporting vertices and then creating the whole slide image elsewhere is probably the 'easiest' way to do it, but I suspect not very easy.

emir konuk

unread,
Mar 12, 2018, 12:42:04 PM3/12/18
to QuPath users
Hello Pete,

Thanks for answering.

Sadly I need the masks for the full image at high resolution. If it is possible to export the vertices, I might be able to create full resolution masks using those points and OpenCV/SimpleITK. Would it be possible to create a list of the 2D coordinates of all the pixels on the boundaries of the annotations? For example as a csv file like this:

113, 65; 114, 66; 115, 67; ... #first annotation
156, 369; 156, 370; 157, 370; 157, 369; ... #second annotation
...

Best Regards,
Emir Konuk

Pete

unread,
Mar 13, 2018, 8:59:32 AM3/13/18
to qupath...@googlegroups.com
I remember writing a bit somewhere about how to export coordinates, although I'm not entirely sure where the best explanation currently is... here's a bit anyway:


Basically: if you have simple shapes and polygons then it is a lot easier.  If you have more complex areas (e.g. with disconnected regions, holes) then things get trickier.  We can figure out a way if necessary.

I've also just written the blog post about exporting binary masks, and given example scripts:

In this case, the full resolution can be used (by setting the 'dowsample' to 1.0) - but it does assume that every individual annotation will have a bounding box of a manageable size.  Depending upon how your annotations are distributed across the image, this could give a very efficient way of encoding the same information as you'd get from exporting a whole slide binary image... or maybe not.

The key thing that differs from your original script is that the bounding box of each annotation is used, rather than attempting to export everything in one go.  This is achieved by applying a translation to the Graphics2D object (as well as the scale, which is already included in your code above), based on the top left corner of the bounding box.  This hints at the other way you could do the export: rather than using the bounding boxes per annotation, create your own non-overlapping bounding boxes for different chunks of the image and write these out - with the plan to concatenate them elsewhere if needed.

You can paint all your annotations to each chunk exactly as above - the ones that don't actually fall inside the chunk just won't be visible.  Or you can do a bounds test on the shape object to see if it is worth painting.  This will probably give better performance.

The information is all there, so one way or another it is possible to get it out...

Pete

unread,
Mar 13, 2018, 11:53:04 AM3/13/18
to QuPath users
I've just updated the blog post link.  Since you also mentioned using OpenCV, this might also be of some interest:

emir konuk

unread,
Mar 13, 2018, 12:32:06 PM3/13/18
to QuPath users
Thank you very much. Exporting binary masks and also providing the bounding box coordinates in the filename seems like a much better solution than using the contour points. As you have explained, the differences in calculating the contours between ImageJ and OpenCV would have made my job much harder anyway.

I believe no annotation would be unreasonably large, so exporting the masks separately might work. I will consult my colleagues again but if we definitely need a single mask containing all of the annotations, it may be necessary to create non-overlapping bounding boxes. If so, I will modify your script and than merge them, probably using the paste function from ITK.

Pete

unread,
Mar 14, 2018, 7:13:25 PM3/14/18
to QuPath users
I've added another post now showing how to do the export directly to create labelled images, with each labelled derived from the annotation classification - and optionally breaking the image into non-overlapping tiles:

Reply all
Reply to author
Forward
0 new messages