Skip to content

Commit

Permalink
Merge pull request #162 from mwcraig/move-to-protocol
Browse files Browse the repository at this point in the history
Move interface definition from base class to protocol
  • Loading branch information
mwcraig authored Jun 26, 2023
2 parents 2c6c338 + e9afc48 commit 210d772
Show file tree
Hide file tree
Showing 8 changed files with 653 additions and 475 deletions.
2 changes: 1 addition & 1 deletion astrowidgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
from ._astropy_init import * # noqa
# ----------------------------------------------------------------------------

from .core import * # noqa
from .ginga import * # noqa
25 changes: 16 additions & 9 deletions astrowidgets/core.py → astrowidgets/ginga.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
from ginga.web.jupyterw.ImageViewJpw import EnhancedCanvasView
from ginga.util.wcs import ra_deg_to_str, dec_deg_to_str

__all__ = ['ImageWidget']

# Allowed locations for cursor display
ALLOWED_CURSOR_LOCATIONS = ['top', 'bottom', None]
from astrowidgets.interface_definition import (
ALLOWED_CURSOR_LOCATIONS,
RESERVED_MARKER_SET_NAMES
)

# List of marker names that are for internal use only
RESERVED_MARKER_SET_NAMES = ['all']
__all__ = ['ImageWidget']


class ImageWidget(ipyw.VBox):
Expand Down Expand Up @@ -55,6 +54,11 @@ class ImageWidget(ipyw.VBox):
correct value to use.*
"""
# Allowed locations for cursor display
ALLOWED_CURSOR_LOCATIONS = ALLOWED_CURSOR_LOCATIONS

# List of marker names that are for internal use only
RESERVED_MARKER_SET_NAMES = RESERVED_MARKER_SET_NAMES

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

if len(tables) == 0:
return None

stacked = vstack(tables, join_type='exact')

if coordinates:
Expand Down Expand Up @@ -648,11 +655,11 @@ def _validate_marker_name(self, marker_name):
"""
Raise an error if the marker_name is not allowed.
"""
if marker_name in RESERVED_MARKER_SET_NAMES:
if marker_name in self.RESERVED_MARKER_SET_NAMES:
raise ValueError('The marker name {} is not allowed. Any name is '
'allowed except these: '
'{}'.format(marker_name,
', '.join(RESERVED_MARKER_SET_NAMES)))
', '.join(self.RESERVED_MARKER_SET_NAMES)))

def add_markers(self, table, x_colname='x', y_colname='y',
skycoord_colname='coord', use_skycoord=False,
Expand Down Expand Up @@ -888,7 +895,7 @@ def cursor(self, val):
else:
raise ValueError('Invalid value {} for cursor.'
'Valid values are: '
'{}'.format(val, ALLOWED_CURSOR_LOCATIONS))
'{}'.format(val, self.ALLOWED_CURSOR_LOCATIONS))
self._cursor = val

@property
Expand Down
255 changes: 255 additions & 0 deletions astrowidgets/interface_definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
from typing import Protocol, runtime_checkable, Any
from abc import abstractmethod

# Allowed locations for cursor display
ALLOWED_CURSOR_LOCATIONS = ('top', 'bottom', None)

# List of marker names that are for internal use only
RESERVED_MARKER_SET_NAMES = ('all',)

__all__ = [
'ImageViewerInterface',
'ALLOWED_CURSOR_LOCATIONS',
'RESERVED_MARKER_SET_NAMES'
]


@runtime_checkable
class ImageViewerInterface(Protocol):
# These are attributes, not methods. The type annotations are there
# to make sure Protocol knows they are attributes. Python does not
# do any checking at all of these types.
click_center: bool
click_drag: bool
scroll_pan: bool
image_width: int
image_height: int
zoom_level: float
is_marking: bool
stretch_options: tuple
autocut_options: tuple
cursor: str
marker: Any
cuts: Any
stretch: str
# viewer: Any

# Allowed locations for cursor display
ALLOWED_CURSOR_LOCATIONS: tuple = ALLOWED_CURSOR_LOCATIONS

# List of marker names that are for internal use only
RESERVED_MARKER_SET_NAMES: tuple = RESERVED_MARKER_SET_NAMES

# The methods, grouped loosely by purpose

# Methods for loading data
@abstractmethod
def load_fits(self, file):
"""
Load a FITS file into the viewer.
Parameters
----------
file : str or `astropy.io.fits.HDU`
The FITS file to load. If a string, it can be a URL or a
file path.
"""
raise NotImplementedError

@abstractmethod
def load_array(self, array):
"""
Load a 2D array into the viewer.
Parameters
----------
array : array-like
The array to load.
"""
raise NotImplementedError

@abstractmethod
def load_nddata(self, data):
"""
Load an `astropy.nddata.NDData` object into the viewer.
Parameters
----------
data : `astropy.nddata.NDData`
The NDData object to load.
"""
raise NotImplementedError

# Saving contents of the view and accessing the view
@abstractmethod
def save(self, filename):
"""
Save the current view to a file.
Parameters
----------
filename : str
The file to save to. The format is determined by the
extension.
"""
raise NotImplementedError

# Marker-related methods
@abstractmethod
def start_marking(self, marker_name=None):
"""
Start interactive marking of points on the image.
Parameters
----------
marker_name : str, optional
The name of the marker set to use. If not given, a unique
name will be generated.
"""
raise NotImplementedError

@abstractmethod
def stop_marking(self, clear_markers=False):
"""
Stop interactive marking of points on the image.
Parameters
----------
clear_markers : bool, optional
If `True`, clear the markers that were created during
interactive marking. Default is `False`.
"""
raise NotImplementedError

@abstractmethod
def add_markers(self, table, x_colname='x', y_colname='y',
skycoord_colname='coord', use_skycoord=False,
marker_name=None):
"""
Add markers to the image.
Parameters
----------
table : `astropy.table.Table`
The table containing the marker positions.
x_colname : str, optional
The name of the column containing the x positions. Default
is ``'x'``.
y_colname : str, optional
The name of the column containing the y positions. Default
is ``'y'``.
skycoord_colname : str, optional
The name of the column containing the sky coordinates. If
given, the ``use_skycoord`` parameter is ignored. Default
is ``'coord'``.
use_skycoord : bool, optional
If `True`, the ``skycoord_colname`` column will be used to
get the marker positions. Default is `False`.
marker_name : str, optional
The name of the marker set to use. If not given, a unique
name will be generated.
"""
raise NotImplementedError

# @abstractmethod
# def remove_all_markers(self):
# raise NotImplementedError

@abstractmethod
def reset_markers(self):
"""
Remove all markers from the image.
"""
raise NotImplementedError

# @abstractmethod
# def remove_markers_by_name(self, marker_name=None):
# raise NotImplementedError

@abstractmethod
def remove_markers(self, marker_name=None):
"""
Remove markers from the image.
Parameters
----------
marker_name : str, optional
The name of the marker set to remove. If not given, all
markers will be removed.
"""
raise NotImplementedError

# @abstractmethod
# def get_all_markers(self):
# raise NotImplementedError

@abstractmethod
def get_markers(self, x_colname='x', y_colname='y',
skycoord_colname='coord',
marker_name=None):
"""
Get the marker positions.
Parameters
----------
x_colname : str, optional
The name of the column containing the x positions. Default
is ``'x'``.
y_colname : str, optional
The name of the column containing the y positions. Default
is ``'y'``.
skycoord_colname : str, optional
The name of the column containing the sky coordinates. Default
is ``'coord'``.
marker_name : str, optional
The name of the marker set to use. If not given, all
markers will be returned.
Returns
-------
table : `astropy.table.Table`
The table containing the marker positions.
"""
raise NotImplementedError

# Methods that modify the view
@abstractmethod
def center_on(self, point):
"""
Center the view on the point.
Parameters
----------
tuple or `~astropy.coordinates.SkyCoord`
If tuple of ``(X, Y)`` is given, it is assumed
to be in data coordinates.
"""
raise NotImplementedError

@abstractmethod
def offset_by(self, dx, dy):
"""
Move the center to a point that is given offset
away from the current center.
Parameters
----------
dx, dy : float or `~astropy.unit.Quantity`
Offset value. Without a unit, assumed to be pixel offsets.
If a unit is attached, offset by pixel or sky is assumed from
the unit.
"""
raise NotImplementedError

@abstractmethod
def zoom(self):
"""
Zoom in or out by the given factor.
Parameters
----------
val : int
The zoom level to zoom the image.
See `zoom_level`.
"""
raise NotImplementedError
Loading

0 comments on commit 210d772

Please sign in to comment.