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 sky coordinates in subset plugin when WCS linked #2553

Closed
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
18 changes: 13 additions & 5 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,18 +1104,26 @@ def _get_multi_mask_subset_definition(self, subset_state):
"sky_region": None,
"subset_state": subset_state}]

def _get_wcs_from_subset(self, subset_state):
""" Usually WCS is subset.parent.coords, except special cuebviz case."""

if self.config == 'cubeviz':
parent_data = subset_state.attributes[0].parent
wcs = parent_data.meta.get("_orig_spatial_wcs", None)
else:
wcs = subset_state.xatt.parent.coords # imviz, try getting WCS from subset data

return wcs


def _get_roi_subset_definition(self, subset_state, to_sky=False):

# pixel region
roi_as_region = roi_subset_state_to_region(subset_state)

wcs = None
if to_sky:
if self.config == 'cubeviz':
parent_data = subset_state.attributes[0].parent
wcs = parent_data.meta.get("_orig_spatial_wcs", None)
else:
wcs = subset_state.xatt.parent.coords # imviz, try getting WCS from subset data
wcs = self._get_wcs_from_subset(subset_state)

# if no spatial wcs on subset, we have to skip computing sky region for this subset
# but want to do so without raising an error (since many subsets could be requested)
Expand Down
238 changes: 213 additions & 25 deletions jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
from glue_jupyter.common.toolbar_vuetify import read_icon
from traitlets import Any, List, Unicode, Bool, observe

from jdaviz.core.events import SnackbarMessage, GlobalDisplayUnitChanged
from jdaviz.core.events import SnackbarMessage, GlobalDisplayUnitChanged, LinkUpdatedMessage
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin, DatasetSelectMixin, SubsetSelect
from jdaviz.core.tools import ICON_DIR
from jdaviz.utils import MultiMaskSubsetState

from regions import CircleSkyRegion, CirclePixelRegion, PixCoord, EllipseSkyRegion, EllipsePixelRegion, CircleAnnulusSkyRegion
from astropy.coordinates import SkyCoord
import astropy.units as u

__all__ = ['SubsetPlugin']

SUBSET_MODES = {
Expand Down Expand Up @@ -76,6 +80,8 @@ def __init__(self, *args, **kwargs):
handler=self._on_subset_update)
self.session.hub.subscribe(self, GlobalDisplayUnitChanged,
handler=self._on_display_unit_changed)
self.session.hub.subscribe(self, LinkUpdatedMessage,
handler=self._on_link_update)

self.subset_select = SubsetSelect(self,
'subset_items',
Expand All @@ -85,6 +91,21 @@ def __init__(self, *args, **kwargs):
self.subset_states = []
self.spectral_display_unit = None

# if hasattr(self.app, '_link_type'):
# self.display_sky_coordinates = (self.app._link_type == 'wcs')
# else:
# self.display_sky_coordinates = False

def _on_link_update(self, *args):
# set to True only when link type is 'wcs'. if none or 'pixel', set
# this to false and display pixel coordinates

self.display_sky_coordinates = (self.app._link_type == 'wcs')

# only update if link changes and there is an active selection in plugin
if self.subset_selected != self.subset_select.default_text:
self._get_subset_definition(*args)
Comment on lines +106 to +107
Copy link
Contributor

Choose a reason for hiding this comment

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

self.subset_selected can be a list in multiselect mode, so this may need some more checks.


def _sync_selected_from_state(self, *args):
if not hasattr(self, 'subset_select') or self.multiselect:
# during initial init, this can trigger before the component is initialized
Expand Down Expand Up @@ -139,13 +160,18 @@ def _unpack_get_subsets_for_ui(self):
Convert what app.get_subsets returns into something the UI of this plugin
can display.
"""

self.hub.broadcast(SnackbarMessage(f'in _unpack_subsets_for_ui', color='error', sender=self))
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a reminder to remove this when you take this PR out of draft mode.


if self.multiselect:
self.is_centerable = True
return

subset_information = self.app.get_subsets(self.subset_selected,
simplify_spectral=False,
use_display_units=True)
use_display_units=True,
include_sky_region=True)

_around_decimals = 6 # Avoid 30 degrees from coming back as 29.999999999999996
if not subset_information:
return
Expand All @@ -170,12 +196,25 @@ def _unpack_get_subsets_for_ui(self):
"orig": subset_state.xatt.parent.label})

if isinstance(subset_state.roi, CircularROI):
x, y = subset_state.roi.center()
r = subset_state.roi.radius
if self.display_sky_coordinates:
sky_region = spec['sky_region']
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe sky_region does not know how to handle annulus types?

if sky_region is not None:
x = sky_region.center.ra.deg
y = sky_region.center.dec.deg
r = sky_region.radius.deg
display_unit_label = '(degrees)'
else:
x, y = subset_state.roi.center()
r = subset_state.roi.radius
display_unit_label = '(pixels)'
else:
x, y = subset_state.roi.center()
r = subset_state.roi.radius
display_unit_label = '(pixels)'
subset_definition += [
{"name": "X Center", "att": "xc", "value": x, "orig": x},
{"name": "Y Center", "att": "yc", "value": y, "orig": y},
{"name": "Radius", "att": "radius", "value": r, "orig": r}]
{"name": f"X Center {display_unit_label}", "att": "xc", "value": x, "orig": x},
{"name": f"Y Center {display_unit_label}", "att": "yc", "value": y, "orig": y},
{"name": f"Radius {display_unit_label}", "att": "radius", "value": r, "orig": r}]

elif isinstance(subset_state.roi, RectangularROI):
for att in ("Xmin", "Xmax", "Ymin", "Ymax"):
Expand All @@ -188,28 +227,51 @@ def _unpack_get_subsets_for_ui(self):
{"name": "Angle", "att": "theta", "value": theta, "orig": theta})

elif isinstance(subset_state.roi, EllipticalROI):
xc, yc = subset_state.roi.center()
rx = subset_state.roi.radius_x
ry = subset_state.roi.radius_y
theta = np.around(np.degrees(subset_state.roi.theta), decimals=_around_decimals)
if self.display_sky_coordinates:
sky_region = spec['sky_region']
xc = sky_region.center.ra.deg
yc = sky_region.center.dec.deg
rx = sky_region.height.deg
ry = sky_region.width.deg
angle = sky_region.angle.value
theta = np.around(np.degrees(angle), decimals=_around_decimals)
display_unit_label = '(degrees)'
else:
xc, yc = subset_state.roi.center()
rx = subset_state.roi.radius_x
ry = subset_state.roi.radius_y
theta = np.around(np.degrees(subset_state.roi.theta), decimals=_around_decimals)
display_unit_label = '(pixels)'

subset_definition += [
{"name": "X Center", "att": "xc", "value": xc, "orig": xc},
{"name": "Y Center", "att": "yc", "value": yc, "orig": yc},
{"name": "X Radius", "att": "radius_x", "value": rx, "orig": rx},
{"name": "Y Radius", "att": "radius_y", "value": ry, "orig": ry},
{"name": f"X Center {display_unit_label}", "att": "xc", "value": xc, "orig": xc},
{"name": f"Y Center {display_unit_label}", "att": "yc", "value": yc, "orig": yc},
{"name": f"X Radius {display_unit_label}", "att": "radius_x", "value": rx, "orig": rx},
{"name": f"Y Radius {display_unit_label}", "att": "radius_y", "value": ry, "orig": ry},
{"name": "Angle", "att": "theta", "value": theta, "orig": theta}]

elif isinstance(subset_state.roi, CircularAnnulusROI):
x, y = subset_state.roi.center()
inner_r = subset_state.roi.inner_radius
outer_r = subset_state.roi.outer_radius
subset_definition += [{"name": "X Center", "att": "xc", "value": x, "orig": x},
{"name": "Y Center", "att": "yc", "value": y, "orig": y},
{"name": "Inner radius", "att": "inner_radius",
if self.display_sky_coordinates:
sky_region = spec['sky_region']
x = sky_region.center.ra.deg
y = sky_region.center.dec.deg
inner_r = sky_region.inner_radius.to(u.deg).value
outer_r = sky_region.outer_radius.to(u.deg).value
display_unit_label = '(degrees)'
else:
x, y = subset_state.roi.center()
inner_r = subset_state.roi.inner_radius
outer_r = subset_state.roi.outer_radius
display_unit_label = '(pixels)'

subset_definition += [{"name": f"X Center {display_unit_label}", "att": "xc", "value": x, "orig": x},
{"name": f"Y Center {display_unit_label}", "att": "yc", "value": y, "orig": y},
{"name": f"Inner radius {display_unit_label}", "att": "inner_radius",
"value": inner_r, "orig": inner_r},
{"name": "Outer radius", "att": "outer_radius",
{"name": f"Outer radius {display_unit_label}", "att": "outer_radius",
"value": outer_r, "orig": outer_r}]


subset_type = subset_state.roi.__class__.__name__

elif isinstance(subset_state, RangeSubsetState):
Expand Down Expand Up @@ -302,7 +364,11 @@ def _on_display_unit_changed(self, msg):
if self.subset_selected != self.subset_select.default_text:
self._get_subset_definition(self.subset_selected)

def _convert_sky_input(self, subset_type):
pass

def vue_update_subset(self, *args):

if self.multiselect:
self.hub.broadcast(SnackbarMessage("Cannot update subset "
"when multiselect is active", color='warning',
Expand All @@ -318,14 +384,135 @@ def vue_update_subset(self, *args):
if len(self.subset_states) <= index:
return
sub_states = self.subset_states[index]

# if the update was done in sky coordinates, we need to convert these
# back to pixels before the subset is updated.

wcs = None
if self.display_sky_coordinates:

wcs = self.app._get_wcs_from_subset(sub_states)

if wcs is not None:

if self.subset_types[index] == "TrueCircularROI":

xy = [x['value'] for x in sub if 'Center' in x['name']]
rad = [x['value'] for x in sub if 'Radius' in x['name']][0]

orig_xy = [x['orig'] for x in sub if 'Center' in x['name']]
orig_rad = [x['orig'] for x in sub if 'Radius' in x['name']][0]

pixregion_xy = CircleSkyRegion(center=SkyCoord(*xy*u.deg),
radius=rad*u.deg).to_pixel(wcs)
pixregion_orig_xy = CircleSkyRegion(center=SkyCoord(*orig_xy*u.deg),
radius=orig_rad*u.deg).to_pixel(wcs)

new_xy = (pixregion_xy.center.x, pixregion_xy.center.y)
new_rad = pixregion_xy.radius

new_orig_xy = (pixregion_orig_xy.center.x, pixregion_orig_xy.center.y)
new_orig_rad = pixregion_orig_xy.radius


if self.subset_types[index] == "EllipticalROI":

xy = [x['value'] for x in sub if 'Center' in x['name']]
rads = [x['value'] for x in sub if 'Radius' in x['name']]
angle = [x['value'] for x in sub if 'Angle' in x['name']][0]

orig_xy = [x['orig'] for x in sub if 'Center' in x['name']]
orig_rads = [x['orig'] for x in sub if 'Radius' in x['name']]
orig_angle = [x['orig'] for x in sub if 'Angle' in x['name']][0]

pixregion_xy = EllipseSkyRegion(SkyCoord(*xy*u.deg), 2*rads[0]*u.deg,
2*rads[1]*u.deg, angle*u.deg).to_pixel(wcs)
pixregion_orig_xy = EllipseSkyRegion(SkyCoord(*orig_xy*u.deg), 2*orig_rads[0]*u.deg,
2*orig_rads[1]*u.deg, orig_angle*u.deg).to_pixel(wcs)

new_xy = (pixregion_xy.center.x, pixregion_xy.center.y)
new_rads = (pixregion_xy.height / 2., pixregion_xy.width / 2.)
new_angle = pixregion_xy.angle.to(u.deg).value

new_orig_xy = (pixregion_orig_xy.center.x, pixregion_orig_xy.center.y)
new_orig_rads = (pixregion_orig_xy.height / 2., pixregion_orig_xy.width / 2.)
new_orig_angle = pixregion_orig_xy.angle.to(u.deg).value

if self.subset_types[index] == "CircularAnnulusROI":

xy = [x['value'] for x in sub if 'Center' in x['name']]
rads = [x['value'] for x in sub if 'radius' in x['name']]

orig_xy = [x['orig'] for x in sub if 'Center' in x['name']]
orig_rads = [x['orig'] for x in sub if 'radius' in x['name']]

pixregion_xy = CircleAnnulusSkyRegion(SkyCoord(*xy*u.deg),
inner_radius=rads[0]*u.deg,
outer_radius=rads[1]*u.deg).to_pixel(wcs)
pixregion_orig_xy = CircleAnnulusSkyRegion(SkyCoord(*orig_xy*u.deg),
inner_radius=orig_rads[0]*u.deg,
outer_radius=orig_rads[1]*u.deg).to_pixel(wcs)

new_xy = (pixregion_xy.center.x, pixregion_xy.center.y)
new_rads = (pixregion_xy.inner_radius, pixregion_xy.outer_radius)

new_orig_xy = (pixregion_orig_xy.center.x, pixregion_orig_xy.center.y)
new_orig_rads = (pixregion_orig_xy.inner_radius, pixregion_orig_xy.outer_radius)


for d_att in sub:

if d_att["att"] == 'parent': # Read-only
continue

# we need WCS to update subset if input was in sky coordinates.
if wcs is not None:
if self.subset_types[index] == "TrueCircularROI":
if d_att["att"] == 'xc':
d_att["value"] = new_xy[0]
d_att["orig"] = new_orig_xy[0]
if d_att["att"] == 'yc':
d_att["value"] = new_xy[1]
d_att["orig"] = new_orig_xy[1]
if d_att["att"] == 'radius':
d_att["value"] = new_rad
d_att["orig"] = new_orig_rad

elif self.subset_types[index] == "TrueEllipticalROI":
if d_att["att"] == 'xc':
d_att["value"] = new_xy[0]
d_att["orig"] = new_orig_xy[0]
if d_att["att"] == 'yc':
d_att["value"] = new_xy[1]
d_att["orig"] = new_orig_xy[1]
if d_att["att"] == 'radius_x':
d_att["value"] = new_rads[0]
d_att["orig"] = new_orig_rads[0]
if d_att["att"] == 'radius_y':
d_att["value"] = new_rads[1]
d_att["orig"] = new_orig_rads[1]
if d_att["att"] == 'theta':
d_att["value"] = new_angle
d_att["orig"] = new_orig_angle

elif self.subset_types[index] == "CircularAnnulusROI":
if d_att["att"] == 'x':
d_att["value"] = new_xy[0]
d_att["orig"] = new_orig_xy[0]
if d_att["att"] == 'y':
d_att["value"] = new_xy[1]
d_att["orig"] = new_orig_xy[1]
if d_att["att"] == 'inner_radius':
d_att["value"] = new_rads[0]
d_att["orig"] = new_orig_rads[0]
if d_att["att"] == 'outer_radius':
d_att["value"] = new_rads[1]
d_att["orig"] = new_orig_rads[1]

if d_att["att"] == 'theta': # Humans use degrees but glue uses radians
d_val = np.radians(d_att["value"])
else:
d_val = float(d_att["value"])
d_val = float(d_att["value"])

# Convert from display unit to original unit if necessary
if self.subset_types[index] == "Range":
Expand All @@ -337,11 +524,12 @@ def vue_update_subset(self, *args):
d_val = d_val.to(u.Unit(base_units))
d_val = d_val.value

if float(d_att["orig"]) != d_val:
if float(d_att["orig"]) != d_val:
if self.subset_types[index] == "Range":
setattr(sub_states, d_att["att"], d_val)
else:
setattr(sub_states.roi, d_att["att"], d_val)

self._push_update_to_ui()

def _push_update_to_ui(self, subset_name=None):
Expand Down
Loading