Source code for jdaviz.configs.cubeviz.plugins.viewers

from functools import cached_property

from glue.core import BaseData
from glue_jupyter.bqplot.image import BqplotImageView
import numpy as np

from jdaviz.core.registries import viewer_registry
from jdaviz.configs.cubeviz.plugins.mixins import WithSliceIndicator, WithSliceSelection
from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin
from jdaviz.configs.specviz.plugins.viewers import Spectrum1DViewer
from jdaviz.core.freezable_state import FreezableBqplotImageViewerState
from jdaviz.configs.cubeviz.plugins.cube_listener import MINVOL
from jdaviz.core.sonified_layers import (SonifiedDataLayerArtist,
                                         SonifiedLayerStateWidget,
                                         SonifiedLayerState)


__all__ = ['CubevizImageView', 'CubevizProfileView']


[docs] @viewer_registry("cubeviz-image-viewer", label="Image 2D (Cubeviz)") class CubevizImageView(JdavizViewerMixin, WithSliceSelection, BqplotImageView): # categories: zoom resets, (zoom, pan), subset, select tools, shortcuts # NOTE: zoom and pan are merged here for space consideration and to avoid # overflow to second row when opening the tray tools_nested = [ ['jdaviz:homezoom', 'jdaviz:prevzoom'], ['jdaviz:pixelboxzoommatch', 'jdaviz:boxzoom', 'jdaviz:pixelpanzoommatch', 'jdaviz:panzoom'], ['bqplot:truecircle', 'bqplot:rectangle', 'bqplot:ellipse', 'bqplot:circannulus'], ['jdaviz:spectrumperspaxel'], ['jdaviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] ] default_class = None _state_cls = FreezableBqplotImageViewerState def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # provide reference from state back to viewer to use for zoom syncing self.state._set_viewer(self) self._subscribe_to_layers_update() self.state.add_callback('reference_data', self._initial_x_axis) self.add_event_callback(self._viewer_mouse_event, events=['mousemove', 'mouseleave', 'mouseenter']) # Hide axes by default self.state.show_axes = False self.volume_level = None self.data_menu._obj.dataset.add_filter('is_image_or_flux_cube') self.layer_volume = {} self.same_pix = None self._cached_properties = ['combined_sonified_grid'] self.sonified_layers_enabled = [] self._layer_style_widget_cls[SonifiedDataLayerArtist] = SonifiedLayerStateWidget @property def _sonify_plugin(self): if self.jdaviz_helper is not None and 'Sonify Data' in self.jdaviz_helper.plugins: return self.jdaviz_helper.plugins['Sonify Data']._obj else: return None @property def _default_spectrum_viewer_reference_name(self): return self.jdaviz_helper._default_spectrum_viewer_reference_name @property def _default_flux_viewer_reference_name(self): return self.jdaviz_helper._default_flux_viewer_reference_name @property def _default_uncert_viewer_reference_name(self): return self.jdaviz_helper._default_uncert_viewer_reference_name def _initial_x_axis(self, *args): # Make sure that the x_att is correct on data load ref_data = self.state.reference_data if ref_data and ref_data.ndim == 3: for att_name in ["Right Ascension", "RA", "Galactic Longitude"]: if att_name in ref_data.component_ids(): x_att = att_name self.state.x_att_world = ref_data.id[x_att] break
[docs] def set_plot_axes(self): self.figure.axes[1].tick_format = None self.figure.axes[0].tick_format = None self.figure.axes[1].label = "y: pixels" self.figure.axes[0].label = "x: pixels" # Make it so y axis label is not covering tick numbers. self.figure.axes[1].label_offset = "-50"
[docs] def data(self, cls=None): return [layer_state.layer # .get_object(cls=cls or self.default_class) for layer_state in self.state.layers if hasattr(layer_state, 'layer') and isinstance(layer_state.layer, BaseData)]
@cached_property def combined_sonified_grid(self): compiled_coords = {} for k, v in self._sonify_plugin.data_lookup.items(): if k not in self.sonified_layers_enabled: continue # Each (x, y) coordinate corresponds to a different sound for each layer. # These sounds can be combined together and played by setting cbuff to True. # TODO: is there a better way to combine sounds or normalize them? # TODO: apply 1/N or 1/N**0.5 normalisation per layer for N layers? for coord, sound_array in v.items(): if coord in compiled_coords: compiled_coords[coord] += ((sound_array * (int(self.layer_volume[k]) / 100)). astype(int)) else: compiled_coords[coord] = ((sound_array * (int(self.layer_volume[k]) / 100)). astype(int)) return compiled_coords
[docs] def recalculate_combined_sonified_grid(self, event=None): self.layer_volume = {} self.sonified_layers_enabled = [] for layer in self.state.layers: if not isinstance(layer, SonifiedLayerState): continue self.layer_volume[layer.layer.label] = layer.volume self.sonified_layers_enabled += ([layer.layer.label] if getattr(layer, 'audible', False) else []) # noqa # Need to force an update of the layer icons since # audible is a state attribute, not a layer artist attribute self.jdaviz_app.state.layer_icons.notify_all() if 'combined_sonified_grid' in self.__dict__: del self.__dict__['combined_sonified_grid'] self.combined_sonified_grid
[docs] def update_volume_level(self, level): if self._sonify_plugin is None or not self._sonify_plugin.sonified_cube: return self.volume_level = level self._sonify_plugin.sonified_cube.atten_level = int(1/np.clip((level/100.)**2, MINVOL, 1))
def _viewer_mouse_event(self, data): if data['event'] in ('mouseleave', 'mouseenter') or not self.sonified_layers_enabled: if self._sonify_plugin is not None: self._sonify_plugin.stop_stream() return if len(self.jdaviz_app.data_collection) < 1: return # Extract data coordinates - these are pixels in the reference image x = np.floor(data['domain']['x']) y = np.floor(data['domain']['y']) if x is None or y is None or x < 0 or y < 0: # Out of bounds return if self.same_pix is None: self.same_pix = (x, y) elif (x, y) == self.same_pix: return if (not self._sonify_plugin.sonified_cube or not hasattr(self._sonify_plugin.sonified_cube, 'newsig') or not hasattr(self._sonify_plugin.sonified_cube, 'sigcube')): return self._sonify_plugin.start_stream() self._sonify_plugin.update_sonified_cube_with_coord(self, (x, y))
[docs] def get_data_layer_artist(self, layer=None, layer_state=None): if 'Sonified' in layer.meta: cls = SonifiedDataLayerArtist return self.get_layer_artist(cls, layer=layer) else: return super().get_data_layer_artist(layer, layer_state)
[docs] @viewer_registry("cubeviz-profile-viewer", label="Profile 1D (Cubeviz)") class CubevizProfileView(Spectrum1DViewer, WithSliceIndicator): # categories: zoom resets, zoom, pan, subset, select tools, shortcuts tools_nested = [ ['jdaviz:homezoom', 'jdaviz:prevzoom'], ['jdaviz:boxzoom', 'jdaviz:xrangezoom', 'jdaviz:yrangezoom'], ['jdaviz:panzoom', 'jdaviz:panzoom_x', 'jdaviz:panzoom_y'], ['bqplot:xrange'], ['jdaviz:selectslice', 'jdaviz:selectline'], ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] ] def __init__(self, *args, **kwargs): kwargs.setdefault('default_tool_priority', ['jdaviz:selectslice']) super().__init__(*args, **kwargs) @property def _default_flux_viewer_reference_name(self): return self.jdaviz_helper._default_flux_viewer_reference_name
[docs] def add_subset(self, subset, *args, **kwargs): # The cubeviz profile viewer does not show the spectra/profiles based # on the subsets of the cubes, but based on extracted datasets derived # from those subsets. We can ignore all subsets that have a subset # state that was defined from the image, and we recognize these by # looking for subset states that are defined based on two attributes. if len(subset.subset_state.attributes) == 2: return False else: return super().add_subset(subset, *args, **kwargs)