import os
from traitlets import Bool, Unicode, observe
from glue_jupyter.common.toolbar_vuetify import read_icon
from jdaviz.configs.imviz.wcs_utils import get_compass_info
from jdaviz.core.custom_traitlets import FloatHandleEmpty
from jdaviz.core.events import AddDataMessage, RemoveDataMessage, CanvasRotationChangedMessage
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin, ViewerSelectMixin
from jdaviz.core.user_api import PluginUserApi
from jdaviz.core.tools import ICON_DIR
__all__ = ['RotateCanvas']
[docs]
@tray_registry('imviz-rotate-canvas', label="Canvas Rotation", viewer_requirements='image')
class RotateCanvas(PluginTemplateMixin, ViewerSelectMixin):
"""
See the :ref:`Canvas Rotation Plugin Documentation <rotate-canvas>` for more details.
Only the following attributes and methods are available through the
:ref:`public plugin API <plugin-apis>`:
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray`
* ``viewer`` (:class:`~jdaviz.core.template_mixin.ViewerSelect`):
Viewer to show orientation/compass information.
* ``angle``:
Angle to rotate the axes canvas, clockwise.
* ``flip_horizontal``:
Whether to flip the canvas horizontally, after applying rotation.
* :meth:`reset`
* :meth:`set_north_up_east_left`
* :meth:`set_north_up_east_right`
"""
template_file = __file__, "rotate_canvas.vue"
angle = FloatHandleEmpty(0).tag(sync=True) # degrees, clockwise
flip_horizontal = Bool(False).tag(sync=True) # horizontal flip applied after rotation
has_wcs = Bool(False).tag(sync=True)
icon_nuer = Unicode(read_icon(os.path.join(ICON_DIR, 'right-east.svg'), 'svg+xml')).tag(sync=True) # noqa
icon_nuel = Unicode(read_icon(os.path.join(ICON_DIR, 'left-east.svg'), 'svg+xml')).tag(sync=True) # noqa
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hub.subscribe(self, AddDataMessage, handler=self._on_viewer_data_changed)
self.hub.subscribe(self, RemoveDataMessage, handler=self._on_viewer_data_changed)
@property
def user_api(self):
return PluginUserApi(self, expose=('viewer', 'angle', 'flip_horizontal', 'reset',
'set_north_up_east_right', 'set_north_up_east_left'))
@property
def ref_data(self):
return self.app.get_viewer_by_id(self.viewer.selected_id).state.reference_data
def _on_viewer_data_changed(self, msg=None):
if not self.viewer_selected: # pragma: no cover
return
self.has_wcs = getattr(self.ref_data, 'coords', None) is not None
@observe('viewer_selected')
def _viewer_selected_changed(self, *args, **kwargs):
if not hasattr(self, 'viewer'):
return
vid = self.viewer.selected_id
self.angle = self.app._viewer_item_by_id(vid).get('canvas_angle', 0)
self.flip_horizontal = self.app._viewer_item_by_id(vid).get('canvas_flip_horizontal', False)
def _get_wcs_angles(self):
if not self.has_wcs:
raise ValueError("reference data does not have WCS, cannot determine orientation")
ref_data = self.ref_data
if ref_data is None: # pragma: no cover
raise ValueError("no data loaded in viewer, cannot determine orientation")
_, _, _, _, _, _, degn, dege, flip = get_compass_info(ref_data.coords, ref_data.shape)
return degn, dege, flip
[docs]
def reset(self):
"""
Reset the rotation to an angle of 0 and no flip
"""
self.angle = 0
self.flip_horizontal = False
[docs]
def set_north_up_east_left(self):
"""
Set the rotation angle and flip to achieve North up and East left according to the reference
image WCS.
"""
degn, dege, flip = self._get_wcs_angles()
self.angle = -degn
self.flip_horizontal = flip
[docs]
def set_north_up_east_right(self):
"""
Set the rotation angle and flip to achieve North up and East right according to the
reference image WCS.
"""
degn, dege, flip = self._get_wcs_angles()
self.angle = -degn
self.flip_horizontal = not flip
[docs]
def vue_reset(self, *args, **kwargs):
self.reset() # pragma: no cover
[docs]
def vue_set_north_up_east_left(self, *args, **kwargs):
self.set_north_up_east_left() # pragma: no cover
[docs]
def vue_set_north_up_east_right(self, *args, **kwargs):
self.set_north_up_east_right() # pragma: no cover
@observe('angle')
def _angle_changed(self, *args, **kwargs):
try:
angle = float(self.angle)
except ValueError: # pragma: no cover
# empty string, etc
angle = 0
# Rotate selected viewer canvas. This changes zoom too.
self.app._viewer_item_by_id(self.viewer.selected_id)['canvas_angle'] = angle
# broadcast message (used by compass, etc)
self.hub.broadcast(CanvasRotationChangedMessage(self.viewer.selected_id,
angle, self.flip_horizontal, sender=self))
@observe('flip_horizontal')
def _flip_changed(self, *args, **kwargs):
self.app._viewer_item_by_id(self.viewer.selected_id)['canvas_flip_horizontal'] = self.flip_horizontal # noqa
self.hub.broadcast(CanvasRotationChangedMessage(self.viewer.selected_id,
self.angle, self.flip_horizontal,
sender=self))