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

feat: table errors #110

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
86 changes: 51 additions & 35 deletions examples/04_Importing_Tables.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
17 changes: 16 additions & 1 deletion js/models/message_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,28 @@ 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"]],
);
if (errorDict["r"] && s.data[errorDict["r"]["col"]])
return A.circle(s.ra, s.dec, s.data[errorDict["r"]["col"]]);
};
A.catalogFromURL(
url,
Object.assign(options, { onClick: "showTable" }),
options,
(catalog) => {
this.aladin.addCatalog(catalog);
},
Expand Down
82 changes: 82 additions & 0 deletions src/ipyaladin/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
It allows to display astronomical images and catalogs in an interactive way.
"""

import math
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
Expand All @@ -19,6 +21,7 @@
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.wcs import WCS
import numpy as np
import traitlets
Expand Down Expand Up @@ -633,6 +636,72 @@ 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: 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():
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] = (
Angle(row[col_name], unit=unit_from).to(unit_to).value
)
table[col_name].unit = unit_to

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.

Expand All @@ -646,6 +715,19 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None:
<https://cds-astro.github.io/aladin-lite/global.html#CatalogOptions>`_

"""
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"):
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")
self.send(
Expand Down
Loading