Labelled annotation import

Dear all,
I am importing xml annotations from Aperio. My annotations are labelled. For example, Layer 1, layer 2 etc. Is there a script that imports these labels onto QuPath?
Many thanks.
Sincerely,
Sabrina

I’m not familiar enough with the Aperio XML format to know where the label is stored, and there is no built-in support in QuPath for such annotations. I’d suggest asking whoever wrote the original import script.

Hi Pete,
Thanks. You mean, to ask Melvin?
I think I saw a script to import labels somewhere. Someone was asking as he had tumor/vessel labels on his annotation. But i cant find it anymore. Also not sure if it was for xmls.
Sincerely,
Sabrina

little edit:
Found the link: https://gist.github.com/DanaCase/9cfc23912fee48e437af03f97763d78e#gistcomment-3088821
However, it wasnt discussed further.

Yes, that’s the link I meant where the script was posted by the author. The label names would need to be parsed as part of that script. @melvingelbard may be able to help, but would need examples of the XML files since it isn’t really clear where exactly the label information is stored – XML files in general can be extremely variable.

(I mention briefly why QuPath doesn’t support XML annotations on this page).

2 Likes

Hi Pete,
Thanks for the suggestion.

Hi Melvin,
Thanks for your super nice script last time. Here is the XML. I imported it with your script. But would be also great if I could add the annotation labels into the script. Would save lot of manual work.
Sincerely,
Sabrina
2020-154-13_2311_tiefer_CEP_HE-agz.xml (117.5 KB)

1 Like

Hi @Sabrina_sarker,

This is possible. However I am not sure which field in your XML refers to the ‘annotation label’.
In your file example, I can see:
<Annotation Id="1" Name="" ReadOnly="0" NameReadOnly="0" LineColorReadOnly="0" Incremental="0" Type="4" LineColor="0" Visible="1" Selected="0" MarkupImagePath="" MacroName="">

All the Name fields are empty. So what would you like to be imported as the PathClass of each annotation?

2 Likes

Hi @Sabrina_sarker

I have code here to import ImageScope annotations here: https://github.com/pjl54/qupath_scripts/blob/d07056fc975c56c138e15bce964f145b5e1c08f3/xml_to_QuPath.groovy that might provide a starting point for what you’re trying to do.

But as mentioned, I don’t see anything in the Name field of the xml file you uploaded. I imported your annotations by adding the “Test tumor” class to my qupath with the RGB color [0,0,0], adding “Test tumor” to the name field of the annotation with LineColor=0: <Annotation Id="1" Name="Test tumor" ReadOnly="0" NameReadOnly="0" LineColorReadOnly="0" Incremental="0" Type="4" LineColor="0" Visible="1" Selected="0" MarkupImagePath="" MacroName="">

I hope this helps.

1 Like

Hi Patrick,
Unfortunately, I uploaded an xml without label. here is attached one labelled. By label, i mean the region id. in this case in the xml, it is ‘‘normaltissue’’. Now, how to apply your script on it?
Sincerely,
Sabrina

2019-271-Noske-ki67-image-analysis-015 (2).xml (22.8 KB)

Your label is saved in the “Text” field of the region, rather than the “Name” field of the annotation as my script is currently configured to do. So you need to add this line:

name = Region.item(0).getAttribute('Text')

After this line:

NodeList Region = Regions.item(0).getElementsByTagName('Region');

Also, my code has some checks to ensure that you don’t import an annotation with a class name matching an existing class, but a different color, since it is unclear what to do in such a situation. Additionally, the RGB color [255,0,0] is interpreted as null class. If you’re not worried about either of these constraints, you should remove everything between if(linecolor == 16711680) { and its corresponding closing bracket and replace it with:

pathclass = PathClassFactory.getPathClass(name,ColorTools.makeRGB(gbr[0],gbr[1],gbr[2]))

That should work.

2 Likes

Hi Patrick,
It doesnt work unfortunately.
For batch import of annotation, i use the following script kindly given to me by Melvin. Is it possible to modify it and extract either the ‘region’ name or the ‘annotation’ name from the xml attached?
Sincerely,
Sabrina

2019-244 noske Sarker Endopredict Ki67.xml (56.8 KB)

import qupath.lib.scripting.QP
import qupath.lib.geom.Point2
import qupath.lib.roi.PolygonROI
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.images.servers.ImageServer

//Aperio Image Scope displays images in a different orientation
def rotated = false

def server = QP.getCurrentImageData().getServer()
def h = server.getHeight()
def w = server.getWidth()

// need to add annotations to hierarchy so qupath sees them
def hierarchy = QP.getCurrentHierarchy()

//Prompt user for exported aperio image scope annotation file
def path = server.getURIs().getAt(0).getPath(); // HERE
path = path.substring(0, path.lastIndexOf(".")) + “.xml” // HERE
def file = new File(path)
def text = file.getText()

def list = new XmlSlurper().parseText(text)

list.Annotation.each {

it.Regions.Region.each { region ->

    def tmp_points_list = []

    region.Vertices.Vertex.each{ vertex ->

        if (rotated) {
            X = vertex.@Y.toDouble()
            Y = h - vertex.@X.toDouble()
        }
        else {
            X = vertex.@X.toDouble()
            Y = vertex.@Y.toDouble()
        }
        tmp_points_list.add(new Point2(X, Y))
    }

    def roi = new PolygonROI(tmp_points_list)

    def annotation = new PathAnnotationObject(roi)

    hierarchy.addPathObject(annotation, false)
}

}

Hi @Sabrina_sarker,

You need to get the name from your XML in order to get a corresponding PathClass in QuPath (see script below).
Also, I just modified a bit the script you provided, which comes from this repo I believe, so I’m not the author of it :slight_smile:

Would the following work? I used the ‘Name’ field in your XML to set the class of the new annotations in QuPath, I think that’s what you were looking for?

import qupath.lib.scripting.QP
import qupath.lib.geom.Point2
import qupath.lib.roi.PolygonROI
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.images.servers.ImageServer

//Aperio Image Scope displays images in a different orientation
def rotated = false

def server = QP.getCurrentImageData().getServer()
def h = server.getHeight()
def w = server.getWidth()

// need to add annotations to hierarchy so qupath sees them
def hierarchy = QP.getCurrentHierarchy()

//Prompt user for exported aperio image scope annotation file
def path = server.getURIs().getAt(0).getPath();           // HERE
path = path.substring(0, path.lastIndexOf(".")) + ".xml"  // HERE
def file = new File(path)
def text = file.getText()

def list = new XmlSlurper().parseText(text)

list.Annotation.each {

    // Get the class from your XML
    def annotationClass = getPathClass(it.@Name.toString())

    it.Regions.Region.each { region ->

        def tmp_points_list = []

        region.Vertices.Vertex.each{ vertex ->

            if (rotated) {
                X = vertex.@Y.toDouble()
                Y = h - vertex.@X.toDouble()
            }
            else {
                X = vertex.@X.toDouble()
                Y = vertex.@Y.toDouble()
            }
            tmp_points_list.add(new Point2(X, Y))
        }

        def roi = new PolygonROI(tmp_points_list)

        def annotation = new PathAnnotationObject(roi)


        // Set the class here below
        annotation.setPathClass(annotationClass)

        hierarchy.addPathObject(annotation, false)
    }
}

// Update hierarchy to see changes in QuPath's hierarchy
fireHierarchyUpdate()

print "Done!"
1 Like

It works!!
Many many thanks!!
Sincerely,
Sabrina

2 Likes