From be2f3761cee03ff61b563d785e317e8baea69666 Mon Sep 17 00:00:00 2001 From: Pey Lian Lim <2090236+pllim@users.noreply.github.com> Date: Thu, 8 Jul 2021 16:23:52 -0400 Subject: [PATCH 1/2] WIP: Rename marker to marker_style and change it to namedtuple --- astrowidgets/core.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/astrowidgets/core.py b/astrowidgets/core.py index 3e573a8..0b2bf24 100644 --- a/astrowidgets/core.py +++ b/astrowidgets/core.py @@ -3,6 +3,7 @@ # STDLIB import functools import warnings +from collections import namedtuple # THIRD-PARTY import numpy as np @@ -21,7 +22,7 @@ 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] @@ -29,6 +30,10 @@ # 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'], + defaults=['circle', 'cyan', 20]) + class ImageWidget(ipyw.VBox): """ @@ -131,7 +136,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.marker_style = MarkerStyle() # Maintain marker tags as a set because we do not want # duplicate names. self._marktags = set() @@ -436,7 +441,7 @@ def is_marking(self): return self._is_marking def start_marking(self, marker_name=None, - marker=None): + marker_style=None): """ Start marking, with option to name this set of markers or to specify the marker style. @@ -456,8 +461,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.marker_style = marker_style def stop_marking(self, clear_markers=False): """ @@ -480,7 +485,7 @@ def stop_marking(self, clear_markers=False): self.reset_markers() @property - def marker(self): + def marker_style(self): """ Marker to use. @@ -488,23 +493,25 @@ def marker(self): Marker can be set as follows:: - {'type': 'circle', 'color': 'cyan', 'radius': 20} - {'type': 'cross', 'color': 'green', 'radius': 20} - {'type': 'plus', 'color': 'red', 'radius': 20} + MarkerStyle(type='circle', color='cyan', radius=20) + MarkerStyle(type='cross', color='green', radius=20) + MarkerStyle(type='plus', color='red', radius=20) """ # 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') + # That makes things like self.marker_style = self.marker_style work. + return self._marker_style + + @marker_style.setter + def 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': @@ -519,7 +526,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', From a960144c5b6b068245727cd09c53bad5fbf879cc Mon Sep 17 00:00:00 2001 From: Pey Lian Lim <2090236+pllim@users.noreply.github.com> Date: Thu, 8 Jul 2021 17:03:38 -0400 Subject: [PATCH 2/2] Remove marker_style setter and let it be defined via method calls. --- astrowidgets/core.py | 47 ++++++++++++++------------- astrowidgets/tests/test_api.py | 18 +++++----- example_notebooks/ginga_widget.ipynb | 22 ++++++++----- example_notebooks/named_markers.ipynb | 20 +++++------- 4 files changed, 57 insertions(+), 50 deletions(-) diff --git a/astrowidgets/core.py b/astrowidgets/core.py index 0b2bf24..e7d397b 100644 --- a/astrowidgets/core.py +++ b/astrowidgets/core.py @@ -31,8 +31,18 @@ RESERVED_MARKER_SET_NAMES = ['all'] # Marker style (might be backend specific) -MarkerStyle = namedtuple('MarkerStyle', ['type', 'color', 'radius'], - defaults=['circle', 'cyan', 20]) +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): @@ -136,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_style = MarkerStyle() + self._validate_and_set_marker_style(MarkerStyle()) # Maintain marker tags as a set because we do not want # duplicate names. self._marktags = set() @@ -440,8 +450,7 @@ def is_marking(self): """ return self._is_marking - def start_marking(self, marker_name=None, - marker_style=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. @@ -462,7 +471,7 @@ def start_marking(self, marker_name=None, self._interactive_marker_set_name = \ self._interactive_marker_set_name_default if marker_style is not None: - self.marker_style = marker_style + self._validate_and_set_marker_style(marker_style) def stop_marking(self, clear_markers=False): """ @@ -486,27 +495,13 @@ def stop_marking(self, clear_markers=False): @property def marker_style(self): - """ - Marker to use. - - .. todo:: Add more examples. - - 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) - - """ + """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_style = self.marker_style work. return self._marker_style - @marker_style.setter - def marker_style(self, val): + def _validate_and_set_marker_style(self, val): if not isinstance(val, MarkerStyle): raise TypeError('marker style must be defined using MarkerStyle') @@ -671,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. @@ -700,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 @@ -711,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) diff --git a/astrowidgets/tests/test_api.py b/astrowidgets/tests/test_api.py index 37113e5..fde478c 100644 --- a/astrowidgets/tests/test_api.py +++ b/astrowidgets/tests/test_api.py @@ -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(): @@ -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 @@ -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(): diff --git a/example_notebooks/ginga_widget.ipynb b/example_notebooks/ginga_widget.ipynb index c422fca..77c4e2f 100644 --- a/example_notebooks/ginga_widget.ipynb +++ b/example_notebooks/ginga_widget.ipynb @@ -20,7 +20,7 @@ "metadata": {}, "outputs": [], "source": [ - "from astrowidgets import ImageWidget" + "from astrowidgets import ImageWidget, MarkerStyle" ] }, { @@ -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, @@ -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))" ] }, { @@ -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())" ] }, { @@ -596,7 +602,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.9.5" } }, "nbformat": 4, diff --git a/example_notebooks/named_markers.ipynb b/example_notebooks/named_markers.ipynb index 0e724d2..a103844 100644 --- a/example_notebooks/named_markers.ipynb +++ b/example_notebooks/named_markers.ipynb @@ -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" @@ -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." ] }, { @@ -95,7 +95,7 @@ "metadata": {}, "outputs": [], "source": [ - "imw.marker = {'color': 'cyan', 'radius': 20, 'type': 'circle'}" + "my_marker = MarkerStyle(color='cyan', radius=20, type='circle')" ] }, { @@ -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)" ] }, { @@ -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'))" ] }, { @@ -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')" ] }, { @@ -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'))" ] }, { @@ -285,7 +283,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.9.5" } }, "nbformat": 4,