Skip to content
Draft
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
79 changes: 79 additions & 0 deletions glue_jupyter/bqplot/profile/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

from .layer_artist import BqplotProfileLayerArtist

from astropy import units as u
from astropy.visualization.wcsaxes.formatter_locator import ScalarFormatterLocator
from astropy.wcs.wcsapi import SlicedLowLevelWCS

from glue.core.component_id import PixelComponentID
from glue_jupyter.common.state_widgets.layer_profile import ProfileLayerStateWidget
from glue_jupyter.common.state_widgets.viewer_profile import ProfileViewerStateWidget

Expand All @@ -31,13 +36,87 @@ class BqplotProfileView(BqplotBaseView):
'bqplot:xrange']

def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

self.state.add_callback('x_att', self._update_axes)
self.state.add_callback('normalize', self._update_axes)
self.state.add_callback('x_display_unit', self._update_axes)
self.state.add_callback('y_display_unit', self._update_axes)
self._update_axes()

self.formatter_locator = ScalarFormatterLocator(number=5, unit=u.one, format='%.3g')
self.scale_x.observe(self._update_labels, names=['min', 'max'])
self.scale_y.observe(self._update_labels, names=['min', 'max'])
self.state.add_callback('x_display_unit', self._update_labels)
self.state.add_callback('x_show_world', self._update_labels)
self.state.add_callback('x_display_unit', self._update_labels)

def _update_labels(self, *args):

if not isinstance(self.state.x_att, PixelComponentID) or not self.state.x_show_world:
self.axis_x.tick_values = None
self.axis_x.tick_labels = None
return

# Get WCS to use to convert pixel coordinates to world coordinates
# TODO: slice WCS with >1 dimensions
coords = self.state.reference_data.coords

if coords.pixel_n_dim > 1:
# As in ProfileLayerState.update_profile, we slice out other dimensions
# TODO: if axes are correlated we should warn the user somehow
# that the values are only true for a certain slice
slices = tuple(slice(None) if idx == self.state.x_att.axis else 0 for idx in range(coords.pixel_n_dim))
wcs_sub = SlicedLowLevelWCS(coords, slices)
else:
wcs_sub = self.state.reference_data.coords

# TODO: make sure we deal correctly with units when non-standard unit is given

# As we can't trust that the lower and upper values will be defined,
# we need to sample the axis at a number of points to determine the
# lower and upper values to use
x = np.linspace(self.scale_x.min, self.scale_x.max, 100)
w = wcs_sub.pixel_to_world_values(x)

# Filter out NaN and Inf values
w = w[np.isfinite(w)]
lower = np.min(w)
upper = np.max(w)

# Find the tick positions
tick_values_world, spacing = self.formatter_locator.locator(lower, upper)

# Convert back to pixel coordinates
tick_values = wcs_sub.world_to_pixel_values(tick_values_world)

# Round tick_values to nearest int to avoid issues with dict lookup of
# labels.
# TODO: can we avoid this and make tick_labels more robust?
tick_values = np.round(tick_values).astype(int).tolist()

# Convert world coordinates to display units
# TODO: right now we need to strip and add back the quantity-ness
# because we can't rely just on astropy's unit conversion, we need to
# use our own converter. For custom units, u.Unit may fail.
converter = UnitConverter()
tick_values_world = converter.to_unit(self.state.reference_data,
self.state.reference_data.world_component_ids[self.state.x_att.axis],
tick_values_world.value,
self.state.x_display_unit) * u.Unit(self.state.x_display_unit)

# Determine custom labels
# TODO: determine how to do pretty formatting for exponential notation
tick_labels = self.formatter_locator.formatter(tick_values_world, spacing)

# Construct tick_labels dictionary
tick_labels = dict(zip(tick_values, tick_labels))

with self.axis_x.hold_sync():
self.axis_x.tick_values = tick_values
self.axis_x.tick_labels = tick_labels

def _update_axes(self, *args):

if self.state.x_att is not None:
Expand Down
271 changes: 271 additions & 0 deletions notebooks/Experimental/Profile viewer world labelling in pixels.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a38fef1f",
"metadata": {},
"source": [
"## 1D data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10603e01",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"from astropy.wcs import WCS"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51dcf536",
"metadata": {},
"outputs": [],
"source": [
"wcs = WCS(naxis=1)\n",
"wcs.wcs.ctype = 'FREQ-LOG',\n",
"wcs.wcs.crval = 1e9,\n",
"wcs.wcs.crpix = 50,\n",
"wcs.wcs.cdelt = 5e7,\n",
"wcs.wcs.cunit = 'Hz',"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a28d61ca",
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb4562dc",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f580306d",
"metadata": {},
"outputs": [],
"source": [
"x = np.arange(100)\n",
"y = np.exp(-(x - 50) ** 2 / 40)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b24a8670",
"metadata": {},
"outputs": [],
"source": [
"plt.subplot(1, 2, 1)\n",
"plt.plot(x, wcs.pixel_to_world(x))\n",
"plt.subplot(1, 2, 2)\n",
"plt.plot(wcs.pixel_to_world(x), y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0fdfa807",
"metadata": {},
"outputs": [],
"source": [
"from glue.core import Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7280b32",
"metadata": {},
"outputs": [],
"source": [
"data = Data(y=y, coords=wcs, label='Test')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29a1c28c",
"metadata": {},
"outputs": [],
"source": [
"from glue_jupyter import jglue"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6547691",
"metadata": {},
"outputs": [],
"source": [
"app = jglue()\n",
"app.add_data(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c8f4d63",
"metadata": {},
"outputs": [],
"source": [
"data.coords.world_axis_units"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b6f7e76",
"metadata": {},
"outputs": [],
"source": [
"comp = data.get_component(data.world_component_ids[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35855deb",
"metadata": {},
"outputs": [],
"source": [
"comp.units"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6204c60c",
"metadata": {},
"outputs": [],
"source": [
"p = app.profile1d()\n",
"p.state.x_att = data.pixel_component_ids[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36468f7a",
"metadata": {},
"outputs": [],
"source": [
"p.axis_x.tick_values"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4117e83f",
"metadata": {},
"outputs": [],
"source": [
"p.state.x_show_world = False"
]
},
{
"cell_type": "markdown",
"id": "27289ccf",
"metadata": {},
"source": [
"## 3D data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a8d6a680",
"metadata": {},
"outputs": [],
"source": [
"wcs_3d = WCS(naxis=3)\n",
"wcs_3d.wcs.ctype = 'RA---TAN', 'FREQ-LOG', 'DEC--TAN'\n",
"wcs_3d.wcs.crval = 10, 1e9, 30\n",
"wcs_3d.wcs.crpix = 30, 50, 70\n",
"wcs_3d.wcs.cdelt = -0.01, 5e7, 0.01\n",
"wcs_3d.wcs.cunit = 'deg', 'Hz', 'deg'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac780fa7",
"metadata": {},
"outputs": [],
"source": [
"wcs_3d.wcs.set()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7566e74b",
"metadata": {},
"outputs": [],
"source": [
"data_3d = Data(y=np.broadcast_to(y[np.newaxis, :, np.newaxis], (50, 100, 50)), coords=wcs_3d, label='Test 3D')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ba13115",
"metadata": {},
"outputs": [],
"source": [
"app.add_data(data_3d)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5b7bf731",
"metadata": {},
"outputs": [],
"source": [
"p = app.profile1d(data=data_3d)\n",
"p.state.x_att = data_3d.pixel_component_ids[1]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}