Skip to content

Commit

Permalink
Merge pull request #2458 from astrofrog/simple-viewers
Browse files Browse the repository at this point in the history
Added 'simple' viewers which can be used for testing or simple use cases
  • Loading branch information
astrofrog authored Nov 10, 2023
2 parents 881345f + 1f897e0 commit ddb7abb
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 15 deletions.
96 changes: 96 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
version: 2.1

jobs:

# The following job is to run any visual comparison test, and runs on any branch
# or in any pull request. It will generate a summary page for each tox environment
# being run which is accessible through the CircleCI artifacts.

visual:
parameters:
jobname:
type: string
docker:
- image: cimg/python:3.11
environment:
TOXENV: << parameters.jobname >>
steps:
- checkout
- run:
name: Install dependencies
command: |
sudo apt update
pip install pip tox --upgrade
- run:
name: Run tests
command: tox -v
- store_artifacts:
path: results
- run:
name: "Image comparison page is available at: "
command: echo "${CIRCLE_BUILD_URL}/artifacts/${CIRCLE_NODE_INDEX}/results/fig_comparison.html"

# The following job runs only on main - and its main purpose is to update the
# reference images in the glue-core-visual-tests repository. This job needs
# a deploy key. To produce this, go to the glue-core-visual-tests
# repository settings and go to SSH keys, then add your public SSH key.
deploy-reference-images:
parameters:
jobname:
type: string
docker:
- image: cimg/python:3.11
environment:
TOXENV: << parameters.jobname >>
steps:
- checkout
- run:
name: Install dependencies
command: |
sudo apt update
pip install pip tox --upgrade
- run: ssh-add -D
- add_ssh_keys:
fingerprints: "44:09:69:d7:c6:77:25:e9:46:da:f1:22:7d:d4:38:29"
- run: ssh-keyscan github.com >> ~/.ssh/known_hosts
- run: git config --global user.email "glue@circleci" && git config --global user.name "Glue Circle CI"
- run: git clone [email protected]:glue-viz/glue-core-visual-tests.git --depth 1 ~/glue-core-visual-tests/
- run:
name: Generate reference images
command: tox -v -- --mpl-generate-path=/home/circleci/glue-core-visual-tests/images/$TOXENV
- run: |
cd ~/glue-core-visual-tests/
git pull
git status
git add .
git commit -m "Update reference images from ${CIRCLE_BRANCH}" || echo "No changes to reference images to deploy"
git push
workflows:
version: 2

visual-tests:
jobs:
- visual:
name: << matrix.jobname >>
matrix:
parameters:
jobname:
- "py311-test-visual"

- deploy-reference-images:
name: baseline-<< matrix.jobname >>
matrix:
parameters:
jobname:
- "py311-test-visual"
requires:
- << matrix.jobname >>
filters:
branches:
only:
- main

notify:
webhooks:
- url: https://giles.cadair.dev/circleci
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ glue/_githash.py
.vscode
# vscode plugin
.history

results
Empty file added glue/tests/visual/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions glue/tests/visual/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

from functools import wraps

import pytest

try:
import pytest_mpl # noqa
except ImportError:
HAS_PYTEST_MPL = False
else:
HAS_PYTEST_MPL = True


def visual_test(*args, **kwargs):
"""
A decorator that defines a visual test.
This automatically decorates tests with mpl_image_compare with common
options used by all figure tests in glue-core.
"""

tolerance = kwargs.pop("tolerance", 0)
style = kwargs.pop("style", {})
savefig_kwargs = kwargs.pop("savefig_kwargs", {})
savefig_kwargs["metadata"] = {"Software": None}

def decorator(test_function):
@pytest.mark.mpl_image_compare(
tolerance=tolerance, style=style, savefig_kwargs=savefig_kwargs, **kwargs
)
@pytest.mark.skipif(
not HAS_PYTEST_MPL, reason="pytest-mpl is required for the figure tests"
)
@wraps(test_function)
def test_wrapper(*args, **kwargs):
return test_function(*args, **kwargs)

return test_wrapper

# If the decorator was used without any arguments, the only positional
# argument will be the test to decorate so we do the following:
if len(args) == 1:
return decorator(*args)

return decorator
6 changes: 6 additions & 0 deletions glue/tests/visual/py311-test-visual.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"glue.viewers.histogram.tests.test_viewer.test_simple_viewer": "cb08123fbad135ab614bb7ec13475fcc83321057d884fe80c3a32970b2d14762",
"glue.viewers.image.tests.test_viewer.test_simple_viewer": "72abd60b484d14f721254f027bb0ab9b36245d5db77eb87693f4dd9998fd28be",
"glue.viewers.profile.tests.test_viewer.test_simple_viewer": "f68a21be5080fec513388b2d2b220512e7b0df5498e2489da54e58708de435b3",
"glue.viewers.scatter.tests.test_viewer.test_simple_viewer": "1020a7bd3abe40510b9e03047c3b423b75c3c64ac18e6dcd6257173cec1ed53f"
}
2 changes: 2 additions & 0 deletions glue/viewers/histogram/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from glue.viewers.matplotlib.layer_artist import MatplotlibLayerArtist
from glue.core.exceptions import IncompatibleAttribute, IncompatibleDataException

__all__ = ["HistogramLayerArtist"]


class HistogramLayerArtist(MatplotlibLayerArtist):

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
from glue.viewers.common.viewer import Viewer
from glue.viewers.histogram.state import HistogramViewerState
import numpy as np

from astropy.utils import NumpyRNGContext

from glue.tests.visual.helpers import visual_test
from glue.viewers.histogram.viewer import SimpleHistogramViewer
from glue.core.application_base import Application
from glue.core.data import Data


class TestHistogramViewer(Viewer):
_state_cls = HistogramViewerState
@visual_test
def test_simple_viewer():

# Make sure the simple viewer can be instantiated

with NumpyRNGContext(12345):

data1 = Data(x=np.random.normal(1, 2, 1000), label='data1')
data2 = Data(y=np.random.uniform(-1, 5, 1000), label='data2')

app = Application()
app.data_collection.append(data1)
app.data_collection.append(data2)

viewer = app.new_data_viewer(SimpleHistogramViewer)
viewer.add_data(data1)
viewer.add_data(data2)

app.data_collection.new_subset_group(label='subset1', subset_state=data1.id['x'] > 2)

return viewer.figure


def test_remove_data_collection():
Expand All @@ -22,7 +45,7 @@ def test_remove_data_collection():
app.data_collection.append(data1)
app.data_collection.append(data2)

viewer = app.new_data_viewer(TestHistogramViewer)
viewer = app.new_data_viewer(SimpleHistogramViewer)
viewer.add_data(data1)
viewer.add_data(data2)

Expand All @@ -46,7 +69,7 @@ def test_incompatible_datasets():
app.data_collection.append(data1)
app.data_collection.append(data2)

viewer = app.new_data_viewer(TestHistogramViewer)
viewer = app.new_data_viewer(SimpleHistogramViewer)
viewer.add_data(data1)
viewer.add_data(data2)

Expand Down
16 changes: 15 additions & 1 deletion glue/viewers/histogram/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from glue.core.subset import roi_to_subset_state
from glue.utils import mpl_to_datetime64

from glue.viewers.matplotlib.viewer import SimpleMatplotlibViewer
from glue.viewers.histogram.compat import update_histogram_viewer_state
from glue.viewers.histogram.layer_artist import HistogramLayerArtist
from glue.viewers.histogram.state import HistogramViewerState

__all__ = ['MatplotlibHistogramMixin']
__all__ = ['MatplotlibHistogramMixin', 'SimpleHistogramViewer']


class MatplotlibHistogramMixin(object):
Expand Down Expand Up @@ -69,3 +72,14 @@ def apply_roi(self, roi, override_mode=None):
@staticmethod
def update_viewer_state(rec, context):
return update_histogram_viewer_state(rec, context)


class SimpleHistogramViewer(MatplotlibHistogramMixin, SimpleMatplotlibViewer):

_state_cls = HistogramViewerState
_data_artist_cls = HistogramLayerArtist
_subset_artist_cls = HistogramLayerArtist

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MatplotlibHistogramMixin.setup_callbacks(self)
27 changes: 27 additions & 0 deletions glue/viewers/image/tests/test_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import numpy as np

from glue.tests.visual.helpers import visual_test
from glue.viewers.image.viewer import SimpleImageViewer
from glue.core.application_base import Application
from glue.core.data import Data


@visual_test
def test_simple_viewer():

# Make sure the simple viewer can be instantiated

data1 = Data(x=np.arange(6).reshape((2, 3)), label='data1')
data2 = Data(y=2 * np.arange(6).reshape((2, 3)), label='data2')

app = Application()
app.data_collection.append(data1)
app.data_collection.append(data2)

viewer = app.new_data_viewer(SimpleImageViewer)
viewer.add_data(data1)
viewer.add_data(data2)

app.data_collection.new_subset_group(label='subset1', subset_state=data1.pixel_component_ids[1] > 1.2)

return viewer.figure
14 changes: 13 additions & 1 deletion glue/viewers/image/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
from glue.core.coordinate_helpers import dependent_axes
from glue.core.data_region import RegionData

from glue.viewers.matplotlib.viewer import SimpleMatplotlibViewer
from glue.viewers.scatter.layer_artist import ScatterLayerArtist, ScatterRegionLayerArtist
from glue.viewers.image.layer_artist import ImageLayerArtist, ImageSubsetLayerArtist
from glue.viewers.image.compat import update_image_viewer_state
from glue.viewers.image.state import ImageViewerState

from glue.viewers.image.frb_artist import imshow
from glue.viewers.image.composite_array import CompositeArray

__all__ = ['MatplotlibImageMixin']
__all__ = ['MatplotlibImageMixin', 'SimpleImageViewer']


def get_identity_wcs(naxis):
Expand Down Expand Up @@ -258,3 +260,13 @@ def _script_footer(self):
x_ticklabel_size=self.state.x_ticklabel_size,
y_ticklabel_size=self.state.y_ticklabel_size)
return [], EXTRA_FOOTER.format(**options) + os.linesep * 2 + script


class SimpleImageViewer(MatplotlibImageMixin, SimpleMatplotlibViewer):

_state_cls = ImageViewerState

def __init__(self, *args, **kwargs):
kwargs['wcs'] = True
super().__init__(*args, **kwargs)
MatplotlibImageMixin.setup_callbacks(self)
13 changes: 11 additions & 2 deletions glue/viewers/matplotlib/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from matplotlib.artist import setp as msetp

from glue.config import settings
from glue.viewers.matplotlib.mpl_axes import update_appearance_from_settings
from glue.viewers.common.viewer import Viewer
from glue.viewers.matplotlib.mpl_axes import update_appearance_from_settings, init_mpl
from echo import delay_callback
from glue.utils import mpl_to_datetime64

__all__ = ['MatplotlibViewerMixin']
__all__ = ['MatplotlibViewerMixin', 'SimpleMatplotlibViewer']

SCRIPT_HEADER = """
# Initialize figure
Expand Down Expand Up @@ -352,3 +353,11 @@ def _script_legend(self):
if not self.state.legend.visible:
legend_str = indent(legend_str, "# ")
return [], legend_str


class SimpleMatplotlibViewer(MatplotlibViewerMixin, Viewer):

def __init__(self, session, parent=None, wcs=None, state=None, projection=None):
super().__init__(session, state=state)
self.figure, self.axes = init_mpl(wcs=wcs, projection=projection)
MatplotlibViewerMixin.setup_callbacks(self)
27 changes: 27 additions & 0 deletions glue/viewers/profile/tests/test_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from glue.tests.visual.helpers import visual_test
from glue.viewers.profile.viewer import SimpleProfileViewer
from glue.core.application_base import Application
from glue.core.data import Data


@visual_test
def test_simple_viewer():

# Make sure the simple viewer can be instantiated

data1 = Data(x=[1, 2, 3], label='data1')
data2 = Data(y=[1, 2, 3], label='data2')

app = Application()
app.data_collection.append(data1)
app.data_collection.append(data2)

viewer = app.new_data_viewer(SimpleProfileViewer)
viewer.add_data(data1)
viewer.add_data(data2)

app.data_collection.new_subset_group(label='subset1', subset_state=data1.pixel_component_ids[0] > 0.8)

viewer.state.layers[2].linewidth = 5

return viewer.figure
17 changes: 16 additions & 1 deletion glue/viewers/profile/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from glue.core.units import UnitConverter
from glue.core.subset import roi_to_subset_state

__all__ = ['MatplotlibProfileMixin']
from glue.viewers.matplotlib.viewer import SimpleMatplotlibViewer
from glue.viewers.profile.state import ProfileViewerState
from glue.viewers.profile.layer_artist import ProfileLayerArtist

__all__ = ['MatplotlibProfileMixin', 'SimpleProfileViewer']


class MatplotlibProfileMixin(object):
Expand Down Expand Up @@ -55,3 +59,14 @@ def apply_roi(self, roi, override_mode=None):

subset_state = roi_to_subset_state(roi, x_att=self.state.x_att)
self.apply_subset_state(subset_state, override_mode=override_mode)


class SimpleProfileViewer(MatplotlibProfileMixin, SimpleMatplotlibViewer):

_state_cls = ProfileViewerState
_data_artist_cls = ProfileLayerArtist
_subset_artist_cls = ProfileLayerArtist

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MatplotlibProfileMixin.setup_callbacks(self)
Empty file.
Loading

0 comments on commit ddb7abb

Please sign in to comment.