Labelling negative space in annotations

Hi, I’m a Consultant Pathologist and have been using QuPath for annotations for machine learning. It’s given us a massive head start and I’m really grateful for all the work that you have put (and continue to put) into creating it.

So that we can use annotations for training we are exporting them into a JSON as co-ordinates. When there is an annotation that sits as an island within a different annotation we run into trouble. We can create a negative space within the larger annotation using the alt key and can export the peripheral co-ordinates first followed by the internal co-ordinates of the negative space. However, if we try and reimport from the JSON back into QuPath then the co-ordinates from the negative space and the periphery get muddled up with lines joining between the two. This creates a shape that is quite different to what was intended.

Is it possible to either label the co-ordinates as indicating a negative space or to give a command that allows for the peripheral ring co-ordinates to be drawn separately to the negative space?

Hope that makes sense and hope you can help us.

I’m not clear on the process; why are you trying to import the JSON back into QuPath? Normally when moving information into another program, JSON is a good choice, but for importing back into QuPath (between versions, new project, etc) I would usually use a script and java streaming to create serialized objects (to keep classes and everything constant).

https://gist.github.com/Svidro/5e4c29630e8d2ef36988184987d1028f#file-import-export-annotations-groovy

The scripts can be expanded slightly to save to a folder by file name, so that they can be run for a project.

If you are doing something to the annotations such that you need to modify the JSON coordinates before adding them into QuPath, hopefully Pete will have something more useful.

Hi @Clare_Craig, glad QuPath is useful to you!

I think the issue depends entirely on how you export and import JSON, and you don’t mention how you are doing this or what version of QuPath you’re using - so I presume you have created some custom scripts for this?

I have recently written a bit about how JSON serialization can be used with QuPath here:

In v0.2.0-m8 this following works for me to convert annotations to GeoJSON - which is a well-defined, standard representation that supports negative regions:

// Create  JSON
def annotations = getAnnotationObjects()
def gson = GsonTools.getInstance(true)
def json = gson.toJson(annotations)

Converting back is a bit more awkward, but a full script that does this (to effectively duplicate the annotations) is below:

// Create  JSON string
def annotations = getAnnotationObjects()
def gson = GsonTools.getInstance(true)
def json = gson.toJson(annotations)
            
// Print the string
println json

// Read the annotations
def type = new com.google.gson.reflect.TypeToken<List<qupath.lib.objects.PathObject>>() {}.getType()
def deserializedAnnotations = gson.fromJson(json, type)

// Set the annotations to have a different name (so we can identify them) & add to the current image
deserializedAnnotations.eachWithIndex {annotation, i -> annotation.setName('New annotation ' + (i+1))}
addObjects(deserializedAnnotations)    

This syntax might change a bit before v0.2.0 is finalized - specifically the line with the TypeToken is very awkward and I’ll probably try to have a more concise way of doing that.
In the case you just have a single annotation, you may use gson.fromJson(json, qupath.lib.objects.PathObject) and avoid the TypeToken bit.

I would recommend using GeoJSON rather than any other JSON representation, but note:

  • I’ve only checked this with QuPath v0.2.0-m8 - I expect it may not fully work in any earlier version
  • GeoJSON only supports ROIs based on line segments; in practice, that means ellipses in QuPath become polygons.
1 Like

Thanks so much for this advice. I think this is going to crack it for us. Thanks again.