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

Merge Astropy Angle support to Master #83

Merged
merged 11 commits into from
Apr 29, 2024
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Support for `astropy.coordinates.SkyCoord` for assigning and reading the `target` property (#80)
- Support for `astropy.coordinates.Angle` for reading the `fov` property (#83)

### Fixed

Expand All @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Change the jslink target trait from `target` to `shared_target` (#80)
- Change the jslink fov trait from `fov` to `shared_fov` (#83)

## [0.3.0]

Expand All @@ -37,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the last clicked object (ra, dec, and catalog content)
- Each ipyaladin version now point to a specific Aladin-lite version instead of the latest available version
- add_table takes new arguments (documented here https://cds-astro.github.io/aladin-lite/Catalog.Catalog.html)
- the new method `add_moc` can take mocpy.MOC objects, URLs, or the dictionnary serialization of a MOC. This will replace `moc_from_URL` and `moc_from_dict` in the future.
- the new method `add_moc` can take mocpy.MOC objects, URLs, or the dictionary serialization of a MOC. This will replace `moc_from_URL` and `moc_from_dict` in the future.

## [0.2.6]

Expand Down
2 changes: 1 addition & 1 deletion examples/10_Advanced-GUI.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"\n",
"\n",
"zoom_slider = widgets.FloatSlider(\n",
" value=180 / aladin.fov,\n",
" value=180 / aladin.fov.deg,\n",
" min=1,\n",
" max=400,\n",
" step=1,\n",
Expand Down
4 changes: 2 additions & 2 deletions examples/6_Linked-widgets.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"widgets.jslink((b, \"shared_target\"), (c, \"shared_target\"))\n",
"\n",
"# synchronize FoV (zoom level) between 3 widgets\n",
"widgets.jslink((a, \"fov\"), (b, \"fov\"))\n",
"widgets.jslink((b, \"fov\"), (c, \"fov\"))\n",
"widgets.jslink((a, \"shared_fov\"), (b, \"shared_fov\"))\n",
"widgets.jslink((b, \"shared_fov\"), (c, \"shared_fov\"))\n",
"\n",
"items = [a, b, c]\n",
"\n",
Expand Down
2 changes: 1 addition & 1 deletion examples/7_on-click-callback.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
" dec,\n",
" ra,\n",
" dec,\n",
" aladin.fov / 10,\n",
" aladin.fov.deg / 10,\n",
" )\n",
"\n",
" r = requests.get(\n",
Expand Down
32 changes: 18 additions & 14 deletions js/widget.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import A from "https://esm.sh/aladin-lite@3.3.3-beta";
import A from "https://esm.sh/aladin-lite@3.4.0-beta";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to remove the goToRaDec after initialization with this bump.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this should be in the changelog and in the readme

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the version change, the removing of gotoRaDec call at the initialization re-introduce the patched bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad, thanks for testing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the correspondence table at the end of the README, could you add a line with unreleased || 3.4.0-beta?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because when you instanciate aladin with:

let aladin = Aladin({target: '0 0', cooFrame: 'galactic', ...})

as there is no frame given with the target position string, it will take that position as what is given in the cooFrame.

@tboch - Should we consider that behaviour as okaish or target should be always considered as given in ICRS frame ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the correspondence table at the end of the README, could you add a line with unreleased || 3.4.0-beta?

Done !

import "./widget.css";

let idxView = 0;
Expand Down Expand Up @@ -85,24 +85,25 @@ function render({ model, el }) {
let fov_js = false;

aladin.on("zoomChanged", (fov) => {
if (!fov_py) {
fov_js = true;
// fov MUST be cast into float in order to be sent to the model
model.set("fov", parseFloat(fov.toFixed(5)));
model.save_changes();
} else {
if (fov_py) {
fov_py = false;
return;
}
fov_js = true;
// fov MUST be cast into float in order to be sent to the model
model.set("_fov", parseFloat(fov.toFixed(5)));
model.set("shared_fov", parseFloat(fov.toFixed(5)));
model.save_changes();
});

model.on("change:fov", () => {
if (!fov_js) {
fov_py = true;
let fov = model.get("fov");
aladin.setFoV(fov);
} else {
model.on("change:shared_fov", () => {
if (fov_js) {
fov_js = false;
return;
}
fov_py = true;
let fov = model.get("shared_fov");
aladin.setFoV(fov);
});

/* Div control */
Expand Down Expand Up @@ -193,6 +194,9 @@ function render({ model, el }) {
model.on("msg:custom", (msg) => {
let options = {};
switch (msg["event_name"]) {
case "change_fov":
aladin.setFoV(msg["fov"]);
break;
case "goto_ra_dec":
const ra = msg["ra"];
const dec = msg["dec"];
Expand Down Expand Up @@ -252,7 +256,7 @@ function render({ model, el }) {
return () => {
// need to unsubscribe the listeners
model.off("change:shared_target");
model.off("change:fov");
model.off("change:shared_fov");
model.off("change:height");
model.off("change:coo_frame");
model.off("change:survey");
Expand Down
36 changes: 34 additions & 2 deletions src/ipyaladin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import warnings

import anywidget
from astropy.coordinates import SkyCoord
from astropy.coordinates import SkyCoord, Angle
from traitlets import (
Float,
Int,
Expand Down Expand Up @@ -43,7 +43,17 @@ class Aladin(anywidget.AnyWidget):
help="A trait that can be used with `~ipywidgets.widgets.jslink`"
"to link two Aladin Lite widgets targets together",
).tag(sync=True)
fov = Float(60.0).tag(sync=True, init_option=True)
_fov = Float(
60.0,
help="A private trait that stores the current field of view of the widget."
" Its public version is the 'fov' property that returns an "
"`~astropy.units.Angle` object",
).tag(sync=True, init_option=True)
shared_fov = Float(
60.0,
help="A trait that can be used with `~ipywidgets.widgets.jslink`"
"to link two Aladin Lite widgets field of view together",
).tag(sync=True)
survey = Unicode("https://alaskybis.unistra.fr/DSS/DSSColor").tag(
sync=True, init_option=True
)
Expand Down Expand Up @@ -99,6 +109,7 @@ def _init_options(self):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.target = kwargs.get("target", "0 0")
self.fov = kwargs.get("fov", 60.0)
self.on_msg(self._handle_custom_message)

def _handle_custom_message(self, model, message, list_of_buffers): # noqa: ARG002
Expand All @@ -119,6 +130,27 @@ def _handle_custom_message(self, model, message, list_of_buffers): # noqa: ARG0
elif event_type == "select" and "select" in self.listener_callback:
self.listener_callback["select"](message_content)

@property
def fov(self) -> Angle:
"""The field of view of the Aladin Lite widget.

It can be set with either a float or an `~astropy.units.Angle` object.
Xen0Xys marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Angle
An astropy.units.Angle object representing the field of view.

"""
return Angle(self._fov, unit="deg")
Xen0Xys marked this conversation as resolved.
Show resolved Hide resolved

@fov.setter
def fov(self, fov: Union[float, Angle]):
if isinstance(fov, Angle):
fov = fov.deg
self._fov = fov
self.send({"event_name": "change_fov", "fov": fov})

@property
def target(self) -> SkyCoord:
"""The target of the Aladin Lite widget.
Expand Down
107 changes: 107 additions & 0 deletions src/test/test_aladin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import pytest
from astropy.coordinates import Angle

from ipyaladin import Aladin, parse_coordinate_string

test_aladin_string_target = [
"M 31",
"sgr a*",
"α Centauri", # noqa RUF001
"* 17 Com",
"1:12:43.2 31:12:43",
"1:12:43.2 +31:12:43",
"1:12:43.2 -31:12:43",
"1 12 43.2 31 12 43",
"1 12 43.2 +31 12 43",
"1 12 43.2 -31 12 43",
"1h12m43.2s 1d12m43s",
"1h12m43.2s +1d12m43s",
"1h12m43.2s -1d12m43s",
"42.67 25.48",
"42.67 +25.48",
"42.67 -25.48",
"0 0",
"J42.67 25.48",
"G42.67 25.48",
"B42.67 25.48",
"J12 30 45 +45 30 15",
"J03 15 20 -10 20 30",
"G120.5 -45.7",
"G90 0",
"B60 30",
"B120 -45",
]


@pytest.mark.parametrize("target", test_aladin_string_target)
def test_aladin_string_target_set(target):
"""Test setting the target of an Aladin object with a string or a SkyCoord object.

Parameters
----------
target : str
The target string.

"""
aladin = Aladin()
aladin.target = target
parsed_target = parse_coordinate_string(target)
assert aladin.target.icrs.ra.deg == parsed_target.icrs.ra.deg
assert aladin.target.icrs.dec.deg == parsed_target.icrs.dec.deg


@pytest.mark.parametrize("target", test_aladin_string_target)
def test_aladin_sky_coord_target_set(target):
"""Test setting and getting the target of an Aladin object with a SkyCoord object.

Parameters
----------
target : str
The target string.

"""
sc_target = parse_coordinate_string(target)
aladin = Aladin()
aladin.target = sc_target
assert aladin.target.icrs.ra.deg == sc_target.icrs.ra.deg
assert aladin.target.icrs.dec.deg == sc_target.icrs.dec.deg


test_aladin_float_fov = [
0,
360,
180,
-180,
720,
]


@pytest.mark.parametrize("angle", test_aladin_float_fov)
def test_aladin_float_fov_set(angle):
"""Test setting the angle of an Aladin object with a float.

Parameters
----------
angle : float
The angle to set.

"""
aladin = Aladin()
aladin.fov = angle
assert aladin.fov.deg == angle


@pytest.mark.parametrize("angle", test_aladin_float_fov)
def test_aladin_angle_fov_set(angle):
"""Test setting the angle of an Aladin object with an Angle object.

Parameters
----------
angle : float
The angle to set.

"""
angle_fov = Angle(angle, unit="deg")
aladin = Aladin()
aladin.fov = angle_fov
assert aladin.fov.deg == angle_fov.deg
65 changes: 0 additions & 65 deletions src/test/test_coordinate_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from ipyaladin import Aladin
from ipyaladin.coordinate_parser import (
parse_coordinate_string,
_split_coordinate_string,
Expand Down Expand Up @@ -188,67 +187,3 @@ def test_parse_coordinate_string(inp, expected):

"""
assert parse_coordinate_string(inp) == expected


test_aladin_string_target = [
"M 31",
"sgr a*",
"α Centauri", # noqa RUF001
"* 17 Com",
"1:12:43.2 31:12:43",
"1:12:43.2 +31:12:43",
"1:12:43.2 -31:12:43",
"1 12 43.2 31 12 43",
"1 12 43.2 +31 12 43",
"1 12 43.2 -31 12 43",
"1h12m43.2s 1d12m43s",
"1h12m43.2s +1d12m43s",
"1h12m43.2s -1d12m43s",
"42.67 25.48",
"42.67 +25.48",
"42.67 -25.48",
"0 0",
"J42.67 25.48",
"G42.67 25.48",
"B42.67 25.48",
"J12 30 45 +45 30 15",
"J03 15 20 -10 20 30",
"G120.5 -45.7",
"G90 0",
"B60 30",
"B120 -45",
]


@pytest.mark.parametrize("target", test_aladin_string_target)
def test_aladin_string_target_set(target):
"""Test setting the target of an Aladin object with a string or a SkyCoord object.

Parameters
----------
target : str
The target string.

"""
aladin = Aladin()
aladin.target = target
parsed_target = parse_coordinate_string(target)
assert aladin.target.icrs.ra.deg == parsed_target.icrs.ra.deg
assert aladin.target.icrs.dec.deg == parsed_target.icrs.dec.deg


@pytest.mark.parametrize("target", test_aladin_string_target)
def test_aladin_sky_coord_target_set(target):
"""Test setting and getting the target of an Aladin object with a SkyCoord object.

Parameters
----------
target : str
The target string.

"""
sc_target = parse_coordinate_string(target)
aladin = Aladin()
aladin.target = sc_target
assert aladin.target.icrs.ra.deg == sc_target.icrs.ra.deg
assert aladin.target.icrs.dec.deg == sc_target.icrs.dec.deg