Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image overlays to annotation schema #742

Merged
merged 10 commits into from
Dec 21, 2021
Merged

Add image overlays to annotation schema #742

merged 10 commits into from
Dec 21, 2021

Conversation

naglepuff
Copy link
Collaborator

This PR addresses server-side changes for #678

Changes include:

  • Modification of existing annotation schema to allow for specification of an image overlay

An image overlay is specified on the annotation itself, and not as an element. At a minimum, an image overlay requires a girder large image item ID. Opacity and location can also be specified. Location is specified with properties for position of the top-left corner of the image to overlay, as well as ta height and width. By default the overlaid image has opacity 1 and is aligned in the upper left in its native resolution.

The reason I went with adding the overlay as a direct child to the annotation schema, and not as a type of annotation element, is that I am trying to avoid a major refactor of what an annotation element is. It seems that a lot of downstream code assumes that annotation elements are made up of points/lines/polygons, and image overlays will need to be handled differently by client code (rendering on different layers, ensuring the linked tiled image exists).

I am open to discussing other possibilities regarding how best to model image overlays on the back end. Particularly I'm wondering if my reasoning above is indeed valid, or if there's another, better approach, like making a dedicated girder Model for image overlays.

@naglepuff naglepuff requested a review from manthey December 17, 2021 14:45
'upperLeftCorner': coordSchema,
'width': {
'type': 'number',
'minimum': 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want these to be exclusiveMinimum rather than minimum.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably will eventually want to allow for an affine transform matrix instead. We end up with six values: xoffset, yoffset, plus a 2x2 scaling matrix [[s11, s12], [s21, s22]]. For the width/height condition, this ends up being [[specified width/image width, 0], [0, specified height/image height]]. In many instances, we don't want to apply a scale or skew, but just an offset and a rotation, but we can leave that to whoever specifies the matrix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add an optional field of transform which would be an array of exactly four numbers (and document them). In this case, it would be nice to specify that you can't specify a transform AND a width or height.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 95c8b45. I think if we eventually want to allow for an affine transform matrix, it makes sense to be able to specify that now.

'minimum': 0
}
},
'required': ['upperLeftCorner', 'width', 'height']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to require these -- if they aren't specified they can default to 0, 0 for the upper left and the image's actual width and height.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 95c8b45. Properties for the position of the overlay are no longer specified as required.

@@ -496,7 +536,8 @@ class AnnotationSchema:
'title': 'Image Markup',
'description': 'Subjective things that apply to a '
'spatial region.'
}
},
'overlay': overlaySchema
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather this was just another possible annotation element. We can expect the viewer to do something similar to how we handle heatmaps -- in web_client/annotations/convertFeatures.js we create a layer per heatmap (which isn't actually strictly necessary, as two heatmaps could share a layer) and manage it there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See d0aa882. Once I added image overlays as an option for elements, I was hitting errors on upload of a new annotation during the calculation of the bounding box.

My naive approach to bounding box calculation for image overlays was just to set it to the size of the entire base image. Perhaps a better approach would be to use the transform properties to calculate the exact coordinates of the overlay. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to calculate the actual bounding box, since, in principle, you could have several images and only need to fetch the ones that intersect some region. Similarly, the overlay could actually extend outside of the base image.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just committed 9c44499, which should do a better job of calculating the bounding box. Let me know if you have any additional thoughts/suggestions.

'properties': {
'girderId': {
'type': 'string',
'description': 'Girder item ID containing the image to '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should enforce that this is a 24 digit hexadecimal number 'pattern': '^[0-9a-f]{24}$'.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 95c8b45

- Use affine transform matrix + offset
- Specify pattern for girderId
- Generate bounding boxes for image overlays based on the large image item
lowx, highx, lowy, highy = Annotationelement()._overlayBounds(element)
baseImageItem = ImageItem().load(annotation['itemId'], force=True)
baseImageMetadata = ImageItem().getMetadata(baseImageItem)
highz = baseImageMetadata.get('levels', 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In thinking about this, I don't think we want to store levels in the z bounding box -- the levels aren't conceptually heights (whereas there ARE multi-frame images that have a z-stack of frames). I think for now, the z will always just be zero. If we do that, then we don't have to pass the parent annotation into the _boundingBox method.

baseImageMetadata = ImageItem().getMetadata(baseImageItem)
highz = baseImageMetadata.get('levels', 0)
except Exception as e:
logger.error('Error generating bounding box for image overlay annotation: %s' % e)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to have this guard in the _overlayBounds method rather than here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 95cf345

Also move some try/except code to the _overlayBounds method from _boundingBox.
@manthey
Copy link
Member

manthey commented Dec 21, 2021

Great. It would be nice to have some basic tests of this. For instance, you could add a test in the girder_annotation/test_annotation/test_annotations.py file, in the TestLargeImageAnnotationElement class, adding something like:

    def testOverlayAnnotation(self, server, admin, fsAssetstore):
        file = utilities.uploadExternalFile('sample_image.ptif', admin, fsAssetstore)
        itemId = str(file['itemId'])
        bbox = Annotationelement()._boundingBox(
            {'type': 'imageoverlay', 'girderId': itemId})
        assert bbox == {
            'lowx': 0, 'lowy': 0, 'lowz': 0,
            'highx': 58368, 'highy': 12288, 'highz': 0,
            'details': 1,
            'size': (58368**2 + 12288**2)**0.5}

would test some of the bounding box code. You'll want some additional conditions with offset and matrix values set.

assert lowy == 1000
assert highx == (58368 / 2) + 500
assert highy == (12288 / 2) + 1000

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Can you also add a test that invokes the _boundingBox method to test that it invokes the _overlayBounds method appropriately? It doesn't have to test the various transform options, just the 'type': 'imageoverlay' pass-through.

lowy = min([corner[1] for corner in corners]).item()
highy = max([corner[1] for corner in corners]).item()
except Exception as e:
logger.error('Error generating bounding box for image overlay annotation: %s' % e)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could just do logger.exception(''Error generating bounding box for image overlay annotation')

Copy link
Member

@manthey manthey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might have to convert this to a non-draft PR in order to merge. It looks good.

@naglepuff naglepuff marked this pull request as ready for review December 21, 2021 17:55
@naglepuff naglepuff self-assigned this Dec 21, 2021
@naglepuff naglepuff merged commit 9164634 into master Dec 21, 2021
@naglepuff naglepuff deleted the overlay-schema branch December 21, 2021 19:09
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.

2 participants