Skip to content

Commit bfa1abd

Browse files
committed
Add api and tests to repo
2 parents f96198b + c4246fb commit bfa1abd

File tree

2 files changed

+697
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)