From 84ff138bed2333b52364f55357912814b815be06 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 7 Aug 2024 10:33:40 +0200 Subject: [PATCH 1/5] :sparkles: Implement oriented ellipse error type in add_table method --- js/models/message_handler.js | 15 ++++++++++++++- src/ipyaladin/widget.py | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/js/models/message_handler.js b/js/models/message_handler.js index b3def3a..8a6afa5 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -136,13 +136,26 @@ export default class MessageHandler { handleAddTable(msg, buffers) { const options = convertOptionNamesToCamelCase(msg["options"] || {}); + const errorDict = options.errorDict; const buffer = buffers[0].buffer; const decoder = new TextDecoder("utf-8"); const blob = new Blob([decoder.decode(buffer)]); const url = URL.createObjectURL(blob); + options.onClick = "showTable"; + if (errorDict) + options.shape = (s) => { + if (errorDict["pa"] && s.data[errorDict["pa"]["col"]]) + return A.ellipse( + s.ra, + s.dec, + s.data[errorDict["smaj"]["col"]], + s.data[errorDict["smin"]["col"]], + s.data[errorDict["pa"]["col"]], + ); + }; A.catalogFromURL( url, - Object.assign(options, { onClick: "showTable" }), + options, (catalog) => { this.aladin.addCatalog(catalog); }, diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 2d06681..89ad615 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -19,6 +19,8 @@ from astropy.table import Table from astropy.io import fits as astropy_fits from astropy.io.fits import HDUList +import astropy.units as u +from astropy.units import Unit from astropy.wcs import WCS import numpy as np import traitlets @@ -633,6 +635,23 @@ def add_moc_from_dict( moc_options = {} self.add_moc(moc_dict, **moc_options) + def _convert_table_units( + self, table: Union[QTable, Table], error_dict: Dict, unit_to: Unit = u.deg + ) -> Union[QTable, Table]: + table = table.copy() + for _, error_spec in error_dict.values(): + col_name = error_spec["col"] + unit_from = error_spec["unit"] + table[col_name].unit = unit_from + for row in table: + if row[col_name] != "--" and not np.isnan(row[col_name]): + row[col_name] = ( + Angle(row[col_name], unit=unit_from).to(unit_to).value + ) + table[col_name].unit = unit_to + + return table + def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: """Load a table into the widget. @@ -646,6 +665,11 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: `_ """ + if table_options.get("error_dict"): + table = self._convert_table_units(table, table_options["error_dict"]) + # remove unit sub-key for all the keys + for key in table_options["error_dict"]: + table_options["error_dict"][key].pop("unit") table_bytes = io.BytesIO() table.write(table_bytes, format="votable") self.send( From 16eff2ab083714e9da2db29754a85b84c8b9cf54 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 7 Aug 2024 14:53:22 +0200 Subject: [PATCH 2/5] :sparkles: Implement radial error and ellipse enclosed probability value --- js/models/message_handler.js | 2 ++ src/ipyaladin/widget.py | 64 +++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/js/models/message_handler.js b/js/models/message_handler.js index 8a6afa5..df6279b 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -152,6 +152,8 @@ export default class MessageHandler { s.data[errorDict["smin"]["col"]], s.data[errorDict["pa"]["col"]], ); + if (errorDict["r"] && s.data[errorDict["r"]["col"]]) + return A.circle(s.ra, s.dec, s.data[errorDict["r"]["col"]]); }; A.catalogFromURL( url, diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 89ad615..8f703de 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -5,6 +5,7 @@ It allows to display astronomical images and catalogs in an interactive way. """ +import math from collections.abc import Callable import io import pathlib @@ -20,7 +21,6 @@ from astropy.io import fits as astropy_fits from astropy.io.fits import HDUList import astropy.units as u -from astropy.units import Unit from astropy.wcs import WCS import numpy as np import traitlets @@ -636,13 +636,33 @@ def add_moc_from_dict( self.add_moc(moc_dict, **moc_options) def _convert_table_units( - self, table: Union[QTable, Table], error_dict: Dict, unit_to: Unit = u.deg + self, table: Union[QTable, Table], error_dict: Dict, unit_to: u.Unit = u.deg ) -> Union[QTable, Table]: + """Convert the units of a table according to the error_dict. + + Parameters + ---------- + table : astropy.table.table.QTable or astropy.table.table.Table + The table to convert. + error_dict : dict + The dictionary containing the error specifications. + unit_to : astropy.units.Unit + The unit to convert to. Default is degrees. + + Returns + ------- + astropy.table.table.QTable or astropy.table.table.Table + The table with the units converted. + + """ table = table.copy() - for _, error_spec in error_dict.values(): + for error_spec in error_dict.values(): + if not isinstance(error_spec, dict): + continue col_name = error_spec["col"] unit_from = error_spec["unit"] table[col_name].unit = unit_from + table[col_name] = table[col_name].astype(float) for row in table: if row[col_name] != "--" and not np.isnan(row[col_name]): row[col_name] = ( @@ -652,6 +672,35 @@ def _convert_table_units( return table + def _update_ellipse_enclosed_probability( + self, table: Union[QTable, Table], error_dict: Dict + ) -> Union[QTable, Table]: + """Update the table according to the ellipse_enclosed_probability. + + Parameters + ---------- + table : astropy.table.table.QTable or astropy.table.table.Table + The table to update. + error_dict : dict + The dictionary containing the error specifications. + + Returns + ------- + astropy.table.table.QTable or astropy.table.table.Table + The updated table. + + """ + table = table.copy() + r = math.sqrt(-2 * math.log(1 - error_dict["ellipse_enclosed_probability"])) + impacted_keys = {"smin", "smaj", "r"} + for key in impacted_keys: + if not error_dict.get(key): + continue + # Multiply table column by r + table[error_dict[key]["col"]] = table[error_dict[key]["col"]] * r + + return table + def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: """Load a table into the widget. @@ -667,8 +716,15 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: """ if table_options.get("error_dict"): table = self._convert_table_units(table, table_options["error_dict"]) - # remove unit sub-key for all the keys + # if dict contains ellipse_enclosed_probability, update the table + if table_options["error_dict"].get("ellipse_enclosed_probability"): + table = self._update_ellipse_enclosed_probability( + table, table_options["error_dict"] + ) + # Remove unit sub-key for all the keys for key in table_options["error_dict"]: + if key == "ellipse_enclosed_probability": + continue table_options["error_dict"][key].pop("unit") table_bytes = io.BytesIO() table.write(table_bytes, format="votable") From c273a49e81ad45e1d472fb758a12c3830d5f0a05 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Thu, 8 Aug 2024 09:27:12 +0200 Subject: [PATCH 3/5] :bug: Fix bug that modify the original error dictionary --- src/ipyaladin/widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 8f703de..35aba22 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -9,6 +9,7 @@ from collections.abc import Callable import io import pathlib +from copy import deepcopy from json import JSONDecodeError from pathlib import Path from typing import ClassVar, Dict, Final, List, Optional, Tuple, Union @@ -715,6 +716,7 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: """ if table_options.get("error_dict"): + table_options["error_dict"] = deepcopy(table_options["error_dict"]) table = self._convert_table_units(table, table_options["error_dict"]) # if dict contains ellipse_enclosed_probability, update the table if table_options["error_dict"].get("ellipse_enclosed_probability"): From b3ba03cc6bac4c48256138868ee70c5942a0231b Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Thu, 8 Aug 2024 09:27:43 +0200 Subject: [PATCH 4/5] :memo: Update example 4 with new error plotting --- examples/04_Importing_Tables.ipynb | 86 ++++++++++++++++++------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/examples/04_Importing_Tables.ipynb b/examples/04_Importing_Tables.ipynb index cd5df9a..c30fa7b 100644 --- a/examples/04_Importing_Tables.ipynb +++ b/examples/04_Importing_Tables.ipynb @@ -99,45 +99,61 @@ "source": [ "aladin.add_table(t)" ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.3" + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Display the table with the error approximations\n", + "First, let's re-use the table from the Simbad query and specify the error columns and units, and the ellipse enclosed probability.\n", + "### Note\n", + "Ipyaladin only support oriented ellipse and radial errors for the moment.\n", + "Radial error can be specified with the `r` column in the table." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "error_dict = {\n", + " \"smaj\": {\"col\": \"coo_err_maj\", \"unit\": u.mas},\n", + " \"smin\": {\"col\": \"coo_err_min\", \"unit\": u.mas},\n", + " \"pa\": {\"col\": \"coo_err_angle\", \"unit\": u.deg},\n", + " # Let's the default value for the ellipse enclosed probability\n", + " \"ellipse_enclosed_probability\": 0.39347,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then add the table to the Aladin widget" + ] }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": false, - "sideBar": false, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": false, - "toc_window_display": false + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aladin2 = Aladin(target=\"M1\", fov=0.2)\n", + "aladin2" + ] }, - "vscode": { - "interpreter": { - "hash": "85bb43f988bdbdc027a50b6d744a62eda8a76617af1f4f9b115d38242716dbac" - } + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aladin2.add_table(table, error_dict=error_dict)" + ] } - }, + ], + "metadata": {}, "nbformat": 4, "nbformat_minor": 4 } From 30395e4a2f61101af34b3ce3d978327c136d5cd0 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Thu, 8 Aug 2024 12:54:44 +0200 Subject: [PATCH 5/5] :memo: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ae3ae..b7317b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add selected sources export as `astropy.Table` list with property `selected_objects` (#100) - Add function `get_view_as_fits` to export the view as a `astropy.io.fits.HDUList` (#86) - Add function `save_view_as_image` to save the view as an image file (#108) +- Add option to `add_table` to display the error of the sources (#110) ### Deprecated