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

Rotate image with glue Affine transform #1551

Closed
wants to merge 8 commits into from

Conversation

pllim
Copy link
Contributor

@pllim pllim commented Aug 8, 2022

Description

This pull request is to use glue Affine transform to rotate Imviz image.

Depends on:

Fixes #1466

TODO

  • How to prevent rotate image centering at a weird pixel location?
  • Is there a way to get a sane zoom box for Compass when rotated?
  • Does this work with multiple images/linking/subsets?
  • Does this work with multiple viewers? On linked pan/zoom and box zoom?
  • How does this affect plugins like aperture photometry?
  • Is there (more) lag, especially for large images?

Checklist for package maintainer(s)

This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.

  • Are two approvals required? Branch protection rule does not check for the second approval. If a second approval is not necessary, please apply the trivial label.
  • Do the proposed changes actually accomplish desired goals? Also manually run the affected example notebooks, if necessary.
  • Do the proposed changes follow the STScI Style Guides?
  • Are tests added/updated as required? If so, do they follow the STScI Style Guides?
  • Are docs added/updated as required? If so, do they follow the STScI Style Guides?
  • Did the CI pass? If not, are the failures related?
  • Is a change log needed? If yes, is it added to CHANGES.rst?
  • Is a milestone set?
  • After merge, any internal documentations need updating (e.g., JIRA, Innerspace)?

@pllim pllim added this to the 2.9 milestone Aug 8, 2022
@pllim pllim requested a review from astrofrog August 8, 2022 20:34
@github-actions github-actions bot added documentation Explanation of code and concepts imviz labels Aug 8, 2022
@pllim pllim mentioned this pull request Aug 8, 2022
@dhomeier
Copy link
Collaborator

dhomeier commented Aug 9, 2022

I think @astrofrog is already on the weird translation behaviour; one oddity I noticed when applying the Affine2D transform directly on the viewer state in the Imviz example:
For the ACS image setting the centre of rotation to the image centre does introduce a shift to the right and downwards, while for the simulated JWST data it is quite well centred already (positive or negative angles are of no relevance here).
Screenshot 2022-08-09 at 1 42 37 pm
Screenshot 2022-08-09 at 1 45 28 pm

Don't know if that tells something about how the transform behaves with different WCS...

@dhomeier
Copy link
Collaborator

dhomeier commented Aug 10, 2022

Don't know if that tells something about how the transform behaves with different WCS...

As you correctly surmised during the SME meeting, the actually relevant difference is the (non-)square aspect ratio – turns out the 'x', 'y' parameters to mpl's Affine2D.translate are pointing to the opposite coordinates we think they do!

So providing the centre coordinates the other way around the rotation appears to remain well in place:
Screenshot 2022-08-10 at 7 39 16 pm

As a note on the last Todo, I have run the above example (3 NIRSpec datasets of ~16000 * 7800 pixels) on my old 16 GB i5, which takes 1 or 2 seconds on the rotation – short comparable to the initial imviz.show().

Since the pointer is not shown in the screenshot, I also note that it was in the upper right, the life info showing the viewport position with the data for those pixel coordinates, i.e. not following the rotation – the actual pointer position is outside the image area here.

@pllim
Copy link
Contributor Author

pllim commented Aug 10, 2022

the actual pointer position is outside the image area here

Hmm, so coordinates info panel is wrong when canvas is rotated? That is a big problem. How do we tell glue-jupyter to give us the mouse event data that is consistent with what is shown?

@pllim
Copy link
Contributor Author

pllim commented Aug 10, 2022

p.s. Whoa, which colormap is that? 🤩

@dhomeier
Copy link
Collaborator

Just manually picked colours for the three filters. 😜
BTW I assume there is no way within Imviz to align the images on world coordinates?

@pllim
Copy link
Contributor Author

pllim commented Aug 10, 2022

align the images on world coordinates?

What do you mean? You should be able to with this:

def link_data(self, **kwargs):

imviz.link_data(link_type='wcs')

@dhomeier
Copy link
Collaborator

imviz.link_data(link_type='wcs')

Exactly that; perfect, thanks! (Did not scroll down far enough in the notebook...)

@dhomeier
Copy link
Collaborator

dhomeier commented Aug 11, 2022

Hmm, so coordinates info panel is wrong when canvas is rotated? That is a big problem. How do we tell glue-jupyter to give us the mouse event data that is consistent with what is shown?

Any hints where/how those data might be returned from glue-jupyter?

@astrofrog – any thoughts if the wrapper in bqplot apply_roi could/should also be replaced by a pretransform (or perhaps even reuse the one from glue.core.roi_pretransforms)?

@pllim
Copy link
Contributor Author

pllim commented Aug 11, 2022

Any hints where/how those data might be returned from glue-jupyter?

The event data we get comes from glue-jupyter + bqplot somewhere.

self.add_event_callback(self.on_mouse_or_key_event, events=['mousemove', 'mouseenter',
'mouseleave', 'keydown'])

https://github.com/glue-viz/glue-jupyter/blob/bda0e37b2730e9f95be62f56b02db502ea30b03f/glue_jupyter/bqplot/common/viewer.py#L133

I think it is the data info for the reference data tied to a particular viewer, which has given me WCS transformation headaches when one isn't viewing the reference data but I have since worked around that in Imviz.

@dhomeier
Copy link
Collaborator

dhomeier commented Aug 11, 2022

The event data we get comes from glue-jupyter + bqplot somewhere.

Might be worth trying to apply the (inverse?) affine_matrix to

x = data['domain']['x']
y = data['domain']['y']

but I think this would indee, if possible, be better handled on the bqplot side, although I don't see where data['domain'] might be populated inside glue_jupyter.

@pllim
Copy link
Contributor Author

pllim commented Aug 11, 2022

Might be worth trying to apply the (inverse?)

I'd rather glue-jupyter or bqplot handles this for us. Otherwise, Jdaviz will have to dig through all the event handling code and figure out when to inverse the transform...

Copy link
Collaborator

@astrofrog astrofrog left a comment

Choose a reason for hiding this comment

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

I've updated my two branches/PRs so this will now require some changes here mentioned below.

Things that don't yet work as far as I can tell:

  • If you change the rotation, the 'grey' active selection shape doesn't follow. I think we should clear it if the rotation changes
  • We should probably provide a method in glue-core on the state class which given cursor coordinates can provide the actual array index into the data since these are now no longer the same - we can then use that to fix the mouse over coordinates here

@dhomeier - would you be able to look into both of these?

jdaviz/configs/imviz/plugins/rotate_image/rotate_image.py Outdated Show resolved Hide resolved

# Rotate selected viewer canvas.
# TODO: Translation still a bit broken if zoomed in.
viewer.state.rotation = math.radians(self._theta)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works but pretty sure it is clockwise again... Is this expected?

Also, the center translation is still a bit off when I zoom in first and then rotate on a rectangular image. If I zoom in on a star, I would expect rotation around the star. But the star moves quite dramatically when I rotate.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Apparently applying the transform directly on x, y in the pretransform.__call__() seems to have it operate the other way around again, but that is easily fixed, even if I cannot follow the full chain of events.

I think for finding the right centre of rotation setting xy from (x_min + x_max) / 2 rather than shape[0] / 2 etc. is still the better option (x_min, y_min may be nonzero even if not zoomed in); at least that that keeps the viewport centred and rotated around the original zoom region when zooming in first and then rotating. Unfortunately with the same modification, zooming in after rotation is completely off – apparently this is setting the limits from the unrotated frame, I recon this is failing due to the data values not being returned from the transformed pixel coordinates just as with the mouseover.
I would expect that once the latter problem is solved, the zoom will also operate on the correct subset, so the alternative above still looks a step forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Derek, all this will be fixed upstream, yes?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure which of the "all" is fixable upstream. I have a patch for the sense of direction, and keeping the centre of rotation in the current viewport centre, but the mouseover stats, active selection area and zoom-after-rotation remain of.

  • We should probably provide a method in glue-core on the state class which given cursor coordinates can provide the actual array index into the data since these are now no longer the same - we can then use that to fix the mouse over coordinates here

I think I'll work on that next, but it sounds like this will still require some adaption from Imviz to use those viewer.state methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it helps, the Imviz mouseover logic is here:

if data['event'] == 'mousemove':

Out of scope now but if you are curious, Cubeviz version is here:

if data['event'] == 'mousemove':

They both send info to the same plugin here:

https://github.com/spacetelescope/jdaviz/blob/main/jdaviz/configs/imviz/plugins/coords_info/coords_info.py

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes; looked at the above, but can I find any more info on what type of object the data is that is passed to on_mouse_or_key_event? I've been struggling to find out what its 'domain' item is or where it is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That I am not sure... Could be glue-jupyter , bqplot-image-gl , bqplot ... 🤷

Hopefully @maartenbreddels or @astrofrog can tell us.


# TODO: Zoom box in Compass still broken, how to fix? If not, we need to disable it.
# Update Compass plugin.
viewer.on_limits_change()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

How do I get viewer limits that is consistent with the rotation, so I can show a sane zoom box in Compass plugin when image is rotated?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you going to show the image rotated in the compass plugin as well? Or are you going to show it in the original/native orientation and want to show the box rotated? (since it matters for the limits)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking it is easier in Compass if we do not rotate the image but only rotate the zoom box. The Compass display real estate is already pretty small. To rotate the image in Compass would make the image display even smaller when rotated.

Also even if not rotated, images can have wild orientations after you link them by WCS.

@codecov
Copy link

codecov bot commented Aug 16, 2022

Codecov Report

Merging #1551 (fdab918) into main (75738ca) will decrease coverage by 0.52%.
The diff coverage is 68.18%.

❗ Current head fdab918 differs from pull request most recent head 5b29340. Consider uploading reports for the commit 5b29340 to get more accurate results

@@            Coverage Diff             @@
##             main    #1551      +/-   ##
==========================================
- Coverage   86.12%   85.60%   -0.53%     
==========================================
  Files          94       96       +2     
  Lines        9318     9237      -81     
==========================================
- Hits         8025     7907     -118     
- Misses       1293     1330      +37     
Impacted Files Coverage Δ
jdaviz/app.py 91.79% <ø> (ø)
...configs/imviz/plugins/rotate_image/rotate_image.py 63.15% <63.15%> (ø)
jdaviz/configs/imviz/plugins/__init__.py 100.00% <100.00%> (ø)
...viz/configs/imviz/plugins/rotate_image/__init__.py 100.00% <100.00%> (ø)
jdaviz/core/helpers.py 25.54% <0.00%> (-40.11%) ⬇️
jdaviz/core/tools.py 74.25% <0.00%> (-3.53%) ⬇️
...plugins/spectral_extraction/spectral_extraction.py 88.93% <0.00%> (-2.50%) ⬇️
jdaviz/configs/cubeviz/plugins/viewers.py 89.28% <0.00%> (-2.09%) ⬇️
jdaviz/core/marks.py 89.75% <0.00%> (-0.78%) ⬇️
...igs/default/plugins/subset_plugin/subset_plugin.py 97.81% <0.00%> (-0.08%) ⬇️
... and 8 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@astrofrog
Copy link
Collaborator

I've pushed a change to glue-viz/glue#2310 which will preserve the field of view when changing the rotation, so if you are centered on a star and rotate the canvas then the star will still be at the center. This is the way that DS9 does it and I think it is the most intuitive.

For the compass, I think that we should also use the DS9 approach - rotate the image and keep the box in the same orientation as the main image view. The easiest way to achieve this is that in the compass imshow() call, we should specify transform= and give it the same affine transform we use in glue - for now if you want to avoid having to re-construct it yourself you can access the glue one with viewer.state._affine_pretransform._transform but we can obviously try and change this to use public attributes if it works.

@astrofrog
Copy link
Collaborator

I've pushed more changes to both the glue-core and glue-jupyter PR so be sure to update these. I've also opened pllim#6 which shows how to fix the compass plugin to correctly show image rotation.

One thing I've noticed is that if I modify the rotation using the plugin, the view seems to update a few times which is due to the part of the code that deals with the aspect ratio calculation. This shouldn't be happening though, we should be delaying calls to the aspect ratio so that it just happens once. So something isn't working quite right there. I see the issue in glue-jupyter too but not in Qt glue so the issue must be in glue-jupyter. I will investigate!

@pllim
Copy link
Contributor Author

pllim commented Aug 19, 2022

@astrofrog / @dhomeier -- Should I hold off until more work upstream is in?

@astrofrog
Copy link
Collaborator

I think it would be valuable to already try it as is

@dhomeier
Copy link
Collaborator

I've put two suggestions here and in glue-viz/glue#2310 to fix the case where a pretransform is not yet defined; otherwise this should be good to work with.

@astrofrog
Copy link
Collaborator

I can't test this right now because my jdaviz installation isn't working properly, but I think you should be able to fix the coordinate overlay by doing:

            # If needed, transform x/y to the actual rotated reference data
            # frame of reference
            if self.state.rotation != 0:
                x, y = self.state._affine_pretransform(x, y)

just before the maxsize = ... in viewer.py

Altough if you think the results don't make sense you might need self.state._affine_pretransform.inverse(x, y) (trying to wrap my head around the direction...)

@astrofrog
Copy link
Collaborator

Also a note that we are going to have to do something about the markers as currently those won't be rotated - will have a think.

@rosteen rosteen modified the milestones: 2.9, 2.10 Aug 24, 2022
@pllim pllim modified the milestones: 2.10, 2.11 Aug 26, 2022
@astrofrog
Copy link
Collaborator

I've had a long think about this and I think as promising as this approach seemed initially, it is now getting a lot more complicated when it comes to dealing properly with markers, and I am also seeing some issues with multiple redraws that are tricky to solve. This is going to require either a lot of special casing in the glue-core and glupyter code base, or duplication of a lot of code (but modified) from glue in jdaviz, neither of which are desirable.

I think that we should consider one of the following approaches:

  • Doing rotation of the final canvas as was done in POC: canvas rotation #1384 which would require figuring out properly what the performance issues were there. The advantage of that approach is that it should be completely agnostic of what is shown in the figure - markers, images, anything else. I wonder if this is something so generic in a sense that it could be developed as a bqplot extension - that is, just generically having the ability to rotate the whole canvas inside a set of axes (and optionally updating the ticks/labels accordingly). This approach is not something I could help with and instead someone like @maartenbreddels would be the best person to help with this (though if there are performance bottlenecks on the glue side I can help with that).

  • A variant of Rotate image in Imviz #1340 - we could develop a RotatedData glue data class that will basically look to all parts of glue/jdaviz as if it had been reprojected and had an updated WCS and data (though the reprojection would happen on-the-fly for performance). This could re-use some of the work done for the approach in the present PR, but it is a different way of thinking about it. The difference with Rotate image in Imviz #1340 is that instead of having to deal with two datasets (rotated and unrotated) we would simply wrap all datasets upon loading in this new data class and then be able to dynamically change the rotation. I think it would probably take a couple of hours to determine if this would be a viable approach or whether it would have performance or other issues.

In the second case, and as in the present PR, one of the limitations is that if the user is ever exposed to pixel coordinates (for example through mouse-over or editing the limits of the plot) the pixel coordinates would be those of the rotated image.

@pllim
Copy link
Contributor Author

pllim commented Sep 9, 2022

In the second case, and as in the present PR, one of the limitations is that if the user is ever exposed to pixel coordinates (for example through mouse-over or editing the limits of the plot) the pixel coordinates would be those of the rotated image.

Is that bad?

I've had a long think about this

I am interested to hear if @maartenbreddels has any opinions on the options.

@maartenbreddels
Copy link
Collaborator

Maybe a good topic for the next SME call? I don't have a good overview of the different approaches and cons and pros.

@pllim
Copy link
Contributor Author

pllim commented Sep 15, 2022

@astrofrog , did you make any progress on this? If there is some new proof-of-concept ready for us to play with, please let us know. Thanks!

@astrofrog
Copy link
Collaborator

No, unfortunately it will be week or so before I can get back to this.

@rosteen rosteen modified the milestones: 2.11, 3.1 Oct 10, 2022
@rosteen rosteen modified the milestones: 3.1, 3.2 Oct 26, 2022
@pllim
Copy link
Contributor Author

pllim commented Dec 14, 2022

Superseded by #1904

@pllim pllim closed this Dec 14, 2022
@pllim pllim deleted the canvas-rotation-again branch December 14, 2022 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Explanation of code and concepts imviz
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEAT] Imviz: Rotate image
5 participants