Skip to content

Commit 0a87db8

Browse files
committed
DOC: Update user-facing doc.
TST: Update test matrix and added a test. DOC: Re-organized doc and notebooks. DOC: Update example notebooks
1 parent 9800195 commit 0a87db8

File tree

16 files changed

+554
-231
lines changed

16 files changed

+554
-231
lines changed

.github/workflows/ci_workflows.yml

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,59 @@ on:
99
- cron: '0 5 * * 5'
1010

1111
jobs:
12-
pep8:
13-
runs-on: ubuntu-16.04
14-
steps:
15-
- name: Checkout code
16-
uses: actions/checkout@v2
17-
- name: Set up Python
18-
uses: actions/setup-python@v2
19-
with:
20-
python-version: '3.x'
21-
- name: Lint with flake8
22-
run: |
23-
python -m pip install --upgrade pip flake8
24-
flake8 astrowidgets --count
25-
2612
tests:
13+
name: ${{ matrix.name }}
2714
runs-on: ${{ matrix.os }}
2815
strategy:
2916
fail-fast: true
3017
matrix:
31-
os: [windows-latest, macos-latest, ubuntu-latest]
32-
steps:
33-
- name: Checkout code
34-
uses: actions/checkout@v2
35-
- name: Set up Python
36-
uses: actions/setup-python@v2
37-
with:
38-
python-version: '3.8'
39-
- name: Install and build
40-
run: python -m pip install tox --upgrade
41-
- name: Run tests
42-
run: tox -e test
18+
include:
19+
20+
- name: Code style checks
21+
os: ubuntu-latest
22+
python: 3.x
23+
toxenv: codestyle
24+
25+
- name: Python 3.9 with abstract only
26+
os: ubuntu-latest
27+
python: 3.9
28+
toxenv: py39-test
29+
30+
- name: Python 3.8 with Ginga backend (Linux)
31+
os: ubuntu-latest
32+
python: 3.8
33+
toxenv: py38-test-alldeps
34+
35+
- name: Python 3.9 with Ginga backend (Windows)
36+
os: windows-latest
37+
python: 3.9
38+
toxenv: py39-test-alldeps
39+
40+
- name: Python 3.7 with Ginga backend (OSX)
41+
os: macos-latest
42+
python: 3.7
43+
toxenv: py37-test-alldeps
44+
45+
- name: Python 3.9 with Ginga backend (dev)
46+
os: ubuntu-latest
47+
python: 3.9
48+
toxenv: py39-test-devdeps
49+
50+
- name: Python 3.6 with Ginga backend (old)
51+
os: ubuntu-latest
52+
python: 3.6
53+
toxenv: py36-test-oldestdeps
4354

44-
devtests:
45-
runs-on: ubuntu-latest
4655
steps:
4756
- name: Checkout code
4857
uses: actions/checkout@v2
58+
with:
59+
fetch-depth: 0
4960
- name: Set up Python
5061
uses: actions/setup-python@v2
5162
with:
52-
python-version: '3.x'
53-
- name: Install and build
54-
run: python -m pip install tox --upgrade
63+
python-version: ${{ matrix.python }}
64+
- name: Install Python dependencies
65+
run: python -m pip install --upgrade tox
5566
- name: Run tests
56-
run: tox -e test-devdeps
67+
run: tox -e ${{ matrix.toxenv }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ MANIFEST
1818
example_notebooks/test.png
1919
*/version.py
2020
pip-wheel-metadata/
21+
example_notebooks/ginga/test.png
2122

2223
# Sphinx
2324
docs/api
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Test to make sure astrowidgets can install and be used with
2+
only abstract class, without optional backend.
3+
4+
"""
5+
import pytest
6+
7+
from astrowidgets.core import BaseImageWidget
8+
9+
10+
class DummyWidget(BaseImageWidget):
11+
pass
12+
13+
14+
def test_abstract_no_imp():
15+
# Ensure subclass cannot be used without implementing all the abstracted
16+
# things.
17+
with pytest.raises(TypeError, match="Can't instantiate abstract class"):
18+
DummyWidget()

docs/abstract.rst

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
.. _abstract_widget_intro:
2+
3+
Understanding BaseImageWidget
4+
=============================
5+
6+
``astrowidgets`` provides an abstract class called
7+
`~astrowidgets.core.BaseImageWidget` to allow developers from different
8+
visualization tools (hereafter known as "backends") to implement their own
9+
solutions using the same set of API. This design is based on
10+
`nb-astroimage-api <https://github.com/eteq/nb-astroimage-api>`_, with the
11+
goal of making all functionality available by a compact and clear API.
12+
This API-first approach would allow manipulating the view programmatically
13+
in a reproducible way.
14+
15+
The idea of the abstract class is that ``astrowidgets`` users would be able
16+
to switch to the backend of their choice without much refactoring of their own.
17+
This would enable, say, astronomers with different backend preferences to
18+
collaborate more easily via Jupyter Lab/Notebook.
19+
20+
The following sub-sections lay out the envisioned high-level usage of
21+
``astrowidgets`` regardless of backend inside Jupyter Lab/Notebook.
22+
However, the examples are not exhaustive. For the full API definition,
23+
please see :ref:`abstract_api`.
24+
25+
.. _abstract_viewer:
26+
27+
Creating a Viewer
28+
-----------------
29+
30+
The snippet below is all you should need to make an image widget.
31+
The widget should be a part of the
32+
`ipywidgets framework <https://ipywidgets.rtfd.io>`_ so that it can
33+
be easily integrated with other controls:
34+
35+
.. code-block:: python
36+
37+
from astrowidgets.somebackend import ImageWidget
38+
image = ImageWidget()
39+
image
40+
41+
.. _abstract_image_load:
42+
43+
Loading an Image
44+
----------------
45+
46+
To load data into the empty viewer created in :ref:`abstract_viewer`,
47+
there should be methods to load different formats:
48+
49+
.. code-block:: python
50+
51+
# FITS image of the field of the exoplanet Kelt-16,
52+
# and also contains part of the Veil Nebula
53+
filename = 'https://zenodo.org/record/3356833/files/kelt-16-b-S001-R001-C084-r.fit.bz2?download=1'
54+
image.load_fits(filename)
55+
56+
.. code-block:: python
57+
58+
# A Numpy array
59+
import numpy as np
60+
arr = np.arange(100).reshape(10, 10)
61+
image.load_array(arr)
62+
63+
.. code-block:: python
64+
65+
# An astropy.nddata.NDData object
66+
from astropy.io import fits
67+
from astropy.nddata import NDData
68+
from astropy.wcs import WCS
69+
with fits.open(filename) as pf:
70+
data = NDData(pf[0].data, wcs=WCS(pf[0].header))
71+
image.load_nddata(data)
72+
73+
If additional format support is desired, the API could be added to the
74+
abstract base class if the new format is widely supported and not specific
75+
to a certain backend implementation.
76+
77+
.. _abstract_cursor_info:
78+
79+
Cursor Info Display
80+
-------------------
81+
82+
The widget actually consists of two child widgets:
83+
84+
* The image display.
85+
* Cursor information panel with the following:
86+
* X and Y cursor locations, taking
87+
`~astrowidgets.core.BaseImageWidget.pixel_offset` into account.
88+
* RA and Dec calculated from the cursor location using the image's WCS,
89+
if available. It is up to the backend on how to handle WCS projection
90+
or distortion.
91+
* Value of the image under the cursor.
92+
93+
The cursor information panel can have three different states:
94+
95+
* Positioned below the image display.
96+
* Positioned above the image display.
97+
* Not displayed.
98+
99+
This state can be set using the `~astrowidgets.core.BaseImageWidget.cursor`
100+
property.
101+
102+
.. _abstract_size:
103+
104+
Changing Display Size
105+
---------------------
106+
107+
There should be a programmatic way to change the display size of the display
108+
widget:
109+
110+
.. code-block:: python
111+
112+
# The height would auto-adjust
113+
image.image_width = 500 # pixels
114+
115+
# The width would auto-adjust
116+
image.image_height = 500 # pixels
117+
118+
.. _abstract_colormap:
119+
120+
Changing Colormap
121+
-----------------
122+
123+
There should be a programmatic way to change the colormap of the display.
124+
However, the available colormaps may differ from backend to backend:
125+
126+
.. code-block:: python
127+
128+
image.set_colormap('viridis')
129+
130+
.. _abstract_controls:
131+
132+
Mouse/Keyboard Controls
133+
-----------------------
134+
135+
Mouse interaction using clicks and scroll should be supported.
136+
Keyboard controls would also be desirable. These controls should be active
137+
when cursor is over the display, but not otherwise.
138+
For example, but not limited to:
139+
140+
* Scrolling to pan up/down the image.
141+
* Using ``+``/``-`` to zoom in/out.
142+
* Using click-and-drag to change the contrast of the image.
143+
144+
In the event where the same click/button can be overloaded, the active
145+
functionality can be controlled by the following properties:
146+
147+
* `~astrowidgets.core.BaseImageWidget.click_center`
148+
* `~astrowidgets.core.BaseImageWidget.click_drag`
149+
* `~astrowidgets.core.BaseImageWidget.scroll_pan`
150+
151+
There should be programmatic ways to perform these controls as well:
152+
153+
.. code-block:: python
154+
155+
# Centering on sky coordinates
156+
from astropy.coordinates import SkyCoord
157+
image.center_on(SkyCoord.from_name('kelt-16'))
158+
159+
# Centering on pixel coordinates
160+
image.center_on((100, 100))
161+
162+
# Moving the center using sky coordinates
163+
from astropy import units as u
164+
image.offset_to(0.1 * u.arcsec, 0.1 * u.arcsec, skycoord_offset=True)
165+
166+
# Moving the center by pixels
167+
image.offset_to(10, 10)
168+
169+
# Zooming (two different ways)
170+
image.zoom(2)
171+
image.zoom_level = 1
172+
173+
# Changing the display stretch
174+
image.stretch = 'log'
175+
176+
# Changing the cut levels (two different ways)
177+
image.cuts = 'histogram'
178+
image.cuts = (0, 10) # (low, high)
179+
180+
Please also see :ref:`abstract_marking`.
181+
182+
.. _abstract_marking:
183+
184+
Marking Objects
185+
---------------
186+
187+
Another important aspect is to allow users to either interactively or
188+
programmatically mark objects of interest on the displayed image.
189+
Marking mode is tracked using the
190+
`~astrowidgets.core.BaseImageWidget.is_marking`
191+
property and can be turned on and off using
192+
:meth:`~astrowidgets.core.BaseImageWidget.start_marking` and
193+
:meth:`~astrowidgets.core.BaseImageWidget.stop_marking`, respectively.
194+
The marker appearance can be changed using
195+
`~astrowidgets.core.BaseImageWidget.marker`.
196+
197+
For interactive marking, after a user runs ``start_marking`` but before
198+
``stop_marking``, a click on the image display would mark the object under
199+
the cursor.
200+
201+
For programmatic marking, user can first build a `~astopy.table.Table` with
202+
either pixel or sky coordinates, and then pass it into
203+
:meth:`~astrowidgets.core.BaseImageWidget.add_markers`.
204+
205+
User can then call
206+
:meth:`~astrowidgets.core.BaseImageWidget.get_markers_by_name` or
207+
:meth:`~astrowidgets.core.BaseImageWidget.get_all_markers` to obtain the
208+
marked locations.
209+
210+
To remove the markers, user can call
211+
:meth:`~astrowidgets.core.BaseImageWidget.remove_markers_by_name` or
212+
:meth:`~astrowidgets.core.BaseImageWidget.remove_all_markers`, as appropriate.
213+
214+
To put this all together, here is an example workflow (out of many)
215+
that may happen:
216+
217+
1. User calls ``start_marking`` to begin the interactive marking session.
218+
2. User clicks on two stars.
219+
3. User calls ``stop_marking`` to end the interactive marking session.
220+
4. User reads a table from a collaborator containing several galaxies in the
221+
field of view.
222+
5. User changes the marker style from a red circle to a green square by
223+
modifying the ``marker`` property.
224+
6. User programmatically marks the galaxies on display with the new marker style
225+
and a new marker name using ``add_markers``.
226+
7. User obtains all the marked locations for post-processing using
227+
``get_all_markers``.
228+
8. User removes all the markers from display using ``remove_all_markers``.
229+
230+
.. _abstract_save:
231+
232+
Saving an Image
233+
---------------
234+
235+
The image display can be programmatically saved to a file, but not the
236+
:ref:`abstract_cursor_info`. Supported output format is controlled by the
237+
individual backend. For example:
238+
239+
.. code-block:: python
240+
241+
image.save('myimage.png')
242+
243+
.. _example_notebooks:
244+
245+
Example Notebooks
246+
-----------------
247+
248+
Please see the `example notebooks folder <https://github.com/astropy/astrowidgets/blob/master/example_notebooks/>`_
249+
for examples using a concrete implementation of this abstract class.
250+
Backend-dependent dependencies are required to run them.
251+
252+
.. _abstract_api:
253+
254+
API
255+
---
256+
257+
.. automodapi:: astrowidgets
258+
:no-inheritance-diagram:

docs/astrowidgets/api.rst

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)