import numpy as np
from glue.viewers.common.state import ViewerState
from echo import (CallbackProperty, SelectionCallbackProperty)
from glue.core.data_combo_helper import ComponentIDComboHelper
from glue.core.state_objects import StateAttributeLimitsHelper
from glue.utils import nonpartial
__all__ = ['ViewerState3D', 'VolumeViewerState', 'Scatter3DViewerState']
[docs]
class ViewerState3D(ViewerState):
"""
A common state object for all 3D viewers
"""
x_att = SelectionCallbackProperty()
x_min = CallbackProperty(0)
x_max = CallbackProperty(1)
# x_stretch = CallbackProperty(1.)
y_att = SelectionCallbackProperty(default_index=1)
y_min = CallbackProperty(0)
y_max = CallbackProperty(1)
# y_stretch = CallbackProperty(1.)
z_att = SelectionCallbackProperty(default_index=2)
z_min = CallbackProperty(0)
z_max = CallbackProperty(1)
# z_stretch = CallbackProperty(1.)
visible_axes = CallbackProperty(True)
# perspective_view = CallbackProperty(False)
# clip_data = CallbackProperty(False)
native_aspect = CallbackProperty(False)
limits_cache = CallbackProperty()
# def _update_priority(self, name):
# if name == 'layers':
# return 2
# elif name.endswith(('_min', '_max')):
# return 0
# else:
# return 1
def __init__(self, **kwargs):
super(ViewerState3D, self).__init__(**kwargs)
if self.limits_cache is None:
self.limits_cache = {}
self.x_lim_helper = StateAttributeLimitsHelper(self, attribute='x_att',
lower='x_min', upper='x_max',
cache=self.limits_cache)
self.y_lim_helper = StateAttributeLimitsHelper(self, attribute='y_att',
lower='y_min', upper='y_max',
cache=self.limits_cache)
self.z_lim_helper = StateAttributeLimitsHelper(self, attribute='z_att',
lower='z_min', upper='z_max',
cache=self.limits_cache)
# TODO: if limits_cache is re-assigned to a different object, we need to
# update the attribute helpers. However if in future we make limits_cache
# into a smart dictionary that can call callbacks when elements are
# changed then we shouldn't always call this. It'd also be nice to
# avoid this altogether and make it more clean.
self.add_callback('limits_cache', nonpartial(self._update_limits_cache))
def _update_limits_cache(self):
self.x_lim_helper._cache = self.limits_cache
self.x_lim_helper._update_attribute()
self.y_lim_helper._cache = self.limits_cache
self.y_lim_helper._update_attribute()
self.z_lim_helper._cache = self.limits_cache
self.z_lim_helper._update_attribute()
[docs]
@property
def aspect(self):
# note that for ipyvolume, we use axis in the order x, z, y in order to have
# the z axis of glue pointing up
# TODO: this could be cached based on the limits, but is not urgent
aspect = np.array([1, 1, 1], dtype=float)
if self.native_aspect:
aspect[0] = 1.
aspect[1] = (self.z_max - self.z_min) / (self.x_max - self.x_min)
aspect[2] = (self.y_max - self.y_min) / (self.x_max - self.x_min)
aspect /= aspect.max()
return aspect
# def reset(self):
# pass
[docs]
def flip_x(self):
self.x_lim_helper.flip_limits()
[docs]
def flip_y(self):
self.y_lim_helper.flip_limits()
[docs]
def flip_z(self):
self.z_lim_helper.flip_limits()
# @property
# def clip_limits(self):
# return (self.x_min, self.x_max,
# self.y_min, self.y_max,
# self.z_min, self.z_max)
# def set_limits(self, x_min, x_max, y_min, y_max, z_min, z_max):
# with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max'):
# self.x_min = x_min
# self.x_max = x_max
# self.y_min = y_min
# self.y_max = y_max
# self.z_min = z_min
# self.z_max = z_max
[docs]
class VolumeViewerState(ViewerState3D):
def __init__(self, **kwargs):
super(VolumeViewerState, self).__init__()
self.add_callback('layers', self._update_attributes)
self.update_from_dict(kwargs)
def _update_attributes(self, *args):
for layer_state in self.layers:
if getattr(layer_state.layer, 'ndim', None) == 3:
data = layer_state.layer
break
else:
data = None
if data is None:
type(self).x_att.set_choices(self, [])
type(self).y_att.set_choices(self, [])
type(self).z_att.set_choices(self, [])
else:
z_cid, y_cid, x_cid = data.pixel_component_ids
type(self).x_att.set_choices(self, [x_cid])
type(self).y_att.set_choices(self, [y_cid])
type(self).z_att.set_choices(self, [z_cid])
[docs]
class Scatter3DViewerState(ViewerState3D):
def __init__(self, **kwargs):
super(Scatter3DViewerState, self).__init__()
self.x_att_helper = ComponentIDComboHelper(self, 'x_att', categorical=True)
self.y_att_helper = ComponentIDComboHelper(self, 'y_att', categorical=True)
self.z_att_helper = ComponentIDComboHelper(self, 'z_att', categorical=True)
self.add_callback('layers', self._on_layers_change)
self.update_from_dict(kwargs)
def _on_layers_change(self, *args):
layers_data = [layer_state.layer for layer_state in self.layers]
self.x_att_helper.set_multiple_data(layers_data)
self.y_att_helper.set_multiple_data(layers_data)
self.z_att_helper.set_multiple_data(layers_data)