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

Display image overlay annotations #750

Merged
merged 14 commits into from
Jan 13, 2022
Merged

Display image overlay annotations #750

merged 14 commits into from
Jan 13, 2022

Conversation

naglepuff
Copy link
Collaborator

@naglepuff naglepuff commented Dec 23, 2021

Implement client support for image overlay annotation elements.

Changes include:

  • Draw image overlays when annotations containing them are selected in the Girder large_image UI
  • Remove image overlays when their annotations are deselected
  • Support rendering image overlays with opacity specified in the annotation JSON
  • Support image overlay offset
  • Support image overlay transform via affine matrix

(opening as draft PR to kick start discussion about implementation details)

Closes #678

@manthey
Copy link
Member

manthey commented Jan 4, 2022

You'll probably need to merge or rebase on master for CI to have any chance of passing since Pillow 9.0.0 broke some of the tests.

(WIP) Fix test overlay element JSON

(WIP) Fix creating test overlay annovation

(WIP) Move test overlay to setup section

Fix test for drawing overlay annotations
@naglepuff naglepuff changed the title (WIP) Display image overlay annotations Display image overlay annotations Jan 4, 2022
@naglepuff naglepuff requested a review from manthey January 4, 2022 19:43
@naglepuff naglepuff marked this pull request as ready for review January 4, 2022 19:47
@@ -1,3 +1,5 @@
// import { restRequest } from '@girder/core/rest';
Copy link
Member

Choose a reason for hiding this comment

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

Please remove this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed in 5b3f52b

Also remove commented out import.
New property `_unclampBoundsForOverlay` with getter/setter added to control
whether or not the X/Y bounds should be clamped when rendering image overlays.
Unclamping the bounds is useful because image overlays may be cut off by the viewer
if they are larger than the base image layer. However, unclamped bounds eliminates
the scroll out to center functionality. The default behavior at the time of this
commit is to unclamp the bounds.
@naglepuff naglepuff requested a review from manthey January 6, 2022 21:40
@manthey
Copy link
Member

manthey commented Jan 7, 2022

I see two issues. I placed a small image (one using the PIL tile source) as an overlay on a much larger image with an identity matrix specified as the transform.

Internally, I still see transform parameters being applied (gcs: "+proj=longlat +axis=enu +s11=1 +s12=0 +s21=0 +s22=1 +xoff=-0 +yoff=0" -- note the -0.

More importantly, though, the small image is magnified. Fundamentally, the scale is incorrect, since the function that calculates the layer properties is making level 0 tiles appear at the same scale for the two sources. This might be as simple as scaling down the transform matrix based on the number of tile levels between the two images, but it might be more complex than that.

Remove console log statement
@naglepuff
Copy link
Collaborator Author

Internally, I still see transform parameters being applied (gcs: "+proj=longlat +axis=enu +s11=1 +s12=0 +s21=0 +s22=1 +xoff=-0 +yoff=0" -- note the -0.

This is easy enough to fix. The issue lies with the check matrix === [[1, 0], [0, 1]].

More importantly, though, the small image is magnified. Fundamentally, the scale is incorrect, since the function that calculates the layer properties is making level 0 tiles appear at the same scale for the two sources. This might be as simple as scaling down the transform matrix based on the number of tile levels between the two images, but it might be more complex than that.

I think I see this as well. If I overlay a small image (~4k by 2k) onto a very large image (~90k by 90k), the small image is blown up well beyond the space it should take up. With my test images, the smaller has 6 zoom levels, the large has 10. How does this ratio inform how to appropriately scale the smaller image?

@manthey
Copy link
Member

manthey commented Jan 12, 2022

GeoJS has a utility function (pixelCoordinateParams) that assumes the image will take up the whole map space. The correct thing to do is to either have a different function where this assumption is not made, or where we adjust the layer parameter outputs of that function.

Alternately, we can probably twiddle the affine transform to make up for the fact that this isn't so -- by scaling the image by 2**(maxlevel of base image - maxlevel of overlayimage) to account for the number of tile levels difference and by (tile width of base image / tile width of overlay image), (tile height of base image / tile height of overlay image). This will probably just work, but the extending or adjusting the results from pixelCoordinateParams would be "more correct" and probably by slightly more efficient.

I'll see if I can generalize this, as we've wanted it once before on a different project.

@naglepuff
Copy link
Collaborator Author

GeoJS has a utility function (pixelCoordinateParams) that assumes the image will take up the whole map space. The correct thing to do is to either have a different function where this assumption is not made, or where we adjust the layer parameter outputs of that function.

Which parameter outputs would need to be adjusted? tilesAtZoom and tilesMaxBounds?

Alternately, we can probably twiddle the affine transform to make up for the fact that this isn't so

I've been fiddling around with this and it seems to be a decent workaround. However, I'd prefer to do something more "correct." I'm leaning towards adjusting the output of pixelCoordinateParams, as I don't want to increase the scope to include changes to geojs at this point. Let me know if you have any thoughts.

@manthey
Copy link
Member

manthey commented Jan 12, 2022

So I dug through an old example I had where I did this somewhere else. There is code below. item is a an object with id as the Girder ID and zoffset the base image's max levels - overlay image's max levels. The big other change is the url isn't just a template string, it is actually a function that returns the url.

    if (item.zoffset) {
      params.layer.url = (x, y, z) => 'api/v1/item/' + item.id + `/tiles/zxy/${z-item.zoffset}/${x}/${y}`;
      params.layer.minLevel = item.zoffset;
      params.layer.maxLevel += item.zoffset;
      params.layer.tilesAtZoom = (level) => {
        var scale = Math.pow(2, params.layer.maxLevel - level);
        return {
          x: Math.ceil(item.meta.sizeX / item.meta.tileWidth / scale),
          y: Math.ceil(item.meta.sizeY / item.meta.tileHeight / scale)
        };
      };
      params.layer.tilesMaxBounds = (level) => {
        var scale = Math.pow(2, params.layer.maxLevel - level);
        return {
          x: Math.floor(item.meta.sizeX / scale),
          y: Math.floor(item.meta.sizeY / scale)
        };
      };
    }

Refactor generating layer params for overlay

Fix layer params with different zoom levels
params.layer.opacity = overlay.opacity || 1;

if (this.levels !== overlayImageMetadata.levels) {
const levelDifference = Math.abs(this.levels - overlayImageMetadata.levels);
Copy link
Member

Choose a reason for hiding this comment

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

This should NOT be abs -- it could be negative.

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 a1743d5

_generateOverlayLayerParams(overlayImageMetadata, overlayImageId, overlay) {
const geo = window.geo;
let params = geo.util.pixelCoordinateParams(
this.viewer.node(), overlayImageMetadata.sizeX, overlayImageMetadata.sizeY, overlayImageMetadata.tileHeight, overlayImageMetadata.tileWidth
Copy link
Member

Choose a reason for hiding this comment

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

tileWidth and tileHeight are swapped in this call.

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 a1743d5

Don't use absolute value when determining the difference in levels
between the base image and overlay image.

Swap tileWidth and tileHeight in call to `pixelCoordinateParams`,
as they were out of order.
@naglepuff naglepuff requested a review from manthey January 13, 2022 19:24
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 image overlay annotations
2 participants