Skip to content

Commit cab2c48

Browse files
authored
Merge pull request #113 from astrofrog/hips-2d
Added 2-d HiPS data class
2 parents a5b440b + b359da4 commit cab2c48

File tree

10 files changed

+273
-6
lines changed

10 files changed

+273
-6
lines changed

.circleci/config.yml

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,92 @@
11
version: 2.1
2+
23
jobs:
3-
build:
4+
5+
# The following job is to run any visual comparison test, and runs on any branch
6+
# or in any pull request. It will generate a summary page for each tox environment
7+
# being run which is accessible through the CircleCI artifacts.
8+
9+
visual:
10+
parameters:
11+
jobname:
12+
type: string
413
docker:
5-
- image: cimg/base:stable
14+
- image: cimg/python:3.11
15+
environment:
16+
TOXENV: << parameters.jobname >>
617
steps:
718
- checkout
19+
- run:
20+
name: Install dependencies
21+
command: |
22+
sudo apt update
23+
pip install pip tox --upgrade
24+
- run:
25+
name: Run tests
26+
command: tox -v
27+
- store_artifacts:
28+
path: results
29+
- run:
30+
name: "Image comparison page is available at: "
31+
command: echo "${CIRCLE_BUILD_URL}/artifacts/${CIRCLE_NODE_INDEX}/results/fig_comparison.html"
32+
33+
# The following job runs only on main - and its main purpose is to update the
34+
# reference images in the glue-astronomy-visual-tests repository. This job needs
35+
# a deploy key. To produce this, go to the glue-astronomy-visual-tests
36+
# repository settings and go to SSH keys, then add your public SSH key.
37+
deploy-reference-images:
38+
parameters:
39+
jobname:
40+
type: string
41+
docker:
42+
- image: cimg/python:3.11
43+
environment:
44+
TOXENV: << parameters.jobname >>
45+
steps:
46+
- checkout
47+
- run:
48+
name: Install dependencies
49+
command: |
50+
sudo apt update
51+
pip install pip tox --upgrade
52+
- run: ssh-add -D
53+
- add_ssh_keys:
54+
fingerprints: "44:09:69:d7:c6:77:25:e9:46:da:f1:22:7d:d4:38:29"
55+
- run: ssh-keyscan github.com >> ~/.ssh/known_hosts
56+
- run: git config --global user.email "glue@circleci" && git config --global user.name "Glue Circle CI"
57+
- run: git clone [email protected]:glue-viz/glue-astronomy-visual-tests.git --depth 1 ~/glue-astronomy-visual-tests/
58+
- run:
59+
name: Generate reference images
60+
command: tox -v -- --mpl-generate-path=/home/circleci/glue-astronomy-visual-tests/images/$TOXENV
61+
- run: |
62+
cd ~/glue-astronomy-visual-tests/
63+
git pull
64+
git status
65+
git add .
66+
git commit -m "Update reference images from ${CIRCLE_BRANCH}" || echo "No changes to reference images to deploy"
67+
git push
68+
869
workflows:
9-
build_and_test:
70+
version: 2
71+
72+
visual-tests:
1073
jobs:
11-
- build
74+
- visual:
75+
name: << matrix.jobname >>
76+
matrix:
77+
parameters:
78+
jobname:
79+
- "py311-test-visual"
80+
81+
- deploy-reference-images:
82+
name: baseline-<< matrix.jobname >>
83+
matrix:
84+
parameters:
85+
jobname:
86+
- "py311-test-visual"
87+
requires:
88+
- << matrix.jobname >>
89+
filters:
90+
branches:
91+
only:
92+
- main

.ruff.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ lint.unfixable = []
104104
# flake8-blind-except (BLE001)
105105
# Pylint collapsible-else-if (PLR5501)
106106
"glue_astronomy/io/spectral_cube/spectral_cube.py" = ["BLE001", "PLR5501"]
107+
"glue_astronomy/data/hips.py" = ["PLR0913", "FBT002", "A002"]
107108

108109
# flake8-bugbear (B904): RaiseWithoutFromInsideExcept
109110
# mccabe (C90): code complexity

glue_astronomy/data/__init__.py

Whitespace-only changes.

glue_astronomy/data/hips.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import numpy as np
2+
3+
from glue.core.component_id import ComponentID
4+
from glue.core.data import BaseCartesianData
5+
from glue.utils import compute_statistic
6+
from glue.core.fixed_resolution_buffer import compute_fixed_resolution_buffer
7+
8+
9+
class HiPSData(BaseCartesianData):
10+
11+
def __init__(self, directory_or_url, *, label):
12+
from reproject.hips import hips_as_dask_array
13+
self._array, self._wcs = hips_as_dask_array(directory_or_url)
14+
self._dask_arrays = []
15+
# Determine order from array shape
16+
self._order = int(np.log2(self._array.shape[0] / 5 / self._array.chunksize[0]))
17+
for level in range(self._order):
18+
self._dask_arrays.append(hips_as_dask_array(directory_or_url, level=level)[0])
19+
self._dask_arrays.append(self._array)
20+
self.data_cid = ComponentID(label="values", parent=self)
21+
self._label = label
22+
super().__init__()
23+
24+
@property
25+
def label(self):
26+
return self._label
27+
28+
@property
29+
def coords(self):
30+
return self._wcs
31+
32+
@property
33+
def shape(self):
34+
return self._array.shape
35+
36+
@property
37+
def main_components(self):
38+
return [self.data_cid]
39+
40+
def get_kind(self, cid):
41+
return "numerical"
42+
43+
def get_data(self, cid, view=None):
44+
if cid is self.data_cid:
45+
if view is None:
46+
raise NotImplementedError("View must be specified for HiPS data")
47+
if isinstance(view, tuple):
48+
if len(view) == 2:
49+
i, j = view
50+
i = i.ravel()
51+
j = j.ravel()
52+
# Only keep non-zero pixels for now
53+
keep = (i > 0) & (j > 0)
54+
i = i[keep]
55+
j = j[keep]
56+
# Determine minimal separation between pixels. Pick any
57+
# pixel and use it as a reference pixel, then find the
58+
# minimum separation from any other pixel to that one.
59+
iref, jref = i[0], j[0]
60+
sep = np.hypot(i[1:] - iref, j[1:] - jref)
61+
min_sep = np.min(sep[sep > 0])
62+
# Now that we have min_sep, we can determine which level
63+
# to use. If the minimum separation is larger than e.g.
64+
# 2 we can use order - 1, and so on.
65+
level = max(0, self._order - int(np.log2(min_sep)))
66+
factor = 2 ** int(self._order - level)
67+
inew, jnew = view
68+
inew = inew // factor
69+
jnew = jnew // factor
70+
return self._dask_arrays[level].vindex[inew, jnew].compute()
71+
else:
72+
raise ValueError("View must be a tuple of two arrays")
73+
raise NotImplementedError("View must be specified for HiPS data")
74+
return super().get_data(cid, view=view)
75+
76+
def get_mask(self, subset_state, view=None):
77+
return subset_state.to_mask(self, view=view)
78+
79+
def compute_fixed_resolution_buffer(self, *args, **kwargs):
80+
return compute_fixed_resolution_buffer(self, *args, **kwargs)
81+
82+
def compute_statistic(
83+
self,
84+
statistic,
85+
cid,
86+
axis=None,
87+
finite=True,
88+
positive=False,
89+
subset_state=None,
90+
percentile=None,
91+
random_subset=None,
92+
):
93+
data = self._dask_arrays[0].compute()
94+
return compute_statistic(
95+
statistic, data, axis=axis, percentile=percentile, finite=finite
96+
)
97+
98+
def compute_histogram(
99+
self,
100+
cid,
101+
range=None,
102+
bins=None,
103+
log=False,
104+
subset_state=None,
105+
subset_group=None,
106+
):
107+
108+
raise NotImplementedError("Histogram computation not implemented for HiPS data")

glue_astronomy/data/tests/__init__.py

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import pytest
2+
3+
import numpy as np
4+
from astropy.wcs import WCS
5+
from glue_astronomy.data.hips import HiPSData
6+
from glue.tests.visual.helpers import visual_test
7+
from glue.viewers.image.viewer import SimpleImageViewer
8+
from glue.core.application_base import Application
9+
from echo import delay_callback
10+
11+
try:
12+
from reproject import reproject_interp
13+
from reproject.hips import reproject_to_hips
14+
except ImportError:
15+
pytest.skip(allow_module_level=True)
16+
17+
18+
@pytest.fixture(scope="session")
19+
def example_hips_dataset(tmp_path_factory):
20+
21+
array = np.arange(20000).reshape((100, 200))
22+
23+
wcs = WCS(naxis=2)
24+
wcs.wcs.ctype = 'RA---TAN', 'DEC--TAN'
25+
wcs.wcs.crval = 20, 40
26+
wcs.wcs.cdelt = -0.02, 0.02
27+
wcs.wcs.crpix = 30, 35
28+
29+
hips_directory = tmp_path_factory.mktemp('hips') / 'hips'
30+
31+
reproject_to_hips((array, wcs), output_directory=hips_directory,
32+
coord_system_out='equatorial',
33+
reproject_function=reproject_interp, level=3)
34+
35+
return hips_directory
36+
37+
38+
@visual_test
39+
def test_hips_data_image(example_hips_dataset):
40+
41+
hips_data = HiPSData(example_hips_dataset, label='HiPS Data')
42+
43+
app = Application()
44+
app.data_collection.append(hips_data)
45+
46+
viewer = app.new_data_viewer(SimpleImageViewer)
47+
viewer.add_data(hips_data)
48+
49+
with delay_callback(viewer.state, 'x_min', 'x_max', 'y_min', 'y_max'):
50+
viewer.state.x_min = 11000
51+
viewer.state.x_max = 11600
52+
viewer.state.y_min = 7000
53+
viewer.state.y_max = 7800
54+
55+
app.data_collection.new_subset_group(label='subset1',
56+
subset_state=hips_data.main_components[0] > 10000)
57+
app.data_collection.new_subset_group(label='subset2',
58+
subset_state=hips_data.pixel_component_ids[1] > 11400)
59+
60+
return viewer.figure
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"glue_astronomy.data.tests.test_hips.test_hips_data_image": "6377b20b9adf086d459b934f61ed2a431636b7880449a09b70610f6fe9b76f8b"
3+
}

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,8 @@ known-first-party = ["glue", "glue_astronomy"]
6565

6666
[tool.ruff.lint.pydocstyle]
6767
convention = "numpy"
68+
69+
[tool.gilesbot.circleci_artifacts.figure_report]
70+
url = "results/fig_comparison.html"
71+
message = "Click details to see the image test comparisons, for py311-test-visual"
72+
report_on_fail = true

setup.cfg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ install_requires =
2323
specutils>=1.20
2424
specreduce>=1.0.0
2525
spectral-cube>=0.6.0
26+
reproject>=0.16.0 ; python_version>='3.11'
2627

2728
[options.extras_require]
2829
docs =
@@ -38,6 +39,8 @@ test =
3839
mock
3940
qt =
4041
PyQt5
42+
visualtest =
43+
pytest-mpl
4144

4245
[options.entry_points]
4346
glue.plugins =
@@ -57,6 +60,9 @@ filterwarnings =
5760
ignore:numpy\.ndarray size changed:RuntimeWarning
5861
ignore:`product` is deprecated
5962
ignore:The isiterable function is deprecated and may be removed in a future version
63+
ignore:CoordinateHelper
64+
markers =
65+
mpl_image_compare
6066

6167
[flake8]
6268
max-line-length = 100

tox.ini

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[tox]
2-
envlist = py{310,311,312,313}-{test,docs,codestyle}-{dev}
2+
envlist = py{310,311,312,313}-{test,docs,codestyle}{,visual}-{dev}
33
requires = pip >= 18.0
44
setuptools >= 30.3.0
55

66
[testenv]
77
setenv =
88
dev: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
9+
visual: MPLFLAGS = -m "mpl_image_compare" --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/results --mpl-hash-library={toxinidir}/glue_astronomy/tests/visual/{envname}.json --mpl-baseline-path=https://raw.githubusercontent.com/glue-viz/glue-astronomy-visual-tests/main/images/{envname}/
910
passenv =
1011
HOME
1112
changedir =
@@ -17,16 +18,18 @@ deps =
1718
dev: git+https://github.com/astropy/regions.git
1819
dev: git+https://github.com/astropy/specutils.git
1920
dev: git+https://github.com/astropy/specreduce.git
21+
dev: git+https://github.com/astropy/reproject.git
2022
dev: git+https://github.com/glue-viz/glue.git
2123
dev: git+https://github.com/glue-viz/glue-qt.git
2224
dev: git+https://github.com/radio-astro-tools/spectral-cube.git
2325
dev: git+https://github.com/radio-astro-tools/radio-beam.git
2426
extras =
2527
test: test
2628
docs: docs
29+
visual: visualtest
2730
commands =
2831
pip freeze
29-
test: pytest glue_astronomy --cov glue_astronomy {posargs}
32+
test: pytest glue_astronomy --cov glue_astronomy {env:MPLFLAGS} {posargs}
3033
docs: sphinx-build -W -b html -d _build/doctrees . _build/html
3134

3235
[testenv:codestyle]

0 commit comments

Comments
 (0)