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

POC: canvas rotation #1384

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Cubeviz
Imviz
^^^^^

- New Simple Image Rotation plugin to rotate the axes of images. [#1384]

Mosviz
^^^^^^

Expand Down
3 changes: 3 additions & 0 deletions docs/reference/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ Plugins
.. automodapi:: jdaviz.configs.imviz.plugins.links_control.links_control
:no-inheritance-diagram:

.. automodapi:: jdaviz.configs.imviz.plugins.rotate_image.rotate_image
:no-inheritance-diagram:

.. automodapi:: jdaviz.configs.mosviz.plugins.row_lock.row_lock
:no-inheritance-diagram:

Expand Down
10 changes: 9 additions & 1 deletion jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ def _viewer_by_reference(self, reference):

Returns
-------
`~glue_jupyter.bqplot.common.BqplotBaseView`
viewer : `~glue_jupyter.bqplot.common.BqplotBaseView`
The viewer class instance.
"""
viewer_item = self._viewer_item_by_reference(reference)
Expand Down Expand Up @@ -1083,6 +1083,13 @@ def resize(stack_items):
bqplot_fig.layout.height = '99.9%'
bqplot_fig.layout.height = '100%'

if len(args) and isinstance(args[0], dict) and 'viewer' in args[0]:
# TODO: set appropriate viewer.zoom_level
# TODO: box-zoom to act based on overscaled canvas
# viewer = self._viewer_by_id(args[0]['viewer'])
# viewer.zoom_level = 'fit' # Did not see any difference, also tried 0.5
pass

def vue_destroy_viewer_item(self, cid):
"""
Callback for when viewer area tabs are destroyed. Finds the viewer item
Expand Down Expand Up @@ -1380,6 +1387,7 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None):
'viewer_options': "IPY_MODEL_" + viewer.viewer_options.model_id,
'layer_viewer_open': False,
'selected_data_items': [],
'rotation': 0,
'config': self.config, # give viewer access to app config/layout
'data_open': False,
'collapse': True,
Expand Down
1 change: 1 addition & 0 deletions jdaviz/configs/imviz/imviz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tray:
- imviz-compass
- imviz-line-profile-xy
- imviz-aper-phot-simple
- imviz-rotate-image
- g-export-plot
viewer_area:
- container: col
Expand Down
1 change: 1 addition & 0 deletions jdaviz/configs/imviz/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .compass import * # noqa
from .aper_phot_simple import * # noqa
from .line_profile_xy import * # noqa
from .rotate_image import * # noqa
1 change: 1 addition & 0 deletions jdaviz/configs/imviz/plugins/rotate_image/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .rotate_image import * # noqa
31 changes: 31 additions & 0 deletions jdaviz/configs/imviz/plugins/rotate_image/rotate_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from traitlets import Any

from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import TemplateMixin, ViewerSelectMixin


@tray_registry('imviz-rotate-image', label="Simple Image Rotation")
class RotateImageSimple(TemplateMixin, ViewerSelectMixin):
template_file = __file__, "rotate_image.vue"

angle = Any(0).tag(sync=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._theta = 0 # degrees, clockwise

def vue_rotate_image(self, *args, **kwargs):
# We only grab the value here to avoid constantly updating as
# user is still entering or updating the value.
try:
self._theta = float(self.angle)
except Exception:
return

viewer = self.app._viewer_by_id(self.viewer_selected)

# Rotate selected viewer canvas. This changes zoom too.
self.app._viewer_item_by_id(self.viewer_selected)['rotation'] = self._theta

# Update Compass plugin.
viewer.on_limits_change()
30 changes: 30 additions & 0 deletions jdaviz/configs/imviz/plugins/rotate_image/rotate_image.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<j-tray-plugin>
<v-row>
<j-docs-link :link="'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#simple-image-rotation'">Rotate image.</j-docs-link>
</v-row>

<plugin-viewer-select
:items="viewer_items"
:selected.sync="viewer_selected"
label="Viewer"
hint="Select viewer."
/>

<v-row>
<v-col>
<v-text-field
v-model="angle"
type="number"
label="Angle"
hint="Rotation angle in degrees clockwise"
></v-text-field>
</v-col>
</v-row>

<v-row justify="end">
<v-btn color="primary" text @click="rotate_image">Rotate</v-btn>
</v-row>

</j-tray-plugin>
</template>
19 changes: 19 additions & 0 deletions jdaviz/configs/imviz/plugins/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ def _get_real_xy(self, image, x, y):

return x, y, coords_status

# TODO: Remove if we end up not using it.
# def _get_zoom_center(self, image):
# """Return ``(x, y)`` of the center of zoom for a given image.
# Also see :meth:`_get_zoom_limits`.
# """
# x_cen = (self.state.x_min + self.state.x_max) * 0.5
# y_cen = (self.state.y_min + self.state.y_max) * 0.5
# return self._get_real_xy(image, x_cen, y_cen)[:2]

def _get_zoom_limits(self, image):
"""Return a list of ``(x, y)`` that defines four corners of
the zoom box for a given image.
Expand All @@ -263,6 +272,16 @@ def _get_zoom_limits(self, image):
(self.state.x_min, self.state.y_max),
(self.state.x_max, self.state.y_max),
(self.state.x_max, self.state.y_min)))

# Disable if there is canvas rotation.
theta = self.jdaviz_app._viewer_item_by_id(self.reference_id)['rotation']
if not np.allclose(theta, 0):
zoom_limits = None

# TODO: I thought this would work but it does not.
# new_x, new_y = wcs_utils.rotate_pt(zoom_limits[:, 0], zoom_limits[:, 1], theta)
# zoom_limits = np.stack([new_x, new_y], axis=1)

return zoom_limits

def set_compass(self, image):
Expand Down
59 changes: 55 additions & 4 deletions jdaviz/container.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:data_items="data_items"
:app_settings="app_settings"
:icons="icons"
@resize="$emit('resize')"
@resize="(e) => $emit('resize', e)"
:closefn="closefn"
@data-item-selected="$emit('data-item-selected', $event)"
@data-item-remove="$emit('data-item-remove', $event)"
Expand All @@ -18,7 +18,7 @@
:key="viewer.id"
:title="viewer.id"
:tab-id="viewer.id"
@resize="$emit('resize')"
@resize="resizeViewer(viewer)"
@destroy="destroy($event, viewer.id)"
style="display: flex; flex-flow: column; height: 100%; overflow-y: auto; overflow-x: hidden"
>
Expand Down Expand Up @@ -53,8 +53,15 @@

</div>

<v-card tile flat style="flex: 1; margin-top: -2px; overflow-y: auto;">
<jupyter-widget :widget="viewer.widget" style="width: 100%; height: 100%"></jupyter-widget>
<v-card
tile
flat
style="flex: 1; margin-top: -2px; overflow: hidden;">
<jupyter-widget
:widget="viewer.widget"
:ref="'viewer-widget-'+viewer.id"
:style="'width: 100%; height: 100%; overflow: hidden; transform: rotate('+viewer.rotation+'deg)'"
></jupyter-widget>
</v-card>
</gl-component>
</component>
Expand All @@ -69,6 +76,15 @@ module.exports = {
return this.$children[0];
};
},
watch: {
stack(new_stack, old_stack) {
console.log("watch: stack")
new_stack.viewers.forEach((viewer) => {
console.log("original resize of "+viewer.id)
this.resizeViewer(viewer)
})
}
},
methods: {
computeChildrenPath() {
return this.$parent.computeChildrenPath();
Expand All @@ -78,6 +94,41 @@ module.exports = {
* between a user closing a tab or a re-render. However, when the user closes a tab, the
* source of the event is a vue component. We can use that distinction as a close signal. */
source.$root && this.closefn(viewerId);
},
resizeViewer(viewer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I rotate to 45 degrees and then rotate back, but the Compass zoom box starts acting weird (i.e., it is showing the zoom limits that is a little off from what I am looking at). Since Compass grabs the numbers straight of viewer.state.x/y_min/max attributes, something isn't sync-ing right after this operation but I don't know what. Is it the aspect ratio?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe the WCS distortion in the Imviz example notebook data is causing trouble.

if (viewer.config !== 'imviz') {
this.$emit('resize')
return
}

const viewerWidget = this.$refs['viewer-widget-'+viewer.id][0]
const cardEl = viewerWidget.$parent
const cardHeight = cardEl.$el.clientHeight
const cardWidth = cardEl.$el.clientWidth

// resize width and height to be the diagonal dimension,
// then offset so the center stays in the center
// TODO: eventually we may want to make this dynamic to avoid over-resizing
// (by using a rectangle and also adjusting as the rotation angle is changed)
const diagDim = (cardEl.$el.clientHeight**2+cardEl.$el.clientWidth**2)**0.5;
const top = (cardHeight - diagDim) / 2
const left = (cardWidth - diagDim) / 2

viewerWidget.$el.style.height = diagDim+"px"
viewerWidget.$el.style.width = diagDim+"px"
viewerWidget.$el.style.position = 'absolute'
viewerWidget.$el.style.top = top+"px"
viewerWidget.$el.style.left = left+"px"
//console.log("diagDim: "+diagDim+ " top: "+top+" left: "+left)

// TODO: "undo" oversizing by adjusting the viewer.zoom_level
// TODO: box zoom also needs to account for scale factor to set limits that result in the box
// being shown in the viewer, not the oversized canvas
this.$emit('resize', {viewer: viewer.id,
height: cardHeight,
width: cardWidth,
canvasHeight: diagDim,
canvasWidth: diagDim})
}
},
computed: {
Expand Down