How to divide annotation (ROI) into equal dimension bins?

Hi everyone,

I’m trying to compute cell distribution across a rectangluar ROI in qupath. I was wondering about a method to divide the roi (qupath annotation) into 10 bins (equal dimension) in order to calculate frequency distribution of my cells.
Any idea on how to split the roi automatically on QuPath?

I found the equivalent macro in imageJ:
#@ int(label=“How many divisions (e.g., 2 means quarters)?”) n
N = 10;
w = getWidth;
h = getHeight;
id = getImageID;
for(i=0; i<N; i++){
selectImage(id);
makeRectangle(0, i*h/N, w, h/N);
name = “” + (i + 1);
run(“Duplicate…”, “title=”+name);
}

but I don’t know how to convert into QuPath language.

Thank you in advance.
Sara

Hi @Sara_Mancinelli ,

I’m not sure what you mean by bins in this case? It sounds like you want tiles created from your annotation.
Would selecting your annotation and then run Analyze > Tiles & superpixels > Create tiles work for you?

2 Likes

If @melvingelbard’s suggestion is ok for you in principle, then you can use the following script to devide your annotation into n x m ‘sub-annotations’.

Currently the script will delete the parent annotation. This can be changed.

// File: tileAnnotation.groovy

import qupath.lib.gui.QuPathGUI
import qupath.lib.gui.viewer.QuPathViewer
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.roi.interfaces.ROI
import qupath.lib.gui.scripting.QPEx


// Adjust THIS
int n = 5

QuPathViewer viewer = QuPathGUI.getInstance().getViewer()
def objSelected = viewer.getSelectedObject()

if (objSelected != null && objSelected instanceof PathAnnotationObject){
    ROI roi = ((PathAnnotationObject)objSelected).getROI()

    int w = roi.getBoundsWidth()
    int h = roi.getBoundsWidth()

    float tileSize = w/n
    if (h/n > w/n)
        tileSize = w/n

    // Command string cmd, e.g.
    // '{"tileSizePx": 5000.0,  "trimToROI": true,  "makeAnnotations": true,  "removeParentAnnotation": true}'
    String cmd = '{"tileSizePx": ' + tileSize + ',  "trimToROI": true,  "makeAnnotations": true,  "removeParentAnnotation": true}'

    QPEx.runPlugin('qupath.lib.algorithms.TilerPlugin', cmd)
}

println('Done!')

Before

and after running the script

As you can see, the scripts devides the longer axis of the annotation by n (the parameter to be adjusted in the script).

If this isn’t exactly what you want, it might at least give you an idea of how to proceed.

2 Likes

Thank you Melvin.
By bins I mean split the roi in 10 (for example) identical subrois. I attach you a picture of what I would need ( I did it manually in QuPath) and what I obtain by creating tiles as you suggested,.

Hi Peter, thank you!
I tried your script and I have two questions:

the value n is the number of tiles or a dimension in pixel? I tried with 10 and I obtained a huge number of tiles (see pic). Do you know how to fix the number of tile to 10 for example?


the second question is concerning the tile orientation. It seems to me that the tiles follow the orientation of the grid. It is possible to rotate them? I attach you a pic of what am I am wishing to obtaining to perform my analysis.

The code indicates that N is the number of tiles on a side - so 10 would be 10 tiles vertically and 10 tiles horizontally.

Also, if the tiles do not follow the rectangle, it will be almost impossible to follow your initial request.

The dimensions would then no longer be equal, nor the areas.

2 Likes

Thank you @Research_Associate. I see the point.
Do you think is there any possibility to divide the roi in this manner (see pic) automatycally in qupath?

It is probably possible, though slightly more difficult if the object is at an angle initially. Once you rotate something like that it is no longer a rectangle, but a polygon (programmatically, as pixels do not rotate well). This is also deviating significantly from the initial ImageJ macro.

Why you want to do this at all? It seems less biologically relevant than, for example, getting a smooth distribution of cell distances from the tissue border using the Spatial analysis options. It also introduces bias when the areas are manually selected vs selecting the entire area of interest. It looks like something better suited to annotation expansions within a tissue.

Something like this except updated for 0.2.3

If you are looking at density or frequency, the areas do not need to be the same.

If you need to bin them, you can always bin them later in software better designed for data analysis.
image

1 Like

@Sara_Mancinelli This may do what you need

3 Likes

I get a “points do not appear to form a rectangle” error when using tilted objects.


Might need to be a little more permissive. It works fine when I comment out the “return” statement.

Also setting eps to 1 worked, but might need an additional comment to let users know that it is a measurement of error.

2 Likes

How was your rotate rectangle generated? It doesn’t look to be entirely rectangular by eye… if you draw a rectangle and use the Rotate annotation command it should work (at least it did for the one I tried).

But yes, relaxing the eps might be enough.

This exactly. It probably has to do with the pixel size and degree of rotation.
image
It was close, though just over 1x10-3

I checked a few more times and it generally happens right around 45 degrees. Smaller rotations are fine and give a value of around 0. Test was on the CMU-1.svs

Thanks @Research_Associate , I’ve increased the value in the gist and added a note.

1 Like

Hi,

here is an alternative script

// File: tileAnnotation2.groovy

import qupath.lib.gui.QuPathGUI
import qupath.lib.gui.viewer.QuPathViewer
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.roi.interfaces.ROI
import qupath.lib.roi.RoiTools


// Adjust THIS (n : Number of Tiles)
int n = 5
def trimToROI = false

QuPathViewer viewer = QuPathGUI.getInstance().getViewer()
def objSelected = viewer.getSelectedObject()

if (objSelected != null && objSelected instanceof PathAnnotationObject){
    ROI roi = ((PathAnnotationObject)objSelected).getROI()

    int w = roi.getBoundsWidth()
    int h = roi.getBoundsHeight()

    int tW, tH
    if ( w > h){
        tW = Math.floor(w/n)
        tH = h
    }
    else{
        tW = w
        tH = Math.floor(h/n)
    }

    List<ROI> pathROIs = RoiTools.makeTiles(roi, tW, tH, trimToROI)

    List<PathObject> tiles = new ArrayList<>(pathROIs.size())

    Iterator<ROI> iter = pathROIs.iterator()
    int idx = 0
    while (iter.hasNext()) {
        try {
            PathObject tile = PathObjects.createAnnotationObject(iter.next())
            if (tile != null) {
                idx++
                tile.setName("Tile " + idx)
                tiles.add(tile)
            }
        } catch (InterruptedException e) {
            lastMessage = "Tile creation interrupted for " + objSelected
            return
        } catch (Exception e) {
            iter.remove()
        }
    }

    ((PathAnnotationObject)objSelected).addPathObjects(tiles);
    viewer.getImageData().getHierarchy().fireHierarchyChangedEvent(this, objSelected);
}

println('Done!')

The number of sub-annotations is defined in n.

Depending on the parameter trimToROI the result can look like this

1 Like

Thank you! This is also superuseful.

Hi @petebankhead,

this is exactly what I was looking for!
Thank you!

1 Like

Hi @phaub,

this alternative one is also superuseful for me in the case I have to split irregular rois.
Thank you a lot!

Sara