Skip to content

Commit

Permalink
Merge pull request #2524 from astrofrog/python-export-tests
Browse files Browse the repository at this point in the history
Move Python export tests from glue-qt to here
  • Loading branch information
astrofrog authored Jan 8, 2025
2 parents 40c58fb + 3afdd50 commit f356aeb
Show file tree
Hide file tree
Showing 8 changed files with 660 additions and 9 deletions.
4 changes: 4 additions & 0 deletions glue/core/coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ def __setgluestate__(cls, rec, context):
units=rec['units'],
labels=rec['labels'])

@property
def axis_correlation_matrix(self):
return self._matrix[:-1, :-1] != 0


# Kept for backward-compatibility
WCSCoordinates = WCS
Expand Down
61 changes: 61 additions & 0 deletions glue/viewers/histogram/tests/test_python_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from astropy.utils import NumpyRNGContext

from glue.core import Data, DataCollection
from glue.core.application_base import Application
from glue.viewers.histogram.viewer import SimpleHistogramViewer
from glue.viewers.matplotlib.tests.test_python_export import BaseTestExportPython, random_with_nan


class TestExportPython(BaseTestExportPython):

def setup_method(self, method):

with NumpyRNGContext(12345):
self.data = Data(**dict((name, random_with_nan(100, nan_index=idx + 1)) for idx, name in enumerate('abcdefgh')))
self.data_collection = DataCollection([self.data])
self.app = Application(self.data_collection)
self.viewer = self.app.new_data_viewer(SimpleHistogramViewer)
self.viewer.add_data(self.data)
self.viewer.state.x_att = self.data.id['a']

def teardown_method(self, method):
self.viewer = None
self.app = None

def test_simple(self, tmpdir):
self.assert_same(tmpdir)

def test_simple_visual(self, tmpdir):
self.viewer.state.layers[0].color = 'blue'
self.viewer.state.layers[0].alpha = 0.5
self.assert_same(tmpdir)

def test_simple_visual_legend(self, tmpdir):
self.viewer.state.legend.visible = True
self.viewer.state.layers[0].color = 'blue'
self.viewer.state.layers[0].alpha = 0.5
self.assert_same(tmpdir)

def test_cumulative(self, tmpdir):
self.viewer.state.cumulative = True
self.assert_same(tmpdir)

def test_normalize(self, tmpdir):
self.viewer.state.normalize = True
self.assert_same(tmpdir)

def test_subset(self, tmpdir):
self.data_collection.new_subset_group('mysubset', self.data.id['a'] > 0.5)
self.assert_same(tmpdir)

def test_subset_legend(self, tmpdir):
self.viewer.state.legend.visible = True
self.data_collection.new_subset_group('mysubset', self.data.id['a'] > 0.5)
self.assert_same(tmpdir)

def test_empty(self, tmpdir):
self.viewer.state.x_min = 10
self.viewer.state.x_max = 11
self.viewer.state.hist_x_min = 10
self.viewer.state.hist_x_max = 11
self.assert_same(tmpdir)
10 changes: 8 additions & 2 deletions glue/viewers/image/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def _on_yatt_change(self, *args):
self.y_att_world = self.y_att

@defer_draw
def _on_xatt_world_change(self, *args):
def _on_xatt_world_change(self, *args, forced=False):

if self.x_att_world is not None:

Expand All @@ -279,8 +279,11 @@ def _on_xatt_world_change(self, *args):
else:
self.x_att = self.x_att_world

if not forced:
self._on_yatt_world_change(forced=True)

@defer_draw
def _on_yatt_world_change(self, *args):
def _on_yatt_world_change(self, *args, forced=False):

if self.y_att_world is not None:

Expand All @@ -302,6 +305,9 @@ def _on_yatt_world_change(self, *args):
else:
self.y_att = self.y_att_world

if not forced:
self._on_xatt_world_change(forced=True)

def _set_reference_data(self):
if self.reference_data is None:
for layer in self.layers:
Expand Down
124 changes: 124 additions & 0 deletions glue/viewers/image/tests/test_python_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import pytest
import numpy as np
import matplotlib.pyplot as plt
from astropy.utils import NumpyRNGContext
from astropy.wcs import WCS

from glue.core import Data, DataCollection
from glue.core.coordinates import AffineCoordinates
from glue.core.application_base import Application
from glue.viewers.image.viewer import SimpleImageViewer
from glue.viewers.matplotlib.tests.test_python_export import BaseTestExportPython


class TestExportPython(BaseTestExportPython):

def setup_method(self, method):

with NumpyRNGContext(12345):
self.data = Data(cube=np.random.random((30, 50, 20)))
# Create data versions with WCS and affine coordinates
matrix = np.array([[2, 0, 0, -1], [0, 2, 1, 2], [0, 3, 1, -2], [0, 0, 0, 1]])
affine = AffineCoordinates(matrix, units=['Mm', 'Mm', 'km'], labels=['xw', 'yw', 'zw'])

self.data_wcs = Data(label='cube', cube=self.data['cube'], coords=WCS(naxis=3))
self.data_affine = Data(label='cube', cube=self.data['cube'], coords=affine)
self.data_collection = DataCollection([self.data, self.data_wcs, self.data_affine])
self.app = Application(self.data_collection)
self.viewer = self.app.new_data_viewer(SimpleImageViewer)
self.viewer.add_data(self.data)
# FIXME: On some platforms, using an integer label size
# causes some of the labels to be non-deterministically
# shifted by one pixel, so we pick a non-round font size
# to avoid this.
self.viewer.state.x_ticklabel_size = 8.21334111
self.viewer.state.y_ticklabel_size = 8.21334111

def teardown_method(self, method):
self.viewer = None
self.app = None

def assert_same(self, tmpdir, tol=0.1):
BaseTestExportPython.assert_same(self, tmpdir, tol=tol)

def viewer_load(self, coords):
if coords is not None:
self.viewer.add_data(getattr(self, f'data_{coords}'))
self.viewer.remove_data(self.data)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_simple(self, tmpdir, coords):
self.viewer_load(coords)
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_simple_legend(self, tmpdir, coords):
self.viewer_load(coords)
self.viewer.state.show_legend = True
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_simple_att(self, tmpdir, coords):
self.viewer_load(coords)
self.viewer.state.x_att = self.viewer.state.reference_data.pixel_component_ids[1]
self.viewer.state.y_att = self.viewer.state.reference_data.pixel_component_ids[0]
if coords == 'affine':
pytest.xfail('Known issue with axis label rendering')
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_simple_visual(self, tmpdir, coords):
self.viewer_load(coords)
self.viewer.state.legend.visible = True
self.viewer.state.layers[0].cmap = plt.cm.RdBu
self.viewer.state.layers[0].v_min = 0.2
self.viewer.state.layers[0].v_max = 0.8
self.viewer.state.layers[0].stretch = 'sqrt'
self.viewer.state.layers[0].stretch = 'sqrt'
self.viewer.state.layers[0].contrast = 0.9
self.viewer.state.layers[0].bias = 0.6
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_slice(self, tmpdir, coords):
self.viewer_load(coords)
self.viewer.state.x_att = self.viewer.state.reference_data.pixel_component_ids[1]
self.viewer.state.y_att = self.viewer.state.reference_data.pixel_component_ids[0]
self.viewer.state.slices = (2, 3, 4)
if coords == 'affine':
pytest.xfail('Known issue with axis label rendering')
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_aspect(self, tmpdir, coords):
self.viewer_load(coords)
self.viewer.state.aspect = 'auto'
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_subset(self, tmpdir, coords):
self.viewer_load(coords)
self.data_collection.new_subset_group('mysubset', self.data.id['cube'] > 0.5)
self.assert_same(tmpdir)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_subset_legend(self, tmpdir, coords):
self.viewer_load(coords)
self.data_collection.new_subset_group('mysubset',
self.viewer.state.reference_data.id['cube'] > 0.5)
self.viewer.state.legend.visible = True
self.assert_same(tmpdir, tol=0.15) # transparency and such

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_subset_slice(self, tmpdir, coords):
self.viewer_load(coords)
self.data_collection.new_subset_group('mysubset', self.data.id['cube'] > 0.5)
self.test_slice(tmpdir, coords)

@pytest.mark.parametrize('coords', [None, 'wcs', 'affine'])
def test_subset_transposed(self, tmpdir, coords):
self.viewer_load(coords)
self.data_collection.new_subset_group('mysubset', self.data.id['cube'] > 0.5)
self.viewer.state.x_att = self.data.pixel_component_ids[0]
self.viewer.state.y_att = self.data.pixel_component_ids[1]
self.assert_same(tmpdir)
62 changes: 62 additions & 0 deletions glue/viewers/matplotlib/tests/test_python_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
import sys
import pytest
import subprocess

from glue.config import settings

import numpy as np

from matplotlib.testing.compare import compare_images

__all__ = ['random_with_nan', 'BaseTestExportPython']


def random_with_nan(nsamples, nan_index):
x = np.random.random(nsamples)
x[nan_index] = np.nan
return x


class BaseTestExportPython:

def assert_same(self, tmpdir, tol=0.1):

os.chdir(tmpdir.strpath)

expected = tmpdir.join('expected.png').strpath
script = tmpdir.join('actual.py').strpath
actual = tmpdir.join('glue_plot.png').strpath

self.viewer.axes.figure.savefig(expected)

self.viewer.export_as_script(script)
subprocess.call([sys.executable, script])

msg = compare_images(expected, actual, tol=tol)

if msg:

from base64 import b64encode

print("SCRIPT:")
with open(script, 'r') as f:
print(f.read())

print("EXPECTED:")
with open(expected, 'rb') as f:
print(b64encode(f.read()).decode())

print("ACTUAL:")
with open(actual, 'rb') as f:
print(b64encode(f.read()).decode())

pytest.fail(msg, pytrace=False)

def test_color_settings(self, tmpdir):
settings.FOREGROUND_COLOR = '#a51d2d'
settings.BACKGROUND_COLOR = '#99c1f1'
self.viewer._update_appearance_from_settings()
self.assert_same(tmpdir)
settings.reset_defaults()
self.viewer._update_appearance_from_settings()
87 changes: 87 additions & 0 deletions glue/viewers/profile/tests/test_python_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from astropy.utils import NumpyRNGContext

from glue.core import Data, DataCollection
from glue.core.application_base import Application
from glue.viewers.profile.viewer import SimpleProfileViewer
from glue.viewers.matplotlib.tests.test_python_export import BaseTestExportPython, random_with_nan
from glue.viewers.profile.tests.test_state import SimpleCoordinates


class TestExportPython(BaseTestExportPython):

def setup_method(self, method):

self.data = Data(label='d1')
self.data.coords = SimpleCoordinates()
with NumpyRNGContext(12345):
self.data['x'] = random_with_nan(48, 5).reshape((6, 4, 2))
self.data['y'] = random_with_nan(48, 12).reshape((6, 4, 2))
self.data_collection = DataCollection([self.data])
self.app = Application(self.data_collection)
self.viewer = self.app.new_data_viewer(SimpleProfileViewer)
self.viewer.add_data(self.data)
# Make legend location deterministic
self.viewer.state.legend.location = 'lower left'

def teardown_method(self, method):
self.viewer = None
self.app = None

def test_simple(self, tmpdir):
self.assert_same(tmpdir)

def test_simple_legend(self, tmpdir):
self.viewer.state.legend.visible = True
self.assert_same(tmpdir)

def test_color(self, tmpdir):
self.viewer.state.layers[0].color = '#ac0567'
self.assert_same(tmpdir)

def test_linewidth(self, tmpdir):
self.viewer.state.layers[0].linewidth = 7.25
self.assert_same(tmpdir)

def test_max(self, tmpdir):
self.viewer.state.function = 'maximum'
self.assert_same(tmpdir)

def test_min(self, tmpdir):
self.viewer.state.function = 'minimum'
self.assert_same(tmpdir)

def test_mean(self, tmpdir):
self.viewer.state.function = 'mean'
self.assert_same(tmpdir)

def test_median(self, tmpdir):
self.viewer.state.function = 'median'
self.assert_same(tmpdir)

def test_sum(self, tmpdir):
self.viewer.state.function = 'sum'
self.assert_same(tmpdir)

def test_normalization(self, tmpdir):
self.viewer.state.normalize = True
self.assert_same(tmpdir)

def test_subset(self, tmpdir):
self.viewer.state.function = 'mean'
self.data_collection.new_subset_group('mysubset', self.data.id['x'] > 0.25)
self.assert_same(tmpdir)

def test_subset_legend(self, tmpdir):
self.viewer.state.legend.visible = True
self.viewer.state.function = 'mean'
self.viewer.state.layers[0].linewidth = 7.25
self.data_collection.new_subset_group('mysubset', self.data.id['x'] > 0.25)
self.assert_same(tmpdir)

def test_xatt(self, tmpdir):
self.viewer.x_att = self.data.pixel_component_ids[1]
self.assert_same(tmpdir)

def test_profile_att(self, tmpdir):
self.viewer.layers[0].state.attribute = self.data.id['y']
self.assert_same(tmpdir)
Loading

0 comments on commit f356aeb

Please sign in to comment.