Skip to content

Commit

Permalink
feat: adding FITS to the view with new add_fits method
Browse files Browse the repository at this point in the history
* 🚧 Add basic add_fits support

* 🎨 Add url revocation after fits is display

* ✨ Allow add_fits to load fits from path or HDUList object

* 🔇 Remove unnecessary logging

* 🎨 Improve Aladin Lite layer naming

* 🐛 Fix fits loading and potential errors with file handling

* 📝 Update changelog

* ✨ Allow user to give image options and define layer name using "name" option

* docs: add add_fits example

* docs: edit markdown cells

* ✨ Add new function to utils and fix Lock comments

* ✨ Add zero padding to default fits images naming

* 🔥 Remove padWithZero function

* conflict in notebook

* docs: edit changelog
  • Loading branch information
Xen0Xys authored Jul 16, 2024
1 parent 2454c0e commit 1a61e29
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- monochromatic FITS images can be added to the view with `ipyaladin.Aladin.add_fits`.
The method accepts `astropy.io.fits.HDUList`, `pathlib.Path`, or `string` representing paths (#86)

## [0.4.0]

### Added
Expand Down
53 changes: 46 additions & 7 deletions examples/2_Base_Commands.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ipyaladin import Aladin"
"from ipyaladin import Aladin\n",
"from pathlib import Path"
]
},
{
Expand Down Expand Up @@ -73,11 +74,18 @@
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"aladin.target"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The x-axis field of view (fov) can be set"
]
},
{
Expand All @@ -100,6 +108,13 @@
"aladin.fov"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The overlay survey is always on top of the base layer"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -112,6 +127,13 @@
"aladin.overlay_survey_opacity = 0.5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can change the coordinate frame (the choices are `ICRS`, `ICRSd` or `Galactic`)."
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -136,17 +158,17 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Some commands can be used with astropy objects"
"The target and field of view can be set with astropy objects"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from astropy.coordinates import Angle, SkyCoord"
]
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
Expand All @@ -165,6 +187,23 @@
"source": [
"aladin.fov = Angle(5, \"deg\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also add a FITS image to the view of the widget, either as a path (string of pathlib.Path object) or as an\n",
"astropy HDU object."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"aladin.add_fits(Path(\"images/m31.fits\"), name=\"M31\", opacity=0.5)"
]
}
],
"metadata": {
Expand Down
1 change: 1 addition & 0 deletions js/models/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export default class EventHandler {
this.eventHandlers = {
change_fov: this.messageHandler.handleChangeFoV,
goto_ra_dec: this.messageHandler.handleGotoRaDec,
add_fits: this.messageHandler.handleAddFits,
add_catalog_from_URL: this.messageHandler.handleAddCatalogFromURL,
add_MOC_from_URL: this.messageHandler.handleAddMOCFromURL,
add_MOC_from_dict: this.messageHandler.handleAddMOCFromDict,
Expand Down
17 changes: 17 additions & 0 deletions js/models/message_handler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { convertOptionNamesToCamelCase } from "../utils";
import A from "../aladin_lite";

let imageCount = 0;

export default class MessageHandler {
constructor(aladin) {
this.aladin = aladin;
Expand All @@ -14,6 +16,21 @@ export default class MessageHandler {
this.aladin.gotoRaDec(msg["ra"], msg["dec"]);
}

handleAddFits(msg, buffers) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
if (!options.name)
options.name = `image_${String(++imageCount).padStart(3, "0")}`;
const buffer = buffers[0];
const blob = new Blob([buffer], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const image = this.aladin.createImageFITS(url, options, (ra, dec) => {
this.aladin.gotoRaDec(ra, dec);
console.info(`FITS located at ra: ${ra}, dec: ${dec}`);
URL.revokeObjectURL(url);
});
this.aladin.setOverlayImageLayer(image, options.name);
}

handleAddCatalogFromURL(msg) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
this.aladin.addCatalog(A.catalogFromURL(msg["votable_URL"], options));
Expand Down
4 changes: 2 additions & 2 deletions js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ class Lock {
locked = false;

/**
* Locks the object
* Unlocks the object
*/
unlock() {
this.locked = false;
}

/**
* Unlocks the object
* Locks the object
*/
lock() {
this.locked = true;
Expand Down
32 changes: 30 additions & 2 deletions src/ipyaladin/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
It allows to display astronomical images and catalogs in an interactive way.
"""

import io
import pathlib
from pathlib import Path
import typing
from typing import ClassVar, Union, Final, Optional
import warnings
Expand All @@ -14,6 +16,8 @@
from astropy.table.table import QTable
from astropy.table import Table
from astropy.coordinates import SkyCoord, Angle
from astropy.io import fits as astropy_fits
from astropy.io.fits import HDUList
import traitlets

try:
Expand Down Expand Up @@ -241,6 +245,32 @@ def add_catalog_from_URL(
}
)

def add_fits(self, fits: Union[str, Path, HDUList], **image_options: any) -> None:
"""Load a FITS file into the widget.
Parameters
----------
fits: a path as a string or `pathlib.Path`, or an `~astropy.io.fits.HDUList`
The FITS file to load into the widget.
image_options: dict
The options for the image. See the Aladin Lite image options:
https://cds-astro.github.io/aladin-lite/global.html#ImageOptions
"""
is_path = isinstance(fits, (Path, str))
if is_path:
with astropy_fits.open(fits) as fits_file:
fits_bytes = io.BytesIO()
fits_file.writeto(fits_bytes)
else:
fits_bytes = io.BytesIO()
fits.writeto(fits_bytes)

self.send(
{"event_name": "add_fits", "options": image_options},
buffers=[fits_bytes.getvalue()],
)

# MOCs

def add_moc(self, moc: any, **moc_options: any) -> None:
Expand Down Expand Up @@ -364,8 +394,6 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None:
And the table should appear in the output of Cell 1!
"""
import io

table_bytes = io.BytesIO()
table.write(table_bytes, format="votable")
self.send(
Expand Down

0 comments on commit 1a61e29

Please sign in to comment.