Skip to content

Commit 210d772

Browse files
authored
Merge pull request #162 from mwcraig/move-to-protocol
Move interface definition from base class to protocol
2 parents 2c6c338 + e9afc48 commit 210d772

File tree

8 files changed

+653
-475
lines changed

8 files changed

+653
-475
lines changed

astrowidgets/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
from ._astropy_init import * # noqa
77
# ----------------------------------------------------------------------------
88

9-
from .core import * # noqa
9+
from .ginga import * # noqa

astrowidgets/core.py renamed to astrowidgets/ginga.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
from ginga.web.jupyterw.ImageViewJpw import EnhancedCanvasView
2222
from ginga.util.wcs import ra_deg_to_str, dec_deg_to_str
2323

24-
__all__ = ['ImageWidget']
25-
26-
# Allowed locations for cursor display
27-
ALLOWED_CURSOR_LOCATIONS = ['top', 'bottom', None]
24+
from astrowidgets.interface_definition import (
25+
ALLOWED_CURSOR_LOCATIONS,
26+
RESERVED_MARKER_SET_NAMES
27+
)
2828

29-
# List of marker names that are for internal use only
30-
RESERVED_MARKER_SET_NAMES = ['all']
29+
__all__ = ['ImageWidget']
3130

3231

3332
class ImageWidget(ipyw.VBox):
@@ -55,6 +54,11 @@ class ImageWidget(ipyw.VBox):
5554
correct value to use.*
5655
5756
"""
57+
# Allowed locations for cursor display
58+
ALLOWED_CURSOR_LOCATIONS = ALLOWED_CURSOR_LOCATIONS
59+
60+
# List of marker names that are for internal use only
61+
RESERVED_MARKER_SET_NAMES = RESERVED_MARKER_SET_NAMES
5862

5963
def __init__(self, logger=None, image_width=500, image_height=500,
6064
pixel_coords_offset=0, **kwargs):
@@ -565,6 +569,9 @@ def get_markers(self, x_colname='x', y_colname='y',
565569
del table[skycoord_colname]
566570
tables.append(table)
567571

572+
if len(tables) == 0:
573+
return None
574+
568575
stacked = vstack(tables, join_type='exact')
569576

570577
if coordinates:
@@ -648,11 +655,11 @@ def _validate_marker_name(self, marker_name):
648655
"""
649656
Raise an error if the marker_name is not allowed.
650657
"""
651-
if marker_name in RESERVED_MARKER_SET_NAMES:
658+
if marker_name in self.RESERVED_MARKER_SET_NAMES:
652659
raise ValueError('The marker name {} is not allowed. Any name is '
653660
'allowed except these: '
654661
'{}'.format(marker_name,
655-
', '.join(RESERVED_MARKER_SET_NAMES)))
662+
', '.join(self.RESERVED_MARKER_SET_NAMES)))
656663

657664
def add_markers(self, table, x_colname='x', y_colname='y',
658665
skycoord_colname='coord', use_skycoord=False,
@@ -888,7 +895,7 @@ def cursor(self, val):
888895
else:
889896
raise ValueError('Invalid value {} for cursor.'
890897
'Valid values are: '
891-
'{}'.format(val, ALLOWED_CURSOR_LOCATIONS))
898+
'{}'.format(val, self.ALLOWED_CURSOR_LOCATIONS))
892899
self._cursor = val
893900

894901
@property
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
from typing import Protocol, runtime_checkable, Any
2+
from abc import abstractmethod
3+
4+
# Allowed locations for cursor display
5+
ALLOWED_CURSOR_LOCATIONS = ('top', 'bottom', None)
6+
7+
# List of marker names that are for internal use only
8+
RESERVED_MARKER_SET_NAMES = ('all',)
9+
10+
__all__ = [
11+
'ImageViewerInterface',
12+
'ALLOWED_CURSOR_LOCATIONS',
13+
'RESERVED_MARKER_SET_NAMES'
14+
]
15+
16+
17+
@runtime_checkable
18+
class ImageViewerInterface(Protocol):
19+
# These are attributes, not methods. The type annotations are there
20+
# to make sure Protocol knows they are attributes. Python does not
21+
# do any checking at all of these types.
22+
click_center: bool
23+
click_drag: bool
24+
scroll_pan: bool
25+
image_width: int
26+
image_height: int
27+
zoom_level: float
28+
is_marking: bool
29+
stretch_options: tuple
30+
autocut_options: tuple
31+
cursor: str
32+
marker: Any
33+
cuts: Any
34+
stretch: str
35+
# viewer: Any
36+
37+
# Allowed locations for cursor display
38+
ALLOWED_CURSOR_LOCATIONS: tuple = ALLOWED_CURSOR_LOCATIONS
39+
40+
# List of marker names that are for internal use only
41+
RESERVED_MARKER_SET_NAMES: tuple = RESERVED_MARKER_SET_NAMES
42+
43+
# The methods, grouped loosely by purpose
44+
45+
# Methods for loading data
46+
@abstractmethod
47+
def load_fits(self, file):
48+
"""
49+
Load a FITS file into the viewer.
50+
51+
Parameters
52+
----------
53+
file : str or `astropy.io.fits.HDU`
54+
The FITS file to load. If a string, it can be a URL or a
55+
file path.
56+
"""
57+
raise NotImplementedError
58+
59+
@abstractmethod
60+
def load_array(self, array):
61+
"""
62+
Load a 2D array into the viewer.
63+
64+
Parameters
65+
----------
66+
array : array-like
67+
The array to load.
68+
"""
69+
raise NotImplementedError
70+
71+
@abstractmethod
72+
def load_nddata(self, data):
73+
"""
74+
Load an `astropy.nddata.NDData` object into the viewer.
75+
76+
Parameters
77+
----------
78+
data : `astropy.nddata.NDData`
79+
The NDData object to load.
80+
"""
81+
raise NotImplementedError
82+
83+
# Saving contents of the view and accessing the view
84+
@abstractmethod
85+
def save(self, filename):
86+
"""
87+
Save the current view to a file.
88+
89+
Parameters
90+
----------
91+
filename : str
92+
The file to save to. The format is determined by the
93+
extension.
94+
"""
95+
raise NotImplementedError
96+
97+
# Marker-related methods
98+
@abstractmethod
99+
def start_marking(self, marker_name=None):
100+
"""
101+
Start interactive marking of points on the image.
102+
103+
Parameters
104+
----------
105+
marker_name : str, optional
106+
The name of the marker set to use. If not given, a unique
107+
name will be generated.
108+
"""
109+
raise NotImplementedError
110+
111+
@abstractmethod
112+
def stop_marking(self, clear_markers=False):
113+
"""
114+
Stop interactive marking of points on the image.
115+
116+
Parameters
117+
----------
118+
clear_markers : bool, optional
119+
If `True`, clear the markers that were created during
120+
interactive marking. Default is `False`.
121+
"""
122+
raise NotImplementedError
123+
124+
@abstractmethod
125+
def add_markers(self, table, x_colname='x', y_colname='y',
126+
skycoord_colname='coord', use_skycoord=False,
127+
marker_name=None):
128+
"""
129+
Add markers to the image.
130+
131+
Parameters
132+
----------
133+
table : `astropy.table.Table`
134+
The table containing the marker positions.
135+
x_colname : str, optional
136+
The name of the column containing the x positions. Default
137+
is ``'x'``.
138+
y_colname : str, optional
139+
The name of the column containing the y positions. Default
140+
is ``'y'``.
141+
skycoord_colname : str, optional
142+
The name of the column containing the sky coordinates. If
143+
given, the ``use_skycoord`` parameter is ignored. Default
144+
is ``'coord'``.
145+
use_skycoord : bool, optional
146+
If `True`, the ``skycoord_colname`` column will be used to
147+
get the marker positions. Default is `False`.
148+
marker_name : str, optional
149+
The name of the marker set to use. If not given, a unique
150+
name will be generated.
151+
"""
152+
raise NotImplementedError
153+
154+
# @abstractmethod
155+
# def remove_all_markers(self):
156+
# raise NotImplementedError
157+
158+
@abstractmethod
159+
def reset_markers(self):
160+
"""
161+
Remove all markers from the image.
162+
"""
163+
raise NotImplementedError
164+
165+
# @abstractmethod
166+
# def remove_markers_by_name(self, marker_name=None):
167+
# raise NotImplementedError
168+
169+
@abstractmethod
170+
def remove_markers(self, marker_name=None):
171+
"""
172+
Remove markers from the image.
173+
174+
Parameters
175+
----------
176+
marker_name : str, optional
177+
The name of the marker set to remove. If not given, all
178+
markers will be removed.
179+
"""
180+
raise NotImplementedError
181+
182+
# @abstractmethod
183+
# def get_all_markers(self):
184+
# raise NotImplementedError
185+
186+
@abstractmethod
187+
def get_markers(self, x_colname='x', y_colname='y',
188+
skycoord_colname='coord',
189+
marker_name=None):
190+
"""
191+
Get the marker positions.
192+
193+
Parameters
194+
----------
195+
x_colname : str, optional
196+
The name of the column containing the x positions. Default
197+
is ``'x'``.
198+
y_colname : str, optional
199+
The name of the column containing the y positions. Default
200+
is ``'y'``.
201+
skycoord_colname : str, optional
202+
The name of the column containing the sky coordinates. Default
203+
is ``'coord'``.
204+
marker_name : str, optional
205+
The name of the marker set to use. If not given, all
206+
markers will be returned.
207+
208+
Returns
209+
-------
210+
table : `astropy.table.Table`
211+
The table containing the marker positions.
212+
"""
213+
raise NotImplementedError
214+
215+
# Methods that modify the view
216+
@abstractmethod
217+
def center_on(self, point):
218+
"""
219+
Center the view on the point.
220+
221+
Parameters
222+
----------
223+
tuple or `~astropy.coordinates.SkyCoord`
224+
If tuple of ``(X, Y)`` is given, it is assumed
225+
to be in data coordinates.
226+
"""
227+
raise NotImplementedError
228+
229+
@abstractmethod
230+
def offset_by(self, dx, dy):
231+
"""
232+
Move the center to a point that is given offset
233+
away from the current center.
234+
235+
Parameters
236+
----------
237+
dx, dy : float or `~astropy.unit.Quantity`
238+
Offset value. Without a unit, assumed to be pixel offsets.
239+
If a unit is attached, offset by pixel or sky is assumed from
240+
the unit.
241+
"""
242+
raise NotImplementedError
243+
244+
@abstractmethod
245+
def zoom(self):
246+
"""
247+
Zoom in or out by the given factor.
248+
249+
Parameters
250+
----------
251+
val : int
252+
The zoom level to zoom the image.
253+
See `zoom_level`.
254+
"""
255+
raise NotImplementedError

0 commit comments

Comments
 (0)