def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.x_lim_helper = StateAttributeLimitsHelper(self, 'x_att', lower='x_min', upper='x_max') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, world_coord=True, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} # NOTE: we don't need to use StateAttributeLimitsHelper here because # we can simply call reset_limits below when x/y attributes change. # Using StateAttributeLimitsHelper makes things a lot slower. self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) aspect_display = {'equal': 'Square Pixels', 'auto': 'Automatic'} ImageViewerState.aspect.set_choices(self, ['equal', 'auto']) ImageViewerState.aspect.set_display_func(self, aspect_display.get) ImageViewerState.color_mode.set_choices(self, ['Colormaps', 'One color per layer']) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} self.x_lim_helper = StateAttributeLimitsHelper( self, attribute='x_att', lower='x_min', upper='x_max', limits_cache=self.limits_cache) self.y_lim_helper = StateAttributeLimitsHelper( self, attribute='y_att', lower='y_min', upper='y_max', limits_cache=self.limits_cache) self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False, visible=False, world_coord=True) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False, visible=False, world_coord=True) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(Vispy3DVolumeViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) Vispy3DVolumeViewerState.resolution.set_choices( self, [2**i for i in range(4, 12)]) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} # NOTE: we don't need to use StateAttributeLimitsHelper here because # we can simply call reset_limits below when x/y attributes change. # Using StateAttributeLimitsHelper makes things a lot slower. self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False, world_coord=True) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False, world_coord=True) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('x_att', self._update_att) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs)
def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} self.x_lim_helper = StateAttributeLimitsHelper(self, attribute='x_att', lower='x_min', upper='x_max', limits_cache=self.limits_cache) self.y_lim_helper = StateAttributeLimitsHelper(self, attribute='y_att', lower='y_min', upper='y_max', limits_cache=self.limits_cache) self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False, world_coord=True) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False, world_coord=True) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) self.update_from_dict(kwargs)
class Vispy3DVolumeViewerState(Vispy3DViewerState): downsample = CallbackProperty(True) resolution = SelectionCallbackProperty(4) reference_data = SelectionCallbackProperty( docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') def __init__(self, **kwargs): super(Vispy3DVolumeViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) Vispy3DVolumeViewerState.resolution.set_choices( self, [2**i for i in range(4, 12)]) self.update_from_dict(kwargs) def _first_3d_data(self): for layer_state in self.layers: if getattr(layer_state.layer, 'ndim', None) == 3: return layer_state.layer def _layers_changed(self, *args): self._update_combo_ref_data() self._set_reference_data() self._update_attributes() def _update_combo_ref_data(self, *args): self.ref_data_helper.set_multiple_data(self.layers_data) def _set_reference_data(self, *args): if self.reference_data is None: for layer in self.layers: if isinstance(layer.layer, BaseData): self.reference_data = layer.layer return def _update_attributes(self, *args): data = self._first_3d_data() 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]) @property def clip_limits_relative(self): data = self._first_3d_data() if data is None: return [0., 1., 0., 1., 0., 1.] else: nz, ny, nx = data.shape return (self.x_min / nx, self.x_max / nx, self.y_min / ny, self.y_max / ny, self.z_min / nz, self.z_max / nz)
class ImageViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for an image viewer. """ x_att = DDCProperty( docstring='The component ID giving the pixel component ' 'shown on the x axis') y_att = DDCProperty( docstring='The component ID giving the pixel component ' 'shown on the y axis') x_att_world = DDSCProperty( docstring='The component ID giving the world component ' 'shown on the x axis', default_index=-1) y_att_world = DDSCProperty( docstring='The component ID giving the world component ' 'shown on the y axis', default_index=-2) aspect = DDSCProperty( 0, docstring='Whether to enforce square pixels (``equal``) ' 'or fill the axes (``auto``)') reference_data = DDSCProperty( docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') slices = DDCProperty(docstring='The current slice along all dimensions') color_mode = DDSCProperty(0, docstring='Whether each layer can have ' 'its own colormap (``Colormaps``) or ' 'whether each layer is assigned ' 'a single color (``One color per layer``)') dpi = DDCProperty( 72, docstring= 'The resolution (in dots per inch) of density maps, if present') def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} # NOTE: we don't need to use StateAttributeLimitsHelper here because # we can simply call reset_limits below when x/y attributes change. # Using StateAttributeLimitsHelper makes things a lot slower. self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) aspect_display = {'equal': 'Square Pixels', 'auto': 'Automatic'} ImageViewerState.aspect.set_choices(self, ['equal', 'auto']) ImageViewerState.aspect.set_display_func(self, aspect_display.get) ImageViewerState.color_mode.set_choices( self, ['Colormaps', 'One color per layer']) self.update_from_dict(kwargs) def reset_limits(self): if self.reference_data is None or self.x_att is None or self.y_att is None: return nx = self.reference_data.shape[self.x_att.axis] ny = self.reference_data.shape[self.y_att.axis] with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self.x_min = -0.5 self.x_max = nx - 0.5 self.y_min = -0.5 self.y_max = ny - 0.5 # We need to adjust the limits in here to avoid triggering all # the update events then changing the limits again. self._adjust_limits_aspect() @property def _display_world(self): return isinstance(getattr(self.reference_data, 'coords', None), Coordinates) def _reference_data_changed(self, *args): # This signal can get emitted if just the choices but not the actual # reference data change, so we check here that the reference data has # actually changed if self.reference_data is not getattr(self, '_last_reference_data', None): self._last_reference_data = self.reference_data with delay_callback(self, 'x_att_world', 'y_att_world', 'slices'): if self._display_world: self.xw_att_helper.pixel_coord = False self.yw_att_helper.pixel_coord = False self.xw_att_helper.world_coord = True self.yw_att_helper.world_coord = True else: self.xw_att_helper.pixel_coord = True self.yw_att_helper.pixel_coord = True self.xw_att_helper.world_coord = False self.yw_att_helper.world_coord = False self._update_combo_att() self._set_default_slices() def _layers_changed(self, *args): # The layers callback gets executed if anything in the layers changes, # but we only care about whether the actual set of 'layer' attributes # for all layers change. layers_data = self.layers_data layers_data_cache = getattr(self, '_layers_data_cache', []) if layers_data == layers_data_cache: return self._update_combo_ref_data() self._set_reference_data() self._update_syncing() self._layers_data_cache = layers_data def _update_syncing(self): # If there are multiple layers for a given dataset, we disable the # syncing by default. layer_state_by_data = defaultdict(list) for layer_state in self.layers: if isinstance(layer_state.layer, BaseData): layer_state_by_data[layer_state.layer].append(layer_state) for data, layer_states in layer_state_by_data.items(): if len(layer_states) > 1: for layer_state in layer_states: # Scatter layers don't have global_sync so we need to be # careful here and make sure we return a default value if getattr(layer_state, 'global_sync', False): layer_state.global_sync = False def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def _update_combo_att(self): with delay_callback(self, 'x_att_world', 'y_att_world'): if self.reference_data is None: self.xw_att_helper.set_multiple_data([]) self.yw_att_helper.set_multiple_data([]) else: self.xw_att_helper.set_multiple_data([self.reference_data]) self.yw_att_helper.set_multiple_data([self.reference_data]) def _update_priority(self, name): if name == 'layers': return 3 elif name == 'reference_data': return 2 elif name.endswith(('_min', '_max')): return 0 else: return 1 @defer_draw def _update_att(self, *args): # Need to delay the callbacks here to make sure that we get a chance to # update both x_att and y_att otherwise could end up triggering image # slicing with two pixel components that are the same. with delay_callback(self, 'x_att', 'y_att'): if self.x_att_world is not None: if self._display_world: index = self.reference_data.world_component_ids.index( self.x_att_world) self.x_att = self.reference_data.pixel_component_ids[index] else: self.x_att = self.x_att_world if self.y_att_world is not None: if self._display_world: index = self.reference_data.world_component_ids.index( self.y_att_world) self.y_att = self.reference_data.pixel_component_ids[index] else: self.y_att = self.y_att_world @defer_draw def _on_xatt_change(self, *args): if self.x_att is not None: if self._display_world: self.x_att_world = self.reference_data.world_component_ids[ self.x_att.axis] else: self.x_att_world = self.x_att @defer_draw def _on_yatt_change(self, *args): if self.y_att is not None: if self._display_world: self.y_att_world = self.reference_data.world_component_ids[ self.y_att.axis] else: self.y_att_world = self.y_att @defer_draw def _on_xatt_world_change(self, *args): if self.x_att_world is not None and self.x_att_world == self.y_att_world: world_ids = self.reference_data.world_component_ids if self.x_att_world == world_ids[-1]: self.y_att_world = world_ids[-2] else: self.y_att_world = world_ids[-1] @defer_draw def _on_yatt_world_change(self, *args): if self.y_att_world is not None and self.y_att_world == self.x_att_world: world_ids = self.reference_data.world_component_ids if self.y_att_world == world_ids[-1]: self.x_att_world = world_ids[-2] else: self.x_att_world = world_ids[-1] def _set_reference_data(self): if self.reference_data is None: for layer in self.layers: if isinstance(layer.layer, BaseData): self.reference_data = layer.layer return def _set_default_slices(self): # Need to make sure this gets called immediately when reference_data is changed if self.reference_data is None: self.slices = () else: self.slices = (0, ) * self.reference_data.ndim @property def numpy_slice_aggregation_transpose(self): """ Returns slicing information usable by Numpy. This returns two objects: the first is an object that can be used to slice Numpy arrays and return a 2D array, and the second object is a boolean indicating whether to transpose the result. """ if self.reference_data is None: return None slices = [] agg_func = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis or i == self.y_att.axis: slices.append(slice(None)) agg_func.append(None) else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].slice) agg_func.append(self.slices[i].function) else: slices.append(self.slices[i]) transpose = self.y_att.axis > self.x_att.axis return slices, agg_func, transpose @property def wcsaxes_slice(self): """ Returns slicing information usable by WCSAxes. This returns an iterable of slices, and including ``'x'`` and ``'y'`` for the dimensions along which we are not slicing. """ if self.reference_data is None: return None slices = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis: slices.append('x') elif i == self.y_att.axis: slices.append('y') else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].center) else: slices.append(self.slices[i]) return slices[::-1] def flip_x(self): """ Flip the x_min/x_max limits. """ with delay_callback(self, 'x_min', 'x_max'): self.x_min, self.x_max = self.x_max, self.x_min def flip_y(self): """ Flip the y_min/y_max limits. """ with delay_callback(self, 'y_min', 'y_max'): self.y_min, self.y_max = self.y_max, self.y_min
class ProfileViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for a Profile viewer. """ reference_data = DDSCProperty( docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') x_att = DDSCProperty(docstring='The data component to use for the x-axis ' 'of the profile (should be a pixel component)') function = DDSCProperty( docstring='The function to use for collapsing data') normalize = DDCProperty(False, docstring='Whether to normalize all profiles ' 'to the [0:1] range') # TODO: add function to use def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.x_lim_helper = StateAttributeLimitsHelper(self, 'x_att', lower='x_min', upper='x_max') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, world_coord=True, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs) def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def reset_limits(self): with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self.x_lim_helper.percentile = 100 self.x_lim_helper.update_values(force=True) self._reset_y_limits() def _reset_y_limits(self, *event): if self.normalize: self.y_min = -0.1 self.y_max = +1.1 def _update_priority(self, name): if name == 'layers': return 2 elif name.endswith(('_min', '_max')): return 0 else: return 1 def flip_x(self): """ Flip the x_min/x_max limits. """ self.x_lim_helper.flip_limits() @defer_draw def _layers_changed(self, *args): self._update_combo_ref_data() @defer_draw def _reference_data_changed(self, *args): if self.reference_data is None: self.x_att_helper.set_multiple_data([]) else: self.x_att_helper.set_multiple_data([self.reference_data]) if type(self.reference_data.coords) == Coordinates: self.x_att = self.reference_data.pixel_component_ids[0] else: self.x_att = self.reference_data.world_component_ids[0]
class ProfileViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for a Profile viewer. """ x_att_pixel = DDCProperty(docstring='The component ID giving the pixel component ' 'shown on the x axis') x_att = DDSCProperty(docstring='The component ID giving the pixel or world component ' 'shown on the x axis') reference_data = DDSCProperty(docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') function = DDSCProperty(docstring='The function to use for collapsing data') normalize = DDCProperty(False, docstring='Whether to normalize all profiles ' 'to the [0:1] range') # TODO: add function to use def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed, echo_old=True) self.add_callback('x_att', self._update_att) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, datetime=False, categorical=False, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs) def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def reset_limits(self): with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self._reset_x_limits() self._reset_y_limits() @property def _display_world(self): return getattr(self.reference_data, 'coords', None) is not None @defer_draw def _update_att(self, *args): if self.x_att is not None: if self._display_world: if self.x_att in self.reference_data.pixel_component_ids: self.x_att_pixel = self.x_att else: index = self.reference_data.world_component_ids.index(self.x_att) self.x_att_pixel = self.reference_data.pixel_component_ids[index] else: self.x_att_pixel = self.x_att self._reset_x_limits() def _reset_x_limits(self, *event): # NOTE: we don't use AttributeLimitsHelper because we need to avoid # trying to get the minimum of *all* the world coordinates in the # dataset. Instead, we use the same approach as in the layer state below # and in the case of world coordinates we use online the spine of the # data. if self.reference_data is None or self.x_att_pixel is None: return data = self.reference_data if self.x_att in data.pixel_component_ids: x_min, x_max = -0.5, data.shape[self.x_att.axis] - 0.5 else: axis = data.world_component_ids.index(self.x_att) axis_view = [0] * data.ndim axis_view[axis] = slice(None) axis_values = data[self.x_att, tuple(axis_view)] x_min, x_max = np.nanmin(axis_values), np.nanmax(axis_values) with delay_callback(self, 'x_min', 'x_max'): self.x_min = x_min self.x_max = x_max def _reset_y_limits(self, *event): if self.normalize: with delay_callback(self, 'y_min', 'y_max'): self.y_min = -0.1 self.y_max = +1.1 else: y_min, y_max = np.inf, -np.inf for layer in self.layers: try: profile = layer.profile except Exception: # e.g. incompatible subset continue if profile is not None: x, y = profile if len(y) > 0: y_min = min(y_min, np.nanmin(y)) y_max = max(y_max, np.nanmax(y)) with delay_callback(self, 'y_min', 'y_max'): if y_max > y_min: self.y_min = y_min self.y_max = y_max else: self.y_min = 0 self.y_max = 1 def flip_x(self): """ Flip the x_min/x_max limits. """ with delay_callback(self, 'x_min', 'x_max'): self.x_min, self.x_max = self.x_max, self.x_min @defer_draw def _layers_changed(self, *args): self._update_combo_ref_data() @defer_draw def _reference_data_changed(self, before=None, after=None): # A callback event for reference_data is triggered if the choices change # but the actual selection doesn't - so we avoid resetting the WCS in # this case. if before is after: return for layer in self.layers: layer.reset_cache() # This signal can get emitted if just the choices but not the actual # reference data change, so we check here that the reference data has # actually changed if self.reference_data is not getattr(self, '_last_reference_data', None): self._last_reference_data = self.reference_data with delay_callback(self, 'x_att'): if self.reference_data is None: self.x_att_helper.set_multiple_data([]) else: self.x_att_helper.set_multiple_data([self.reference_data]) if self._display_world: self.x_att_helper.world_coord = True self.x_att = self.reference_data.world_component_ids[0] else: self.x_att_helper.world_coord = False self.x_att = self.reference_data.pixel_component_ids[0] self._update_att() self.reset_limits() def _update_priority(self, name): if name == 'layers': return 2 elif name == 'reference_data': return 1.5 elif name.endswith(('_min', '_max')): return 0 else: return 1
class ImageViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for an image viewer. """ x_att = DDCProperty( docstring='The component ID giving the pixel component ' 'shown on the x axis') y_att = DDCProperty( docstring='The component ID giving the pixel component ' 'shown on the y axis') x_att_world = DDSCProperty( docstring='The component ID giving the world component ' 'shown on the x axis', default_index=-1) y_att_world = DDSCProperty( docstring='The component ID giving the world component ' 'shown on the y axis', default_index=-2) aspect = DDCProperty( 'equal', docstring='Whether to enforce square pixels (``equal``) ' 'or fill the axes (``auto``)') reference_data = DDSCProperty( docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') slices = DDCProperty(docstring='The current slice along all dimensions') color_mode = DDCProperty('Colormaps', docstring='Whether each layer can have ' 'its own colormap (``Colormaps``) or ' 'whether each layer is assigned ' 'a single color (``One color per layer``)') def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} self.x_lim_helper = StateAttributeLimitsHelper( self, attribute='x_att', lower='x_min', upper='x_max', limits_cache=self.limits_cache) self.y_lim_helper = StateAttributeLimitsHelper( self, attribute='y_att', lower='y_min', upper='y_max', limits_cache=self.limits_cache) self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False, visible=False, world_coord=True) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False, visible=False, world_coord=True) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) self.update_from_dict(kwargs) def _reference_data_changed(self, *args): with delay_callback(self, 'x_att_world', 'y_att_world', 'slices'): self._update_combo_att() self._set_default_slices() def _layers_changed(self, *args): # The layers callback gets executed if anything in the layers changes, # but we only care about whether the actual set of 'layer' attributes # for all layers change. layers_data = self.layers_data layers_data_cache = getattr(self, '_layers_data_cache', []) if layers_data == layers_data_cache: return self._update_combo_ref_data() self._set_reference_data() self._update_syncing() self._layers_data_cache = layers_data def _update_syncing(self): # If there are multiple layers for a given dataset, we disable the # syncing by default. layer_state_by_data = defaultdict(list) for layer_state in self.layers: if isinstance(layer_state.layer, Data): layer_state_by_data[layer_state.layer].append(layer_state) for data, layer_states in layer_state_by_data.items(): if len(layer_states) > 1: for layer_state in layer_states: if layer_state.global_sync: layer_state.global_sync = False def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def _update_combo_att(self): with delay_callback(self, 'x_att_world', 'y_att_world'): if self.reference_data is None: self.xw_att_helper.set_multiple_data([]) self.yw_att_helper.set_multiple_data([]) else: self.xw_att_helper.set_multiple_data([self.reference_data]) self.yw_att_helper.set_multiple_data([self.reference_data]) def _update_priority(self, name): if name == 'layers': return 3 elif name == 'reference_data': return 2 elif name.endswith(('_min', '_max')): return 0 else: return 1 @defer_draw def _update_att(self, *args): # Need to delay the callbacks here to make sure that we get a chance to # update both x_att and y_att otherwise could end up triggering image # slicing with two pixel components that are the same. with delay_callback(self, 'x_att', 'y_att'): if self.x_att_world is not None: index = self.reference_data.world_component_ids.index( self.x_att_world) self.x_att = self.reference_data.pixel_component_ids[index] if self.y_att_world is not None: index = self.reference_data.world_component_ids.index( self.y_att_world) self.y_att = self.reference_data.pixel_component_ids[index] @defer_draw def _on_xatt_change(self, *args): if self.x_att is not None: self.x_att_world = self.reference_data.world_component_ids[ self.x_att.axis] @defer_draw def _on_yatt_change(self, *args): if self.y_att is not None: self.y_att_world = self.reference_data.world_component_ids[ self.y_att.axis] @defer_draw def _on_xatt_world_change(self, *args): if self.x_att_world is not None and self.x_att_world == self.y_att_world: world_ids = self.reference_data.world_component_ids if self.x_att_world == world_ids[-1]: self.y_att_world = world_ids[-2] else: self.y_att_world = world_ids[-1] @defer_draw def _on_yatt_world_change(self, *args): if self.y_att_world is not None and self.y_att_world == self.x_att_world: world_ids = self.reference_data.world_component_ids if self.y_att_world == world_ids[-1]: self.x_att_world = world_ids[-2] else: self.x_att_world = world_ids[-1] def _set_reference_data(self): if self.reference_data is None: for layer in self.layers: if isinstance(layer.layer, Data): self.reference_data = layer.layer return def _set_default_slices(self): # Need to make sure this gets called immediately when reference_data is changed if self.reference_data is None: self.slices = () else: self.slices = (0, ) * self.reference_data.ndim @property def numpy_slice_aggregation_transpose(self): """ Returns slicing information usable by Numpy. This returns two objects: the first is an object that can be used to slice Numpy arrays and return a 2D array, and the second object is a boolean indicating whether to transpose the result. """ if self.reference_data is None: return None slices = [] agg_func = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis or i == self.y_att.axis: slices.append(slice(None)) agg_func.append(None) else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].slice) agg_func.append(self.slices[i].function) else: slices.append(self.slices[i]) transpose = self.y_att.axis > self.x_att.axis return slices, agg_func, transpose @property def wcsaxes_slice(self): """ Returns slicing information usable by WCSAxes. This returns an iterable of slices, and including ``'x'`` and ``'y'`` for the dimensions along which we are not slicing. """ if self.reference_data is None: return None slices = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis: slices.append('x') elif i == self.y_att.axis: slices.append('y') else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].center) else: slices.append(self.slices[i]) return slices[::-1] def flip_x(self): """ Flip the x_min/x_max limits. """ self.x_lim_helper.flip_limits() def flip_y(self): """ Flip the y_min/y_max limits. """ self.y_lim_helper.flip_limits()
class ProfileViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for a Profile viewer. """ x_att_pixel = DDCProperty( docstring='The component ID giving the pixel component ' 'shown on the x axis') x_att = DDSCProperty( docstring='The component ID giving the pixel or world component ' 'shown on the x axis') reference_data = DDSCProperty( docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') function = DDSCProperty( docstring='The function to use for collapsing data') normalize = DDCProperty(False, docstring='Whether to normalize all profiles ' 'to the [0:1] range') # TODO: add function to use def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('x_att', self._update_att) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs) def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def reset_limits(self): with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self._reset_x_limits() self._reset_y_limits() @property def _display_world(self): return (isinstance(getattr(self.reference_data, 'coords', None), Coordinates) and type(self.reference_data.coords) != Coordinates) @defer_draw def _update_att(self, *args): if self.x_att is not None: if self._display_world: if self.x_att in self.reference_data.pixel_component_ids: self.x_att_pixel = self.x_att else: index = self.reference_data.world_component_ids.index( self.x_att) self.x_att_pixel = self.reference_data.pixel_component_ids[ index] else: self.x_att_pixel = self.x_att self._reset_x_limits() def _reset_x_limits(self, *event): # NOTE: we don't use AttributeLimitsHelper because we need to avoid # trying to get the minimum of *all* the world coordinates in the # dataset. Instead, we use the same approach as in the layer state below # and in the case of world coordinates we use online the spine of the # data. if self.reference_data is None or self.x_att_pixel is None: return data = self.reference_data if self.x_att in data.pixel_component_ids: x_min, x_max = -0.5, data.shape[self.x_att.axis] - 0.5 else: axis = data.world_component_ids.index(self.x_att) axis_view = [0] * data.ndim axis_view[axis] = slice(None) axis_values = data[self.x_att, tuple(axis_view)] x_min, x_max = np.nanmin(axis_values), np.nanmax(axis_values) with delay_callback(self, 'x_min', 'x_max'): self.x_min = x_min self.x_max = x_max def _reset_y_limits(self, *event): if self.normalize: with delay_callback(self, 'y_min', 'y_max'): self.y_min = -0.1 self.y_max = +1.1 def flip_x(self): """ Flip the x_min/x_max limits. """ with delay_callback(self, 'x_min', 'x_max'): self.x_min, self.x_max = self.x_max, self.x_min @defer_draw def _layers_changed(self, *args): self._update_combo_ref_data() @defer_draw def _reference_data_changed(self, *args): # This signal can get emitted if just the choices but not the actual # reference data change, so we check here that the reference data has # actually changed if self.reference_data is not getattr(self, '_last_reference_data', None): self._last_reference_data = self.reference_data if self.reference_data is None: self.x_att_helper.set_multiple_data([]) else: self.x_att_helper.set_multiple_data([self.reference_data]) if self._display_world: self.x_att_helper.world_coord = True self.x_att = self.reference_data.world_component_ids[0] else: self.x_att_helper.world_coord = False self.x_att = self.reference_data.pixel_component_ids[0]
class ProfileViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for a Profile viewer. """ x_att_pixel = DDCProperty(docstring='The component ID giving the pixel component ' 'shown on the x axis') x_att = DDSCProperty(docstring='The component ID giving the pixel or world component ' 'shown on the x axis') reference_data = DDSCProperty(docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') function = DDSCProperty(docstring='The function to use for collapsing data') normalize = DDCProperty(False, docstring='Whether to normalize all profiles ' 'to the [0:1] range') # TODO: add function to use def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('x_att', self._update_att) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs) def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def reset_limits(self): with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self._reset_x_limits() self._reset_y_limits() @property def _display_world(self): return (isinstance(getattr(self.reference_data, 'coords', None), Coordinates) and type(self.reference_data.coords) != Coordinates) @defer_draw def _update_att(self, *args): if self.x_att is not None: if self._display_world: if self.x_att in self.reference_data.pixel_component_ids: self.x_att_pixel = self.x_att else: index = self.reference_data.world_component_ids.index(self.x_att) self.x_att_pixel = self.reference_data.pixel_component_ids[index] else: self.x_att_pixel = self.x_att self._reset_x_limits() def _reset_x_limits(self, *event): # NOTE: we don't use AttributeLimitsHelper because we need to avoid # trying to get the minimum of *all* the world coordinates in the # dataset. Instead, we use the same approach as in the layer state below # and in the case of world coordinates we use online the spine of the # data. if self.reference_data is None or self.x_att_pixel is None: return data = self.reference_data if self.x_att in data.pixel_component_ids: x_min, x_max = -0.5, data.shape[self.x_att.axis] - 0.5 else: axis = data.world_component_ids.index(self.x_att) axis_view = [0] * data.ndim axis_view[axis] = slice(None) axis_values = data[self.x_att, tuple(axis_view)] x_min, x_max = np.nanmin(axis_values), np.nanmax(axis_values) with delay_callback(self, 'x_min', 'x_max'): self.x_min = x_min self.x_max = x_max def _reset_y_limits(self, *event): if self.normalize: with delay_callback(self, 'y_min', 'y_max'): self.y_min = -0.1 self.y_max = +1.1 def flip_x(self): """ Flip the x_min/x_max limits. """ with delay_callback(self, 'x_min', 'x_max'): self.x_min, self.x_max = self.x_max, self.x_min @defer_draw def _layers_changed(self, *args): self._update_combo_ref_data() @defer_draw def _reference_data_changed(self, *args): # This signal can get emitted if just the choices but not the actual # reference data change, so we check here that the reference data has # actually changed if self.reference_data is not getattr(self, '_last_reference_data', None): self._last_reference_data = self.reference_data if self.reference_data is None: self.x_att_helper.set_multiple_data([]) else: self.x_att_helper.set_multiple_data([self.reference_data]) if self._display_world: self.x_att_helper.world_coord = True self.x_att = self.reference_data.world_component_ids[0] else: self.x_att_helper.world_coord = False self.x_att = self.reference_data.pixel_component_ids[0]
class ProfileViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for a Profile viewer. """ reference_data = DDSCProperty(docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') x_att = DDSCProperty(docstring='The data component to use for the x-axis ' 'of the profile (should be a pixel component)') function = DDSCProperty(docstring='The function to use for collapsing data') normalize = DDCProperty(False, docstring='Whether to normalize all profiles ' 'to the [0:1] range') # TODO: add function to use def __init__(self, **kwargs): super(ProfileViewerState, self).__init__() self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.x_lim_helper = StateAttributeLimitsHelper(self, 'x_att', lower='x_min', upper='x_max') self.add_callback('layers', self._layers_changed) self.add_callback('reference_data', self._reference_data_changed) self.add_callback('normalize', self._reset_y_limits) self.x_att_helper = ComponentIDComboHelper(self, 'x_att', numeric=False, categorical=False, world_coord=True, pixel_coord=True) ProfileViewerState.function.set_choices(self, list(FUNCTIONS)) ProfileViewerState.function.set_display_func(self, FUNCTIONS.get) self.update_from_dict(kwargs) def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def reset_limits(self): with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self.x_lim_helper.percentile = 100 self.x_lim_helper.update_values(force=True) self._reset_y_limits() def _reset_y_limits(self, *event): if self.normalize: self.y_min = -0.1 self.y_max = +1.1 def flip_x(self): """ Flip the x_min/x_max limits. """ self.x_lim_helper.flip_limits() @defer_draw def _layers_changed(self, *args): self._update_combo_ref_data() @defer_draw def _reference_data_changed(self, *args): if self.reference_data is None: self.x_att_helper.set_multiple_data([]) else: self.x_att_helper.set_multiple_data([self.reference_data]) if type(self.reference_data.coords) == Coordinates: self.x_att = self.reference_data.pixel_component_ids[0] else: self.x_att = self.reference_data.world_component_ids[0]
class ImageViewerState(MatplotlibDataViewerState): """ A state class that includes all the attributes for an image viewer. """ x_att = DDCProperty(docstring='The component ID giving the pixel component ' 'shown on the x axis') y_att = DDCProperty(docstring='The component ID giving the pixel component ' 'shown on the y axis') x_att_world = DDSCProperty(docstring='The component ID giving the world component ' 'shown on the x axis', default_index=-1) y_att_world = DDSCProperty(docstring='The component ID giving the world component ' 'shown on the y axis', default_index=-2) aspect = DDCProperty('equal', docstring='Whether to enforce square pixels (``equal``) ' 'or fill the axes (``auto``)') reference_data = DDSCProperty(docstring='The dataset that is used to define the ' 'available pixel/world components, and ' 'which defines the coordinate frame in ' 'which the images are shown') slices = DDCProperty(docstring='The current slice along all dimensions') color_mode = DDCProperty('Colormaps', docstring='Whether each layer can have ' 'its own colormap (``Colormaps``) or ' 'whether each layer is assigned ' 'a single color (``One color per layer``)') dpi = DDCProperty(72, docstring='The resolution (in dots per inch) of density maps, if present') def __init__(self, **kwargs): super(ImageViewerState, self).__init__() self.limits_cache = {} self.x_lim_helper = StateAttributeLimitsHelper(self, attribute='x_att', lower='x_min', upper='x_max', limits_cache=self.limits_cache) self.y_lim_helper = StateAttributeLimitsHelper(self, attribute='y_att', lower='y_min', upper='y_max', limits_cache=self.limits_cache) self.ref_data_helper = ManualDataComboHelper(self, 'reference_data') self.xw_att_helper = ComponentIDComboHelper(self, 'x_att_world', numeric=False, categorical=False, world_coord=True) self.yw_att_helper = ComponentIDComboHelper(self, 'y_att_world', numeric=False, categorical=False, world_coord=True) self.add_callback('reference_data', self._reference_data_changed, priority=1000) self.add_callback('layers', self._layers_changed, priority=1000) self.add_callback('x_att', self._on_xatt_change, priority=500) self.add_callback('y_att', self._on_yatt_change, priority=500) self.add_callback('x_att_world', self._update_att, priority=500) self.add_callback('y_att_world', self._update_att, priority=500) self.add_callback('x_att_world', self._on_xatt_world_change, priority=1000) self.add_callback('y_att_world', self._on_yatt_world_change, priority=1000) self.update_from_dict(kwargs) def reset_limits(self): if self.reference_data is None or self.x_att is None or self.y_att is None: return nx = self.reference_data.shape[self.x_att.axis] ny = self.reference_data.shape[self.y_att.axis] with delay_callback(self, 'x_min', 'x_max', 'y_min', 'y_max'): self.x_min = -0.5 self.x_max = nx - 0.5 self.y_min = -0.5 self.y_max = ny - 0.5 def _reference_data_changed(self, *args): with delay_callback(self, 'x_att_world', 'y_att_world', 'slices'): self._update_combo_att() self._set_default_slices() def _layers_changed(self, *args): # The layers callback gets executed if anything in the layers changes, # but we only care about whether the actual set of 'layer' attributes # for all layers change. layers_data = self.layers_data layers_data_cache = getattr(self, '_layers_data_cache', []) if layers_data == layers_data_cache: return self._update_combo_ref_data() self._set_reference_data() self._update_syncing() self._layers_data_cache = layers_data def _update_syncing(self): # If there are multiple layers for a given dataset, we disable the # syncing by default. layer_state_by_data = defaultdict(list) for layer_state in self.layers: if isinstance(layer_state.layer, Data): layer_state_by_data[layer_state.layer].append(layer_state) for data, layer_states in layer_state_by_data.items(): if len(layer_states) > 1: for layer_state in layer_states: # Scatter layers don't have global_sync so we need to be # careful here and make sure we return a default value if getattr(layer_state, 'global_sync', False): layer_state.global_sync = False def _update_combo_ref_data(self): self.ref_data_helper.set_multiple_data(self.layers_data) def _update_combo_att(self): with delay_callback(self, 'x_att_world', 'y_att_world'): if self.reference_data is None: self.xw_att_helper.set_multiple_data([]) self.yw_att_helper.set_multiple_data([]) else: self.xw_att_helper.set_multiple_data([self.reference_data]) self.yw_att_helper.set_multiple_data([self.reference_data]) def _update_priority(self, name): if name == 'layers': return 3 elif name == 'reference_data': return 2 elif name.endswith(('_min', '_max')): return 0 else: return 1 @defer_draw def _update_att(self, *args): # Need to delay the callbacks here to make sure that we get a chance to # update both x_att and y_att otherwise could end up triggering image # slicing with two pixel components that are the same. with delay_callback(self, 'x_att', 'y_att'): if self.x_att_world is not None: index = self.reference_data.world_component_ids.index(self.x_att_world) self.x_att = self.reference_data.pixel_component_ids[index] if self.y_att_world is not None: index = self.reference_data.world_component_ids.index(self.y_att_world) self.y_att = self.reference_data.pixel_component_ids[index] @defer_draw def _on_xatt_change(self, *args): if self.x_att is not None: self.x_att_world = self.reference_data.world_component_ids[self.x_att.axis] @defer_draw def _on_yatt_change(self, *args): if self.y_att is not None: self.y_att_world = self.reference_data.world_component_ids[self.y_att.axis] @defer_draw def _on_xatt_world_change(self, *args): if self.x_att_world is not None and self.x_att_world == self.y_att_world: world_ids = self.reference_data.world_component_ids if self.x_att_world == world_ids[-1]: self.y_att_world = world_ids[-2] else: self.y_att_world = world_ids[-1] @defer_draw def _on_yatt_world_change(self, *args): if self.y_att_world is not None and self.y_att_world == self.x_att_world: world_ids = self.reference_data.world_component_ids if self.y_att_world == world_ids[-1]: self.x_att_world = world_ids[-2] else: self.x_att_world = world_ids[-1] def _set_reference_data(self): if self.reference_data is None: for layer in self.layers: if isinstance(layer.layer, Data): self.reference_data = layer.layer return def _set_default_slices(self): # Need to make sure this gets called immediately when reference_data is changed if self.reference_data is None: self.slices = () else: self.slices = (0,) * self.reference_data.ndim @property def numpy_slice_aggregation_transpose(self): """ Returns slicing information usable by Numpy. This returns two objects: the first is an object that can be used to slice Numpy arrays and return a 2D array, and the second object is a boolean indicating whether to transpose the result. """ if self.reference_data is None: return None slices = [] agg_func = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis or i == self.y_att.axis: slices.append(slice(None)) agg_func.append(None) else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].slice) agg_func.append(self.slices[i].function) else: slices.append(self.slices[i]) transpose = self.y_att.axis > self.x_att.axis return slices, agg_func, transpose @property def wcsaxes_slice(self): """ Returns slicing information usable by WCSAxes. This returns an iterable of slices, and including ``'x'`` and ``'y'`` for the dimensions along which we are not slicing. """ if self.reference_data is None: return None slices = [] for i in range(self.reference_data.ndim): if i == self.x_att.axis: slices.append('x') elif i == self.y_att.axis: slices.append('y') else: if isinstance(self.slices[i], AggregateSlice): slices.append(self.slices[i].center) else: slices.append(self.slices[i]) return slices[::-1] def flip_x(self): """ Flip the x_min/x_max limits. """ self.x_lim_helper.flip_limits() def flip_y(self): """ Flip the y_min/y_max limits. """ self.y_lim_helper.flip_limits()