Skip to content

Instantly share code, notes, and snippets.

@DanaCase
Last active May 6, 2023 09:05
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DanaCase/9cfc23912fee48e437af03f97763d78e to your computer and use it in GitHub Desktop.
Save DanaCase/9cfc23912fee48e437af03f97763d78e to your computer and use it in GitHub Desktop.
Convert Exported Aperio Image Scope Annotations to QuPath Annotations
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 = true
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 file = getQuPath().getDialogHelper().promptForFile('xml', null, 'aperio xml file', null)
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)
}
}
@DanaCase
Copy link
Author

Before running this script for the first time, you need to drag the groovy-xml.jar (found in the groovy-sdk lib folder) onto the QuPath window. This copies the jar into QuPath's jar directory.

@sankrish93
Copy link

Hi ! thank you so much for this code ! however when I run this code, the annotations seem to be appearing outside the image(specifically above the image instead of on it) In your opinion, do you think this might be due to an internal error or is there some fix to be made to the code before running it for the first time?

@DanaCase
Copy link
Author

It seems like somehow there is a mismatch between your aperio annotations x, y coords and the QuPath x, y coords? I would guess either scaling or rotation? It's kinda hard to tell without seeing the image.

Do you have two 'images' in the QuPath folder? Sometimes I have to open the actual image instead of the 'maco' image depending on the file type.

It may also be that QuPath is rotating the image? You can set the rotate param near the top of the script to false to try that out.

The last thing that i can think of is that there is some scaling factor? You can try to figure out what it is and multiply the x and y by it?

Let me know what you come up with :)

@gagarin37
Copy link

Dear DanaCase,

thanks a lot for this srcipt. This imports the annotations very nice.
However I have the same problem as sankrish93. They are all above the image, and rotation seems not to be the case.
I also attach the image.
I would very appreciate if you can give me some ideas how to improve the situation..
screenshot from 2018-04-19 21-57-10

@gagarin37
Copy link

Dear DanaCase,

the rotation seems to be the problem. It seems to be so, that several images go with rotated = "false" and several with "true" and than the correspondence is perfect.

Thank's a lot once more time.

All best.

@JohnKPan
Copy link

JohnKPan commented Jan 14, 2019

I modified this script to import NDPA files from NDPVIEW.

It however involves some really hacky stuff because the NDPA coordinate system is relative to physical slide center, and not top left of scanned image. It works though. Hopefully someone will figure out how to get the necessary information from OpenSlide to make it simpler.

You will need the Groovy-xml.jar as well.

https://groups.google.com/forum/#!searchin/qupath-users/import$20annotation%7Csort:date/qupath-users/xhCx_nhbWQQ/0kW38lEXCAAJ

@SteveBoothHLI
Copy link

SteveBoothHLI commented Nov 20, 2019

Hi there, this is working perfectly for me now! However, in Aperio we have given our annotations names for the classes ("Airway", "Vessel"), these are stored in the xml file under name attributes. Is there a way to automatically set the classes of the imported annotations to match those from the Aperio xml?

Also I noticed that the order of the annotations is different each time I run the import - is this expected?
Thanks,
Steve

@Sabrina1948
Copy link

Hi all,
I have lots of xmls in folder. i want to import them on corresponding images. A batch import i mean. Would please help me in this regard? the script can only import one xml at a time while i got 300+ xmls.
Many thanks in advance.
Sincerely,
Sabrina

@Svidro
Copy link

Svidro commented May 21, 2020

In case @SteveBoothHLI or others want the name import included, Sabrina1948 opened a post on image.sc that ended up resolving the issue.
https://forum.image.sc/t/labelled-annotation-import/37787/11

@narminGhaffari
Copy link

Dear DanaCase,

thanks a lot for this srcipt. This imports the annotations very nice.
However I have the same problem as sankrish93. They are all above the image, and rotation seems not to be the case.
I also attach the image.
I would very appreciate if you can give me some ideas how to improve the situation..
screenshot from 2018-04-19 21-57-10

Hey, did you find any solution to this problem?

@PerfidAlbion
Copy link

I can't launch this script. I've got the following errors :

With Qupath 0.4.3 : ERROR: It looks like you've tried to import a class 'XmlSlurper' that couldn't be found

With Qupath 0.2.3 : ERROR: MissingMethodException at line 20: No signature of method: qupath.lib.gui.QuPathGUI.getDialogHelper() is applicable for argument types: () values: []

Could you help

Thanks

@MichaelSNelson
Copy link

The latter error and the solution can be found on the image.sc forum.

I am not sure about the XmlSlurper though. It is likely due to reorganization of the functions used: https://stackoverflow.com/questions/55169114/unable-to-add-xmlslurper-to-eclipse-groovy-project
https://stackoverflow.com/questions/11245073/groovy-2-cant-find-jsonslurper-and-xmlslurper-anymore
Etc. I am not sure what the exact solution for QuPath will be.

@PerfidAlbion
Copy link

Thanks for the answer. I have managed to make it work :

Here is the script that worked for me :

"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 file = Dialogs.promptForFile('xml', null, 'aperio xml file', null)
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.addObject(annotation, false)
}

}"

I changed the following ::

replace getQuPath().getDialogHelper().promptForFile( with Dialogs.promptForFile(
replace hierarchy.addPathObject(annotation, false) with hierarchy.addObject(annotation, false)
Used version groovy-xml-3.0.17

Hope it helps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment