PMA.core is a tile server that supports the creation of annotations on top of whole slide images.
The annotations are stored in the PMA.core back-end database as WKT strings. In the past, we’ve written about the different kinds of WKT strings that can be used to encode diffirent shapes of annotations, as well as what format is best suited to scale for greater volumes of annotations.
In this article we show the different ways the PMA.python SDK allows for the creation of annotations programmatically.
Random WKT rectangles with Python
Since this article is about how to define annotations in PMA.core, we decided to keep things simple and work with simple rectangles.
A rectangle in WKT is defined by 5 (x, y) coordinates: 4 corners, with a fifth point that traces back to the original corner to close the POLYGON loop.
We start by generating four x and y coordinates x1, x2, y1, y2 that will define the boundaries of the rectangle. We then use these to define (x, y) tuples, and combine them in a WKT string:
If you want to create more complex annotations, you can of course. the geo variable can contain any WKT-compatible annotation. And if you really want to go all out, have a look at the Python Shapely library as well, which contains a wrapper around the WKT syntax.
Storing single annotations
Once we have the WKT string, the most direct way to save the annotation is to pass it along with the add_annotation() call, like this:
As people have started using our SDK more frequently, we’ve also been asked for additional features. Just so we wouldn’t have to keep adding parameters to the add_annotation() call, we now also offer the option to pass a dictionary along instead of a single WKT geometry string. For convenience, we offer a separate method that returns a blank dictionary with the appropriate keys filled out already (you determine the appropriate values):
As you can see we can provide much richer styling of our annotation, without changing the syntax of the add_annotation() call; we simply pass the dictionary along, rather than the WKT geometry-string (which becomes part of the dictionary itself).
After running these two snippets of code, we end up with two random rectangles; one in the top-left, and another one in the top-right quadrant of our slide.
One, many, lots!
In our previous article we talked about performance matters, and how the right formatting and grouping of your WKT strings matter just as much.
When you have a lot a annotations to pass along, it’s faster to pass them along in a single call as an array, rather than having to invoke the same method over and over again.
This is why in addition to add_annotation(), there’s a new call introduced as of PMA.python revision 171 that allows for an array to be passed along instead of a single annotation.
Let’s look at the trivial example of passing along an array with just one element:
If you want to do a gradual conversion of your code from one type of interface to the next, you can also just replace your add_annotation() with add_annotations() calls. The latter is smart enough to figure out whether the passed argument is a list or not; if it’s not a list, the code will automatically adapt. The following lines of code are therefore equivalent:
core.add_annotations(slideref, "class", "notes", ann, sessionID = session)
core.add_annotation(slideref, "class", "notes", ann, sessionID = session)
The full code to fill up the fourth quadrant on our sample slide becomes like this:
Now it’s your turn!
The sample code from this article is available as a Jupyter notebook from our website.