import ipywidgets as widgets
from IPython.display import display
from glue.core.application_base import Application
from glue.core.link_helpers import LinkSame
from glue.core.roi import PolygonalROI
from glue.core.subset import RoiSubsetState
from glue.core.command import ApplySubsetState
from glue.core.edit_subset_mode import (NewMode, ReplaceMode, AndMode, OrMode,
XorMode, AndNotMode)
from glue_jupyter.utils import _update_not_none
from glue_jupyter.widgets.subset_select import SubsetSelect
from glue_jupyter.widgets.subset_mode import SubsetMode
__all__ = ['JupyterApplication']
# TODO: move this to glue-core so that the subset mode can be set ot a string
# there too
SUBSET_MODES = {'new': NewMode, 'replace': ReplaceMode, 'and': AndMode,
'or': OrMode, 'xor': XorMode, 'not': AndNotMode}
[docs]class JupyterApplication(Application):
"""
The main Glue application object for the Jupyter environment.
This is used as the primary way to interact with glue, including loading
data, creating viewers, and adding links.
Parameters
----------
data_collection : `~glue.core.data_collection.DataCollection`
A pre-existing data collection. By default, a new data collection is
created.
session : `~glue.core.session.Session`
A pre-existing session object. By default, a new session object is
created.
"""
def __init__(self, data_collection=None, session=None):
super(JupyterApplication, self).__init__(data_collection=data_collection, session=session)
self.output = widgets.Output()
self.widget_data_collection = widgets.SelectMultiple()
self.widget_subset_select = SubsetSelect(self.session)
self.widget_subset_mode = SubsetMode(self.session)
self.widget = widgets.VBox(children=[self.widget_subset_mode, self.output])
def _ipython_display_(self):
display(self.widget)
[docs] def link(self, links):
"""
Parse and add links.
"""
from glue.qglue import parse_links
self.data_collection.add_link(parse_links(self.data_collection, links))
[docs] def add_link(self, data1, attribute1, data2, attribute2):
"""
Add a simple identity link between two attributes.
Parameters
----------
data1 : `~glue.core.data.Data`
The dataset containing the first attribute.
attribute1 : str or `~glue.core.component_id.ComponentID`
The first attribute to link.
data2 : `~glue.core.data.Data`
The dataset containing the first attribute.
attribute2 : str or `~glue.core.component_id.ComponentID`
The first attribute to link.
"""
# For now this assumes attribute1 and attribute2 are strings and single
# attributes. In future we should generalize this while keeping the
# simplest use case simple.
att1 = data1.id[attribute1]
att2 = data2.id[attribute2]
link = LinkSame(att1, att2)
self.data_collection.add_link(link)
[docs] def set_subset_mode(self, mode):
"""
Set the current subset mode.
By default, selections in viewers update the current subset by
replacing the previous selection with the new selection. However it is
also possible to combine the current selection with previous selections
using boolean operations.
Parameters
----------
mode : {'new', 'replace', 'and', 'or', 'xor', 'not'}
The selection mode to use.
"""
if mode in SUBSET_MODES:
mode = SUBSET_MODES[mode]
self.session.edit_subset_mode.mode = mode
[docs] def new_data_viewer(self, *args, **kwargs):
show = kwargs.pop('show', True)
viewer = super().new_data_viewer(*args, **kwargs)
if show:
viewer.show()
return viewer
[docs] def histogram1d(self, x=None, data=None, widget='bqplot', color=None,
x_min=None, x_max=None, n_bin=None, normalize=False,
cumulative=False, viewer_state=None, layer_state=None,
show=True):
"""
Open an interactive histogram viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
widget : {'bqplot', 'matplotlib'}
Whether to use bqplot or Matplotlib as the front-end.
color : str or tuple, optional
The color to use for the data. Note that this will have the
effect of setting the data color for all viewers.
x_min : float, optional
The lower value of the range to compute the histogram in.
x_max : float, optional
The upper value of the range to compute the histogram in.
n_bin : int, optional
The number of bins in the histogram.
normalize : bool, optional
Whether to normalize the histogram.
cumulative : bool, optional
Whether to show a cumulative histogram.
viewer_state : `~glue.viewers.common.state.ViewerState`
The initial state for the viewer (advanced).
layer_state : `~glue.viewers.common.state.LayerState`
The initial state for the data layer (advanced).
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
if widget == 'bqplot':
from .bqplot.histogram import BqplotHistogramView
viewer_cls = BqplotHistogramView
elif widget == 'matplotlib':
from .matplotlib.histogram import HistogramJupyterViewer
viewer_cls = HistogramJupyterViewer
else:
raise ValueError("Widget type should be 'bqplot' or 'matplotlib'")
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
viewer_state_obj = viewer_cls._state_cls()
viewer_state_obj.x_att_helper.append_data(data)
viewer_state = viewer_state or {}
if x is not None:
viewer_state['x_att'] = data.id[x]
# x_min and x_max get set to the hist_x_min/max in
# glue.viewers.histogram.state for this API it make more sense to call
# it x_min and x_max, and for consistency with the rest
_update_not_none(viewer_state, hist_x_min=x_min, hist_x_max=x_max, hist_n_bin=n_bin,
normalize=normalize, cumulative=cumulative)
viewer_state_obj.update_from_dict(viewer_state)
view = self.new_data_viewer(viewer_cls, data=data,
state=viewer_state_obj, show=show)
layer_state = layer_state or {}
_update_not_none(layer_state, color=color)
view.layers[0].state.update_from_dict(layer_state)
return view
[docs] def scatter2d(self, x=None, y=None, data=None, widget='bqplot', color=None,
size=None, viewer_state=None, layer_state=None, show=True):
"""
Open an interactive 2d scatter plot viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis.
y : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the y axis.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
widget : {'bqplot', 'matplotlib'}
Whether to use bqplot or Matplotlib as the front-end.
color : str or tuple, optional
The color to use for the markers. Note that this will have the
effect of setting the data color for all viewers.
size : int or float
The size to use for the markers. Note that this will have the
effect of setting the marker size for all viewers.
viewer_state : `~glue.viewers.common.state.ViewerState`
The initial state for the viewer (advanced).
layer_state : `~glue.viewers.common.state.LayerState`
The initial state for the data layer (advanced).
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
if widget == 'bqplot':
from .bqplot.scatter import BqplotScatterView
viewer_cls = BqplotScatterView
elif widget == 'matplotlib':
from .matplotlib.scatter import ScatterJupyterViewer
viewer_cls = ScatterJupyterViewer
else:
raise ValueError("Widget type should be 'bqplot' or 'matplotlib'")
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
viewer_state_obj = viewer_cls._state_cls()
viewer_state_obj.x_att_helper.append_data(data)
viewer_state_obj.y_att_helper.append_data(data)
viewer_state = viewer_state or {}
if x is not None:
viewer_state['x_att'] = data.id[x]
if y is not None:
viewer_state['y_att'] = data.id[y]
viewer_state_obj.update_from_dict(viewer_state)
view = self.new_data_viewer(viewer_cls, data=data,
state=viewer_state_obj, show=show)
layer_state = layer_state or {}
_update_not_none(layer_state, color=color, size=size)
view.layers[0].state.update_from_dict(layer_state)
return view
[docs] def scatter3d(self, x=None, y=None, z=None, data=None, show=True):
"""
Open an interactive 3d scatter plot viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis.
y : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the y axis.
z : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the z axis.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
from .ipyvolume import IpyvolumeScatterView
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
view = self.new_data_viewer(IpyvolumeScatterView, data=data, show=show)
if x is not None:
x = data.id[x]
view.state.x_att = x
if y is not None:
y = data.id[y]
view.state.y_att = y
if z is not None:
z = data.id[z]
view.state.z_att = z
return view
[docs] def imshow(self, x=None, y=None, data=None, widget='bqplot', show=True):
"""
Open an interactive image viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis. This should be one of the
pixel axis attributes.
y : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the y axis. This should be one of the
pixel axis attributes.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
widget : {'bqplot', 'matplotlib'}
Whether to use bqplot or Matplotlib as the front-end.
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
if widget == 'bqplot':
from .bqplot.image import BqplotImageView
viewer_cls = BqplotImageView
elif widget == 'matplotlib':
from .matplotlib.image import ImageJupyterViewer
viewer_cls = ImageJupyterViewer
else:
raise ValueError("Widget type should be 'bqplot' or 'matplotlib'")
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
if len(data.pixel_component_ids) < 2:
raise ValueError('Only data with two or more dimensions can be used '
'as the initial dataset in the image viewer')
view = self.new_data_viewer(viewer_cls, data=data, show=show)
if x is not None:
x = data.id[x]
view.state.x_att = x
if y is not None:
y = data.id[y]
view.state.y_att = y
return view
[docs] def profile1d(self, x=None, data=None, widget='bqplot', show=True):
"""
Open an interactive 1d profile viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis. This should be a pixel or
world coordinate `~glue.core.component_id.ComponentID`.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
widget : {'bqplot', 'matplotlib'}
Whether to use bqplot or Matplotlib as the front-end.
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
if widget == 'bqplot':
from .bqplot.profile import BqplotProfileView
viewer_cls = BqplotProfileView
elif widget == 'matplotlib':
from .matplotlib.profile import ProfileJupyterViewer
viewer_cls = ProfileJupyterViewer
else:
raise ValueError("Widget type should be 'matplotlib'")
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
view = self.new_data_viewer(viewer_cls, data=data, show=show)
if x is not None:
x = data.id[x]
view.state.x_att = x
return view
[docs] def volshow(self, x=None, y=None, z=None, data=None, show=True):
"""
Open an interactive volume viewer.
Parameters
----------
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis. This should be one of the
pixel axis attributes.
y : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the y axis. This should be one of the
pixel axis attributes.
z : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the z axis. This should be one of the
pixel axis attributes.
data : `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""
from .ipyvolume import IpyvolumeVolumeView
if data is None:
if len(self._data) != 1:
raise ValueError('There is more than one data set in the data '
'collection, please pass a data argument')
else:
data = self._data[0]
view = self.new_data_viewer(IpyvolumeVolumeView, data=data, show=show)
if x is not None:
x = data.id[x]
view.state.x_att = x
if y is not None:
y = data.id[y]
view.state.y_att = y
if z is not None:
z = data.id[z]
view.state.z_att = z
return view
[docs] def subset(self, name, subset_state):
"""
Create a new selection/subset.
Parameters
----------
name : str
The name of the new subset.
subset_state : `~glue.core.subset.SubsetState`
The definition of the subset. See the documentation at
http://docs.glueviz.org/en/stable/python_guide/data_tutorial.html#defining-new-subsets
for more information about creating subsets programmatically.
"""
return self.data_collection.new_subset_group(name, subset_state)
[docs] def subset_lasso2d(self, x_att, y_att, lasso_x, lasso_y):
"""
Create a subset from a programmatic 2d lasso selection.
Parameters
----------
x_att : `~glue.core.component_id.ComponentID`
The attribute corresponding to the x values being selected.
y_att : `~glue.core.component_id.ComponentID`
The attribute corresponding to the x values being selected.
lasso_x : iterable
The x values of the lasso.
lasso_y : iterable
The y values of the lasso.
"""
roi = PolygonalROI(lasso_x, lasso_y)
self.subset_roi([x_att, y_att], roi)
[docs] def subset_roi(self, attributes, roi):
"""
Create a subset from a region of interest.
Parameters
----------
attributes : iterable
The attributes on the x and y axis
roi : `~glue.core.roi.Roi`
The region of interest to use to create the subset.
"""
subset_state = RoiSubsetState(attributes[0], attributes[1], roi)
cmd = ApplySubsetState(data_collection=self.data_collection,
subset_state=subset_state)
self._session.command_stack.do(cmd)
# Methods that we need to override to avoid the default behavior
def _update_undo_redo_enabled(self, *args):
pass # TODO: if we want a gui for this, we need to update it here
@staticmethod
def _choose_merge(*args, **kwargs):
# Never suggest automatic merging
return None, None