-
Notifications
You must be signed in to change notification settings - Fork 43
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
Conversation
'upperLeftCorner': coordSchema, | ||
'width': { | ||
'type': 'number', | ||
'minimum': 0 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ' |
There was a problem hiding this comment.
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}$'
.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Great. It would be nice to have some basic tests of this. For instance, you could add a test in the
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 | ||
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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')
Also use `logger.exception` over `logger.error`
There was a problem hiding this 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.
This PR addresses server-side changes for #678
Changes include:
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.