Skip to content

Commit

Permalink
Merge pull request #423 from astrofrog/contour-level-units
Browse files Browse the repository at this point in the history
Implement unit conversion for contour levels
  • Loading branch information
astrofrog committed Apr 17, 2024
2 parents c61d58c + 2035138 commit dcddade
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 9 deletions.
12 changes: 12 additions & 0 deletions glue_jupyter/bqplot/image/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 UnitConverter
from ...link import link

from bqplot_image_gl import Contour
Expand Down Expand Up @@ -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.attribute_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)
Expand Down
38 changes: 35 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 UnitConverter


class BqplotImageViewerState(ImageViewerState):
Expand All @@ -31,6 +32,7 @@ class BqplotImageLayerState(ImageLayerState):
contour_visible = CallbackProperty(False, 'whether to show the image as contours')

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

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

BqplotImageLayerState.level_mode.set_choices(self, ['Linear', 'Custom'])
Expand All @@ -53,6 +55,8 @@ def __init__(self, *args, **kwargs):
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_display_unit', self._convert_units_c_limits, echo_old=True)

self._update_levels()

def _update_priority(self, name):
Expand All @@ -66,9 +70,37 @@ 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 _convert_units_c_limits(self, old_unit, new_unit):

if (
getattr(self, '_previous_attribute', None) is self.attribute and
old_unit != new_unit and
self.layer is not None
):

limits = np.hstack([self.c_min, self.c_max, self.levels])

converter = UnitConverter()

limits_native = converter.to_native(self.layer,
self.attribute, limits,
old_unit)

limits_new = converter.to_unit(self.layer,
self.attribute, limits_native,
new_unit)

with delay_callback(self, 'c_min', 'c_max', 'levels'):
self.c_min, self.c_max = sorted(limits_new[:2])
self.levels = tuple(limits_new[2:])

# Make sure that we keep track of what attribute the limits
# are for - if the attribute changes, we should not try and
# update the limits.
self._previous_attribute = self.attribute
8 changes: 4 additions & 4 deletions glue_jupyter/bqplot/image/tests/test_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ def test_contour_levels(app, data_image, data_volume):
layer.state.c_min = 0
layer.state.c_max = 10
layer.state.n_levels = 3
assert layer.state.levels == [2.5, 5, 7.5]
assert layer.state.levels == [0, 5, 10]
# since we start invisible, we don't compute the contour lines
assert len(layer.contour_artist.contour_lines) == 0
# make the visible, so we trigger a compute
layer.state.contour_visible = True
assert len(layer.contour_artist.contour_lines) == 3
layer.state.level_mode = 'Custom'
layer.state.n_levels = 1
assert layer.state.levels == [2.5, 5, 7.5]
assert layer.state.levels == [0, 5, 10]
layer.state.level_mode = 'Linear'
assert layer.state.levels == [5]
assert layer.state.levels == [0]
assert len(layer.contour_artist.contour_lines) == 1

# test the visual attributes
Expand Down Expand Up @@ -81,7 +81,7 @@ def test_contour_state(app, data_image):
{'level_mode': 'Linear', 'levels': [2, 3]}
)
# Without priority of levels, this gets set to [2, 3]
assert layer.state.levels == [2.5, 5, 7.5]
assert layer.state.levels == [0, 5, 10]


def test_add_markers_zoom(app, data_image, data_volume, dataxyz):
Expand Down
39 changes: 39 additions & 0 deletions glue_jupyter/bqplot/image/tests/test_visual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import numpy as np
from numpy.testing import assert_allclose

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].attribute_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
1 change: 1 addition & 0 deletions glue_jupyter/tests/images/py311-test-visual.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"glue_jupyter.bqplot.image.tests.test_visual.test_contour_units[chromium]": "fa4f68c5c62e1437c1666c656ba02376396f6c75b6f7956f712c760569a2045b",
"glue_jupyter.bqplot.scatter.tests.test_visual.test_visual_scatter2d[chromium]": "fbdd9fe2649a0d72813c03e77af6233909df64207cb834f28da479f50b9e7a1d",
"glue_jupyter.bqplot.scatter.tests.test_visual.test_visual_scatter2d_density[chromium]": "d843a816a91e37cb0212c7caae913d7563f6c2eb42b49fa18345a5952e093b2f"
}
3 changes: 1 addition & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ python_requires = >=3.8
setup_requires =
setuptools_scm
install_requires =
glue-core>=1.17.1
glue-core>=1.20.0
glue-vispy-viewers>=1.0
notebook>=4.0
ipympl>=0.3.0
Expand All @@ -33,7 +33,6 @@ test =
pytest
pytest-cov
nbconvert>=6.4.5
glue-core!=1.2.4; python_version == '3.10'
visualtest =
playwright
pytest-playwright
Expand Down

0 comments on commit dcddade

Please sign in to comment.