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

Implement unit conversion for contour levels #423

Merged
merged 10 commits into from
Apr 17, 2024
Prev Previous commit
Next Next commit
Added visual test for units of contours and fix issues
  • Loading branch information
astrofrog committed Apr 16, 2024
commit 8639658835771a3f22032a73833f8b3c231f8623
12 changes: 12 additions & 0 deletions glue_jupyter/bqplot/image/layer_artist.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
from glue.viewers.image.layer_artist import BaseImageLayerArtist, ImageLayerArtist, ImageSubsetArray
from glue.viewers.image.state import ImageSubsetLayerState
from glue.core.fixed_resolution_buffer import ARRAY_CACHE, PIXEL_CACHE
from glue.core.units import find_unit_choices, UnitConverter
from ...link import link

from bqplot_image_gl import Contour
@@ -88,6 +89,17 @@ def _update_contour_lines(self):
self.contour_artist.contour_lines = []
return

# As the levels may be specified in a different unit we should convert
# the data to match the units of the levels (we do it this way around
# so that the labels are shown in the new units)

converter = UnitConverter()

contour_data = converter.to_unit(self.state.layer,
self.state.attribute,
contour_data,
self.state.c_display_unit)

for level in self.state.levels:
if level not in self._contour_line_cache:
contour_line_set = skimage.measure.find_contours(contour_data.T, level)
22 changes: 19 additions & 3 deletions glue_jupyter/bqplot/image/state.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import numpy as np

from echo import CallbackProperty
from echo import CallbackProperty, delay_callback
from glue.viewers.matplotlib.state import (DeferredDrawCallbackProperty as DDCProperty,
DeferredDrawSelectionCallbackProperty as DDSCProperty)

from glue.viewers.image.state import ImageViewerState, ImageLayerState
from glue.core.state_objects import StateAttributeLimitsHelper
from glue.core.units import find_unit_choices, UnitConverter


class BqplotImageViewerState(ImageViewerState):
@@ -62,8 +63,10 @@ def format_unit(unit):
self.add_callback('c_max', self._update_levels)
self.add_callback('level_mode', self._update_levels)
self.add_callback('levels', self._update_labels)
self.add_callback('attribute', self._update_c_display_unit_choices)
self.add_callback('c_display_unit', self._convert_units_c_limits, echo_old=True)

self._update_c_display_unit_choices()
self._update_levels()

def _update_priority(self, name):
@@ -77,13 +80,26 @@ def _update_priority(self, name):

def _update_levels(self, ignore=None):
if self.level_mode == "Linear":
# TODO: this is exclusive begin/end point, is that a good choise?
self.levels = np.linspace(self.c_min, self.c_max, self.n_levels+2)[1:-1].tolist()
self.levels = np.linspace(self.c_min, self.c_max, self.n_levels).tolist()

def _update_labels(self, ignore=None):
# TODO: we may want to have ways to configure this in the future
self.labels = ["{0:.4g}".format(level) for level in self.levels]

def _update_c_display_unit_choices(self, *args):

if self.layer is None:
BqplotImageLayerState.c_display_unit.set_choices(self, [])
return

component = self.layer.get_component(self.attribute)
if component.units:
c_choices = find_unit_choices([(self.layer, self.attribute, component.units)])
else:
c_choices = ['']
BqplotImageLayerState.c_display_unit.set_choices(self, c_choices)
self.c_display_unit = component.units

def _convert_units_c_limits(self, old_unit, new_unit):

if (
40 changes: 40 additions & 0 deletions glue_jupyter/bqplot/image/tests/test_visual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import numpy as np
from numpy.testing import assert_allclose
import matplotlib.pyplot as plt

from glue_jupyter import jglue
from glue_jupyter.tests.helpers import visual_widget_test


@visual_widget_test
def test_contour_units(
tmp_path,
page_session,
solara_test,
):

x = np.linspace(-7, 7, 88)
y = np.linspace(-6, 6, 69)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X * X + Y * Y) / 4)

app = jglue()
data = app.add_data(data={"x": X, "y": Y, "z": Z})[0]
data.get_component("z").units = 'km'
image = app.imshow(show=False)
image.state.layers[0].attribute = data.id['z']
image.state.layers[0].contour_visible = True
image.state.layers[0].c_min = 0.1
image.state.layers[0].c_max = 0.9
image.state.layers[0].n_levels = 5

assert_allclose(image.state.layers[0].levels, [0.1, 0.3, 0.5, 0.7, 0.9])

image.state.layers[0].c_display_unit = 'm'

assert_allclose(image.state.layers[0].levels, [100, 300, 500, 700, 900])
assert image.state.layers[0].labels == ['100', '300', '500', '700', '900']

figure = image.figure_widget
figure.layout = {"width": "400px", "height": "250px"}
return figure