Skip to content

Commit

Permalink
Merge branch 'main' into export-color-settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Carifio24 authored Oct 11, 2022
2 parents 94a99cb + efca117 commit ea05139
Show file tree
Hide file tree
Showing 33 changed files with 771 additions and 252 deletions.
27 changes: 20 additions & 7 deletions .github/workflows/ci_workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ jobs:
- linux: py37-test-pyside513-all
- linux: py38-test-pyside514
- linux: py39-test-pyqt515
- linux: py310-test-pyside515-all
# Test against latest developer versions of some packages
- linux: py310-test-pyqt515-dev-all
# Try out documentation build on Linux and macOS
- linux: py37-docs-pyqt513
Expand All @@ -55,7 +51,6 @@ jobs:
# Test a few configurations on MacOS X (Big Sur/Monterey on arm64 for py310)
- macos: py37-test-pyqt513
- macos: py38-test-pyqt514
- macos: py310-test-pyqt515-all
PLAT: arm64
# Try out documentation build on macOS
Expand All @@ -76,19 +71,37 @@ jobs:
with:
coverage: codecov
display: true
# The Linux PyQt 5.15 installation requires apt-getting its xcb deps and headless X11 display
# Linux PyQt 5.15 and 6.3 installations require apt-getting xcb and EGL deps
libraries: |
apt:
- '^libxcb.*-dev'
- libxkbcommon-x11-dev
- libegl1-mesa
brew:
- enchant
envs: |
# Some (PySide2 in particular) envs are failing with runtime errors
# Some (PySide2 in particular) envs are failing with runtime errors;
# PyQt6 and PySide6 support in progress
- linux: py38-test-pyside514
- linux: py310-test-pyqt63-all
- linux: py310-test-pyside63
- linux: py310-test-pyside515-all
- linux: py310-test-pyqt513-lts
- linux: py310-test-pyqt515-lts
# Test against latest developer versions of some packages
- linux: py310-test-pyqt515-dev-all
- macos: py38-test-pyside514
- macos: py310-test-pyqt515-all
- macos: py310-test-pyqt63
- macos: py310-test-pyside63
- windows: py38-test-pyqt514-all
- windows: py39-test-pyside515-all
- windows: py310-test-pyqt63
- windows: py310-test-pyside63
publish:
needs: tests
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ v1.6.0 (unreleased)

* Update Python exports to respect glue color settings. [#2322]

* Added one-to-one ``join_on_key``-type links to the link-manager allowing
them to be created and deleted through the UI. This option is available
under 'Create advanced link>Join>Join on ID.' [#2215]

* Modify histogram viewer to not prepend x-axis label with 'Log' when using a log scale x-axis. [#2325]

* Modify scatter viewer to not prepend axis labels with 'Log' when using log scale axes. [#2323]

* Fixed a bug where minor tick marks were not respecting the settings colors. [#2305]
Expand Down
39 changes: 0 additions & 39 deletions doc/customizing_guide/available_plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,42 +220,3 @@ You can also send data from other applications to glue - for more information on
doing this, see the guide for the relevant application you want to use - glue
understands messages adding images and tables, as well as messages related to
subsets.

Specviz
^^^^^^^

The `specviz <https://github.com/spacetelescope/specviz>`_ package is a
standalone application for spectral visualization and analysis, but it
incorporates a plugin for glue that makes it possible to view spectral and/or
spectral cubes open in glue. Full installation instructions are available in
the `specviz documentation
<https://specviz.readthedocs.io/en/latest/installation.html>`__, but you can
also install specviz using::

conda install -c glueviz specviz

or if you don't use conda::

pip install specviz

Once specviz is installed, a new data viewer called **Specviz** will be
available, and should allow you to view spectral cubes and their subsets
as collapsed 1D spectra. More information about specviz can be found in the
`documentation <https://specviz.readthedocs.io/en/latest/index.html>`__, as well
as at the `GitHub repository <https://github.com/spacetelescope/specviz>`_.

CubeViz and MOSViz
^^^^^^^^^^^^^^^^^^

**CubeViz** and **MOSViz** are applications developed at the Space Science
Institute and built on top of glue for the visualization of IFU Spectral Cubes
and for Multi-Object Spectroscopy (MOS) respectively. To find out more about
using these, see https://cubeviz.readthedocs.io and
https://mosviz.readthedocs.io. As for other packages mentioned on this page,
you can easily install these using::

conda install -c glueviz cubeviz mosviz

or if you don't use conda::

pip install cubeviz mosviz
17 changes: 16 additions & 1 deletion doc/developer_guide/data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ The methods you need to define are:
(``view=[(1, 2, 3), (4, 3, 4)]``), and so on. If a view is specified, only that
subset of values should be returned. For example if the data has an overall
shape of ``(10,)`` and ``view=slice(1, 6, 2)``, ``get_data`` should
return an array with shape ``(3,)``.
return an array with shape ``(3,)``. By default, :meth:`BaseCartesianData.get_data <glue.core.data.BaseCartesianData.get_data>`
will return values for pixel and world :class:`~glue.core.component_id.ComponentID`
objects as well as any linked :class:`~glue.core.component_id.ComponentID`, so we
recommend that your implementation calls :meth:`BaseCartesianData.get_data <glue.core.data.BaseCartesianData.get_data>`
for any :class:`~glue.core.component_id.ComponentID` you do not expose yourself.
* :meth:`~glue.core.data.BaseCartesianData.get_mask`: given a
:class:`~glue.core.subset.SubsetState` object (described in `Subset states`_)
and optionally a ``view``, return a boolean array describing which values are
Expand Down Expand Up @@ -138,6 +142,17 @@ While developing your data class, one way to make sure that glue doesn't crash
if you haven't yet implemented support for a specific subset state is to
interpret any unimplemented subset state as simply indicating an empty subset.

Linking
-------

You should be able to link data objects that inherit from
:class:`~glue.core.data.BaseCartesianData` with other datasets - however
for this to work properly you should make sure that your implementation of
:meth:`~glue.core.data.BaseCartesianData.get_data` calls
:meth:`BaseCartesianData.get_data <glue.core.data.BaseCartesianData.get_data>`
for any unrecognized :class:`~glue.core.component_id.ComponentID`, as the base
implementation will handle returning linked values.

Using your data object
----------------------

Expand Down
6 changes: 3 additions & 3 deletions doc/developer_guide/random_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ def get_kind(self, cid):
return 'numerical'

def get_data(self, cid, view=None):
if cid in self.pixel_component_ids:
return super(RandomData, self).get_data(cid, view=view)
else:
if cid is self.data_cid:
return np.random.random(view_shape(self.shape, view))
else:
return super(RandomData, self).get_data(cid, view=view)

def get_mask(self, subset_state, view=None):
return subset_state.to_mask(self, view=view)
Expand Down
191 changes: 112 additions & 79 deletions glue/core/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ class BaseCartesianData(BaseData, metaclass=abc.ABCMeta):
def __init__(self, coords=None):
super(BaseCartesianData, self).__init__()
self._coords = coords
self._externally_derivable_components = OrderedDict()
self._pixel_aligned_data = OrderedDict()

@property
def coords(self):
Expand Down Expand Up @@ -442,16 +444,27 @@ def get_data(self, cid, view=None):
if cid in self.pixel_component_ids:
shape = tuple(-1 if i == cid.axis else 1 for i in range(self.ndim))
pix = np.arange(self.shape[cid.axis], dtype=float).reshape(shape)
return broadcast_to(pix, self.shape)[view]
elif cid in self.world_component_ids:
comp = self.world_components[cid]
if view is not None:
result = comp[view]
if view is None:
return broadcast_to(pix, self.shape)
else:
result = comp.data
return broadcast_to(pix, self.shape)[view]
elif cid in self.world_component_ids:
comp = self._world_components[cid]
elif cid in self._externally_derivable_components:
comp = self._externally_derivable_components[cid]
else:
raise IncompatibleAttribute(cid)

# Note that above we have extracted Component objects from internal
# properties - we don't actually expose Component objects in this class,
# only in the Data class, but we use these components internally for
# convenience.

if view is None:
return comp.data
else:
return comp[view]

@abc.abstractmethod
def get_mask(self, subset_state, view=None):
"""
Expand Down Expand Up @@ -593,13 +606,106 @@ def world_component_ids(self):
self._world_component_ids = []
self._world_components = {}
for i in range(self.ndim):
# Note: we use a Component object here for convenience but we
# don't actually expose it via the BaseCartesianData API - in
# get_data we extract the data from the component.
comp = CoordinateComponent(self, i, world=True)
label = axis_label(self.coords, i)
cid = ComponentID(label, parent=self)
self._world_component_ids.append(cid)
self._world_components[cid] = comp
return self._world_component_ids

def _set_externally_derivable_components(self, derivable_components):
"""
Externally deriable components are components identified by component
IDs from other datasets.
This method is meant for internal use only and is called by the link
manager. The ``derivable_components`` argument should be set to a
dictionary where the keys are the derivable component IDs, and the
values are DerivedComponent instances which can be used to get the
data.
"""

# Note that even though Component objects are not normally exposed as
# part of the BaseCartesianData API, we use these internally here as
# a convenience, and extract the data from them in get_data. The actual
# derived components are however used in the Data class.

if len(self._externally_derivable_components) == 0 and len(derivable_components) == 0:

return

elif len(self._externally_derivable_components) == len(derivable_components):

for key in derivable_components:
if key in self._externally_derivable_components:
if self._externally_derivable_components[key].link is not derivable_components[key].link:
break
else:
break
else:
return # Unchanged!

self._externally_derivable_components = derivable_components

if self.hub:
msg = ExternallyDerivableComponentsChangedMessage(self)
self.hub.broadcast(msg)

def _get_external_link(self, cid):
if cid in self._externally_derivable_components:
return self._externally_derivable_components[cid].link
else:
return None

def _get_coordinate_transform(self, world_cid):
if world_cid in self._world_components:
def transform(values):
return self._world_components._calculate(view=values)
return transform
else:
return None

def _set_pixel_aligned_data(self, pixel_aligned_data):
"""
Pixel-aligned data are datasets that contain pixel component IDs
that are equivalent (identically, not transformed) with all pixel
component IDs in the present dataset.
Note that the other datasets may have more but not fewer dimensions, so
this information may not be symmetric between datasets with differing
numbers of dimensions.
"""

# First check if anything has changed, as if not then we should just
# leave things as-is and avoid emitting a message.
if len(self._pixel_aligned_data) == len(pixel_aligned_data):
for data in self._pixel_aligned_data:
if data not in pixel_aligned_data or pixel_aligned_data[data] != self._pixel_aligned_data[data]:
break
else:
return

self._pixel_aligned_data = pixel_aligned_data
if self.hub:
msg = PixelAlignedDataChangedMessage(self)
self.hub.broadcast(msg)

@property
def pixel_aligned_data(self):
"""
Information about other datasets in the same data collection that have
matching or a subset of pixel component IDs.
This is returned as a dictionary where each key is a dataset with
matching pixel component IDs, and the value is the order in which the
pixel component IDs of the other dataset can be found in the current
one.
"""
return self._pixel_aligned_data


class Data(BaseCartesianData):
"""
Expand Down Expand Up @@ -647,8 +753,6 @@ def __init__(self, label="", coords=None, **kwargs):

# Components
self._components = OrderedDict()
self._externally_derivable_components = OrderedDict()
self._pixel_aligned_data = OrderedDict()
self._pixel_component_ids = ComponentIDList()
self._world_component_ids = ComponentIDList()

Expand Down Expand Up @@ -1004,77 +1108,6 @@ def add_component(self, component, label):

return component_id

def _set_externally_derivable_components(self, derivable_components):
"""
Externally deriable components are components identified by component
IDs from other datasets.
This method is meant for internal use only and is called by the link
manager. The ``derivable_components`` argument should be set to a
dictionary where the keys are the derivable component IDs, and the
values are DerivedComponent instances which can be used to get the
data.
"""

if len(self._externally_derivable_components) == 0 and len(derivable_components) == 0:

return

elif len(self._externally_derivable_components) == len(derivable_components):

for key in derivable_components:
if key in self._externally_derivable_components:
if self._externally_derivable_components[key].link is not derivable_components[key].link:
break
else:
break
else:
return # Unchanged!

self._externally_derivable_components = derivable_components

if self.hub:
msg = ExternallyDerivableComponentsChangedMessage(self)
self.hub.broadcast(msg)

def _set_pixel_aligned_data(self, pixel_aligned_data):
"""
Pixel-aligned data are datasets that contain pixel component IDs
that are equivalent (identically, not transformed) with all pixel
component IDs in the present dataset.
Note that the other datasets may have more but not fewer dimensions, so
this information may not be symmetric between datasets with differing
numbers of dimensions.
"""

# First check if anything has changed, as if not then we should just
# leave things as-is and avoid emitting a message.
if len(self._pixel_aligned_data) == len(pixel_aligned_data):
for data in self._pixel_aligned_data:
if data not in pixel_aligned_data or pixel_aligned_data[data] != self._pixel_aligned_data[data]:
break
else:
return

self._pixel_aligned_data = pixel_aligned_data
if self.hub:
msg = PixelAlignedDataChangedMessage(self)
self.hub.broadcast(msg)

@property
def pixel_aligned_data(self):
"""
Information about other datasets in the same data collection that have
matching or a subset of pixel component IDs.
This is returned as a dictionary where each key is a dataset with
matching pixel component IDs, and the value is the order in which the
pixel component IDs of the other dataset can be found in the current
one.
"""
return self._pixel_aligned_data

@contract(link=ComponentLink,
label='cid_like|None',
returns=DerivedComponent)
Expand Down
Loading

0 comments on commit ea05139

Please sign in to comment.