Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markers API refactor #145

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 38 additions & 30 deletions astrowidgets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# STDLIB
import functools
import warnings
from collections import namedtuple

# THIRD-PARTY
import numpy as np
Expand All @@ -21,14 +22,28 @@
from ginga.web.jupyterw.ImageViewJpw import EnhancedCanvasView
from ginga.util.wcs import raDegToString, decDegToString

__all__ = ['ImageWidget']
__all__ = ['ImageWidget', 'MarkerStyle']

# 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']

# Marker style (might be backend specific)
MarkerStyle = namedtuple(
'MarkerStyle', ['type', 'color', 'radius', 'linewidth'],
defaults=['circle', 'cyan', 20, 1])
# TODO: Add more examples
MarkerStyle.__doc__ += """

Marker can be set as follows::

MarkerStyle(type='circle', color='cyan', radius=20)
MarkerStyle(type='cross', color='green', radius=20)
MarkerStyle(type='plus', color='red', radius=20)
"""


class ImageWidget(ipyw.VBox):
"""
Expand Down Expand Up @@ -131,7 +146,7 @@ def __init__(self, logger=None, image_width=500, image_height=500,
bind_map.map_event(None, ('shift',), 'ms_right', 'contrast_restore')

# Marker
self.marker = {'type': 'circle', 'color': 'cyan', 'radius': 20}
self._validate_and_set_marker_style(MarkerStyle())
# Maintain marker tags as a set because we do not want
# duplicate names.
self._marktags = set()
Expand Down Expand Up @@ -435,8 +450,7 @@ def is_marking(self):
"""
return self._is_marking

def start_marking(self, marker_name=None,
marker=None):
def start_marking(self, marker_name=None, marker_style=None):
"""
Start marking, with option to name this set of markers or
to specify the marker style.
Expand All @@ -456,8 +470,8 @@ def start_marking(self, marker_name=None,
else:
self._interactive_marker_set_name = \
self._interactive_marker_set_name_default
if marker is not None:
self.marker = marker
if marker_style is not None:
self._validate_and_set_marker_style(marker_style)

def stop_marking(self, clear_markers=False):
"""
Expand All @@ -480,31 +494,19 @@ def stop_marking(self, clear_markers=False):
self.reset_markers()

@property
def marker(self):
"""
Marker to use.

.. todo:: Add more examples.

Marker can be set as follows::

{'type': 'circle', 'color': 'cyan', 'radius': 20}
{'type': 'cross', 'color': 'green', 'radius': 20}
{'type': 'plus', 'color': 'red', 'radius': 20}

"""
def marker_style(self):
"""Current marker style in use."""
# Change the marker from a very ginga-specific type (a partial
# of a ginga drawing canvas type) to a generic dict, which is
# what we expect the user to provide.
#
# That makes things like self.marker = self.marker work.
return self._marker_dict

@marker.setter
def marker(self, val):
# Make a new copy to avoid modifying the dict that the user passed in.
_marker = val.copy()
marker_type = _marker.pop('type')
return self._marker_style

def _validate_and_set_marker_style(self, val):
if not isinstance(val, MarkerStyle):
raise TypeError('marker style must be defined using MarkerStyle')

_marker = val._asdict()
marker_type = val.type
if marker_type == 'circle':
self._marker = functools.partial(self.dc.Circle, **_marker)
elif marker_type == 'plus':
Expand All @@ -519,7 +521,7 @@ def marker(self, val):
raise NotImplementedError(
'Marker type "{}" not supported'.format(marker_type))
# Only set this once we have successfully created a marker
self._marker_dict = val
self._marker_style = val

def get_markers(self, x_colname='x', y_colname='y',
skycoord_colname='coord',
Expand Down Expand Up @@ -664,7 +666,7 @@ def _validate_marker_name(self, marker_name):

def add_markers(self, table, x_colname='x', y_colname='y',
skycoord_colname='coord', use_skycoord=False,
marker_name=None):
marker_name=None, marker_style=None):
"""
Creates markers in the image at given points.

Expand Down Expand Up @@ -693,6 +695,10 @@ def add_markers(self, table, x_colname='x', y_colname='y',
marker_name : str, optional
Name to assign the markers in the table. Providing a name
allows markers to be removed by name at a later time.

marker_style: `MarkerStyle`, optional
Marker style to use. If not given, use the current style.

"""
# TODO: Resolve https://github.com/ejeschke/ginga/issues/672

Expand All @@ -704,6 +710,8 @@ def add_markers(self, table, x_colname='x', y_colname='y',
marker_name = self._default_mark_tag_name

self._validate_marker_name(marker_name)
if marker_style is not None:
self._validate_and_set_marker_style(marker_style)

self._marktags.add(marker_name)

Expand Down
18 changes: 10 additions & 8 deletions astrowidgets/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from ginga.ColorDist import ColorDistBase

from ..core import ImageWidget, ALLOWED_CURSOR_LOCATIONS
from ..core import ImageWidget, MarkerStyle, ALLOWED_CURSOR_LOCATIONS


def test_load_fits():
Expand Down Expand Up @@ -116,11 +116,11 @@ def test_start_marking():
image.scroll_pan = False
assert not image.scroll_pan

marker_style = {'color': 'yellow', 'radius': 10, 'type': 'cross'}
marker_style = MarkerStyle(color='yellow', radius=10, type='cross')
image.start_marking(marker_name='something',
marker=marker_style)
marker_style=marker_style)
assert image.is_marking
assert image.marker == marker_style
assert image.marker_style == marker_style
assert not image.click_center
assert not image.click_drag

Expand Down Expand Up @@ -152,10 +152,12 @@ def test_add_markers():

def test_set_markers():
image = ImageWidget()
image.marker = {'color': 'yellow', 'radius': 10, 'type': 'cross'}
assert 'cross' in str(image.marker)
assert 'yellow' in str(image.marker)
assert '10' in str(image.marker)
marker = MarkerStyle(color='yellow', radius=10, type='cross')
image.start_marking(marker_style=marker)
assert image.marker_style.type == 'cross'
assert image.marker_style.color == 'yellow'
assert image.marker_style.radius == 10
assert image.marker_style.linewidth == 1


def test_reset_markers():
Expand Down
22 changes: 14 additions & 8 deletions example_notebooks/ginga_widget.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"metadata": {},
"outputs": [],
"source": [
"from astrowidgets import ImageWidget"
"from astrowidgets import ImageWidget, MarkerStyle"
]
},
{
Expand Down Expand Up @@ -417,6 +417,15 @@
"The following works even when we have set `w.is_marking=False`. This is because `w.is_marking` only controls the interactive marking and does not affect marking programmatically."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"w.marker_style"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -426,12 +435,9 @@
"# Programmatically re-mark from table using X, Y.\n",
"# To be fancy, first 2 points marked as bigger\n",
"# and thicker red circles.\n",
"w.marker = {'type': 'circle', 'color': 'red', 'radius': 50,\n",
" 'linewidth': 2}\n",
"w.add_markers(markers_table[:2])\n",
"w.add_markers(markers_table[:2], marker_style=MarkerStyle(type='circle', color='red', radius=50, linewidth=2))\n",
"# You can also change the type of marker to cross or plus\n",
"w.marker = {'type': 'cross', 'color': 'cyan', 'radius': 20}\n",
"w.add_markers(markers_table[2:])"
"w.add_markers(markers_table[2:], marker_style=MarkerStyle(type='cross', color='cyan', radius=20))"
]
},
{
Expand All @@ -444,7 +450,7 @@
"w.reset_markers()\n",
"\n",
"# Programmatically re-mark from table using SkyCoord\n",
"w.add_markers(markers_table, use_skycoord=True)"
"w.add_markers(markers_table, use_skycoord=True, marker_style=MarkerStyle())"
]
},
{
Expand Down Expand Up @@ -596,7 +602,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
"version": "3.9.5"
}
},
"nbformat": 4,
Expand Down
20 changes: 9 additions & 11 deletions example_notebooks/named_markers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"metadata": {},
"outputs": [],
"source": [
"from astrowidgets import ImageWidget\n",
"from astrowidgets import ImageWidget, MarkerStyle\n",
"from astropy.table import Table \n",
"\n",
"import numpy as np"
Expand Down Expand Up @@ -67,7 +67,7 @@
"source": [
"## Add markers from a table and name them\n",
"\n",
"Rather than pull real markers from a catalog the positions are random for this demonatration."
"Rather than pull real markers from a catalog the positions are random for this demonstration."
]
},
{
Expand Down Expand Up @@ -95,7 +95,7 @@
"metadata": {},
"outputs": [],
"source": [
"imw.marker = {'color': 'cyan', 'radius': 20, 'type': 'circle'}"
"my_marker = MarkerStyle(color='cyan', radius=20, type='circle')"
]
},
{
Expand All @@ -113,7 +113,7 @@
"metadata": {},
"outputs": [],
"source": [
"imw.add_markers(fake_positions, marker_name='cyan 20')"
"imw.add_markers(fake_positions, marker_name='cyan 20', marker_style=my_marker)"
]
},
{
Expand All @@ -130,8 +130,7 @@
"outputs": [],
"source": [
"fake_pos2 = fake_pos_table(ccd)\n",
"imw.marker = {'color': 'yellow', 'radius': 10, 'type': 'circle'}\n",
"imw.add_markers(fake_pos2, marker_name='yellow 10')"
"imw.add_markers(fake_pos2, marker_name='yellow 10', marker_style=MarkerStyle(color='yellow', radius=10, type='circle'))"
]
},
{
Expand All @@ -149,9 +148,8 @@
"metadata": {},
"outputs": [],
"source": [
"imw.start_marking(marker={'color': 'red', 'radius': 30, 'type': 'circle'},\n",
" marker_name='clicked markers'\n",
" )"
"imw.start_marking(marker_style=MarkerStyle(color='red', radius=30, type='circle'),\n",
" marker_name='clicked markers')"
]
},
{
Expand Down Expand Up @@ -208,7 +206,7 @@
"metadata": {},
"outputs": [],
"source": [
"imw.start_marking(marker={'color': 'red', 'radius': 10, 'type': 'cross'} )"
"imw.start_marking(marker_style=MarkerStyle(color='red', radius=10, type='cross'))"
]
},
{
Expand Down Expand Up @@ -285,7 +283,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
"version": "3.9.5"
}
},
"nbformat": 4,
Expand Down