Skip to content

Conversation

Rylern
Copy link
Contributor

@Rylern Rylern commented Apr 23, 2025

Implement #1749.

This lacks proper testing, but I haven't be able to find another image than https://zenodo.org/records/14685515 (and even on this image I don't know what the expected annotations are).

@Rylern Rylern linked an issue Apr 23, 2025 that may be closed by this pull request
@petebankhead petebankhead marked this pull request as draft April 23, 2025 18:50
@petebankhead petebankhead self-requested a review April 23, 2025 18:51
@petebankhead
Copy link
Member

You can use Fiji to get some view of the OME-XML. I see the ROIs are Mask objects, and currently we only return these as rectangles.

There is some information at https://forum.image.sc/t/prepping-and-including-roi-masks/48750 but I'm struggling to understand how this information is supposed to be parsed.

Still, I hacked together this as an alternative to the current createMask function::

private static ROI convertMask(Mask mask) {
        logger.debug("Converting mask {} to QuPath rectangle ROI", mask);

        // I don't know why width and height are doubles...
        // since we don't have the scale info here, I assume square pixels & try to figure out
        // the required transform
        var binData = mask.getBinData();
        long nPixels = binData.getLength().getValue();
        int width = (int)Math.sqrt(Math.round(nPixels * (mask.getWidth() / mask.getHeight())));
        int height = (int)(nPixels / width);

        if (((long)width * height) != nPixels) {
            logger.warn("Couldn't figure out dimensions: {}x{} != {} pixels", width, height, nPixels);
            return null;
        }
        // Create an image we can pass to ContourTracing
        var array = binData.getBase64Binary();
        var simpleImage = SimpleImages.createFloatImage(width, height);
        for (int i = 0; i < nPixels; i++) {
            if (array[i] != 0) {
                simpleImage.setValue(i % width, i / width, 1.0f);
            }
        }

        // Create a request we can use to calibrate the ROI
        // We're assuming x and y are integers and that there is no other transform!
        var request = RegionRequest.createInstance("", 1.0,
                mask.getX().intValue(),
                mask.getY().intValue(),
                simpleImage.getWidth(),
                simpleImage.getHeight(),
                ImagePlane.getPlane(mask.getTheZ().getValue(), mask.getTheT().getValue()));

        return ContourTracing.createTracedROI(simpleImage, 1, 1, request);
    }

It gives much more plausible ROIs for the sample image:

image

There's also an interesting script that shows conversion in the opposite direction at https://github.com/glencoesoftware/ome-omero-roitool/blob/7b3896f6f6f3c168f3610a4a416ccf46df1aa278/src/dist/QuPath.scripts/OME_XML_export.groovy#L243

And a potentially-useful sample that shows more contortions are likely to be needed to figure out how to ensure that we only get annotations for the current series, and not for all ROIs in the image file:
https://github.com/ome/bioformats/blob/c997de185fc71eef191a1c9d82c35990bbd042c9/components/formats-gpl/utils/PrintROIs.java#L52

I've converted this to a draft PR as we probably shouldn't merge it currently.

One potential issue I can't check is that we might end up with this issue again, since I'm not sure how OMERO/the OME Model handles weird, self-intersecting polygons #1674
I think it's worth considering if you find any way to test it, but it may also be obscure enough not to be problematic and I don't think it should be a blocker... since we can't guarantee lossless conversion of ROI types anyway.

@Rylern
Copy link
Contributor Author

Rylern commented Apr 28, 2025

Indeed, the ROIs you get are more plausible.

There's a script that shows a similar conversion here (it's in the same repo than the script you mentionned). I tried to implement it but it wasn't working, as it seems to expect mask.getWidth() * mask.getHeight() == binData.getLength().getValue(), so your code seems better.

I'll start from your code and check that the correct series is used and handle transforms.

@Rylern
Copy link
Contributor Author

Rylern commented Apr 30, 2025

I integrated your code, handled transforms, and added unit tests for the converter from OME shape to QuPath ROI.

I could create a sample image by opening Fiji, opening any sample image, adding ROIs to the ROIs manager, and then Plugins -> Bio-Formats -> Bio-Formats Exporter. This allowed me to check that rectangles, ellipses, polygons, freehand selections, lines, and points work correctly.

About series, there is no function to select ROIs of a particular series (unlike on this script), but ROIs are retrieved from a IFormatReader and it's possible to set the series of a IFormatReader, so I assumed it was enough. I didn't find a way to test it though.

@Rylern Rylern marked this pull request as ready for review April 30, 2025 10:10
@petebankhead petebankhead merged commit d360a4b into qupath:main Apr 30, 2025
3 checks passed
@Rylern Rylern deleted the ome-xml-masks-support branch April 30, 2025 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support OME-XML masks
2 participants