From 2454c0ef7b3f7d148c3eba55013795c294ec155d Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 16 Jul 2024 11:39:26 +0200 Subject: [PATCH 1/2] maint: prepare release 0.4.0 fix: A.line was removed from public AL API in favor of A.vector --- CHANGELOG.md | 8 ++++++- README.md | 12 +++++----- examples/2_Base_Commands.ipynb | 42 +++++++++++++++++++++------------- js/aladin_lite.js | 2 +- js/models/message_handler.js | 4 +++- src/ipyaladin/__about__.py | 4 ++-- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb90f47..30ce1523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0] + ### Added - attribute `__aladin_lite_version__` added to point to the corresponding Aladin Lite released version @@ -27,15 +29,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `clicked_object` was not properly updated after a click - Fix asynchronous update for the `target` property (#80) +- some options were not accepted in snake_case anymore in `add_moc` and in `add_catalog_from_url` (#82) ### Changed - Change the jslink target trait from `target` to `shared_target` (#80) - Change the jslink fov trait from `fov` to `shared_fov` (#83) -- Deprecate the `add_listener` method for a preferred use of `set_listener` method (#82) - Upgrade Aladin Lite version to 3.4.1-beta (#88) - Add support for list of strings in `add_overlay_from_stcs` (#88) + +### Deprecated + - Deprecate `add_overlay_from_stcs` in favor of `add_graphic_overlay_from_stcs` (#88) +- Deprecate the `add_listener` method for a preferred use of `set_listener` method (#82) ## [0.3.0] diff --git a/README.md b/README.md index 9d405a10..1588d45e 100644 --- a/README.md +++ b/README.md @@ -56,21 +56,21 @@ Ipyaladin brings [Aladin Lite](https://github.com/cds-astro/aladin-lite) into no Correspondence table between ipyaladin versions and Aladin Lite versions: -| ipyaladin | Aladin-Lite | -| ---------- | ----------- | -| 0.3.0 | 3.3.3-dev | -| unreleased | 3.4.1-beta | +| ipyaladin | Aladin-Lite | +| --------- | ----------- | +| 0.3.0 | 3.3.3-dev | +| 0.4.0 | 3.4.4-beta | > [!TIP] > This can always be read like so > > ```python > from ipyaladin import __version__, __aladin_lite_version__ -> print("version:", __version__, "running aladin lite:", __aladin_lite_version__) +> print("version:", __version__, "running Aladin Lite:", __aladin_lite_version__) > ``` > > ``` -> version: unreleased running aladin lite: 3.4.1-beta +> version: 0.4.0 running Aladin Lite: 3.4.4-beta > ``` ## Acknowledging ipyaladin diff --git a/examples/2_Base_Commands.ipynb b/examples/2_Base_Commands.ipynb index 855f6e25..e75a4f46 100644 --- a/examples/2_Base_Commands.ipynb +++ b/examples/2_Base_Commands.ipynb @@ -72,11 +72,13 @@ ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "aladin.target" + "metadata": {}, + "outputs": [], + "source": [ + "aladin.target" + ] }, { "cell_type": "code", @@ -118,7 +120,7 @@ }, "outputs": [], "source": [ - "aladin.coo_frame = \"J2000d\"" + "aladin.coo_frame = \"ICRSd\" # ICRS, and angles expressed in degrees" ] }, { @@ -131,30 +133,38 @@ ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Some commands can be used with astropy objects" + "metadata": {}, + "source": [ + "Some commands can be used with astropy objects" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "from astropy.coordinates import Angle, SkyCoord" + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.coordinates import Angle, SkyCoord" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "aladin.target = SkyCoord(\"12h00m00s\", \"-30d00m00s\", frame=\"icrs\")" + "metadata": {}, + "outputs": [], + "source": [ + "aladin.target = SkyCoord(\"12h00m00s\", \"-30d00m00s\", frame=\"icrs\")" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "aladin.fov = Angle(5, \"deg\")" + "metadata": {}, + "outputs": [], + "source": [ + "aladin.fov = Angle(5, \"deg\")" + ] } ], "metadata": { @@ -173,7 +183,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" + "version": "3.11.8" }, "vscode": { "interpreter": { diff --git a/js/aladin_lite.js b/js/aladin_lite.js index 46675390..379517f1 100644 --- a/js/aladin_lite.js +++ b/js/aladin_lite.js @@ -1,3 +1,3 @@ -import A from "https://esm.sh/aladin-lite@3.4.1-beta"; +import A from "https://esm.sh/aladin-lite@3.4.4-beta"; export default A; diff --git a/js/models/message_handler.js b/js/models/message_handler.js index e4b54b55..d421c621 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -63,8 +63,10 @@ export default class MessageHandler { ); break; case "line": + // remove default lineWidth when we switch to AL > 3.4.4 + region.options.lineWidth = region.options.lineWidth || 3; overlay.add( - A.line( + A.vector( infos.ra1, infos.dec1, infos.ra2, diff --git a/src/ipyaladin/__about__.py b/src/ipyaladin/__about__.py index b5c1a6ca..ff178819 100644 --- a/src/ipyaladin/__about__.py +++ b/src/ipyaladin/__about__.py @@ -1,2 +1,2 @@ -__version__ = "0.4.0-beta" -__aladin_lite_version__ = "3.4.1-beta" +__version__ = "0.4.0" +__aladin_lite_version__ = "3.4.4-beta" From 1a61e29e22ac48dec78288bb9a3d46217ba13292 Mon Sep 17 00:00:00 2001 From: Tom Czekaj <47594493+Xen0Xys@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:39:51 +0200 Subject: [PATCH 2/2] feat: adding FITS to the view with new add_fits method * :construction: Add basic add_fits support * :art: Add url revocation after fits is display * :sparkles: Allow add_fits to load fits from path or HDUList object * :mute: Remove unnecessary logging * :art: Improve Aladin Lite layer naming * :bug: Fix fits loading and potential errors with file handling * :memo: Update changelog * :sparkles: Allow user to give image options and define layer name using "name" option * docs: add add_fits example * docs: edit markdown cells * :sparkles: Add new function to utils and fix Lock comments * :sparkles: Add zero padding to default fits images naming * :fire: Remove padWithZero function * conflict in notebook * docs: edit changelog --- CHANGELOG.md | 5 ++++ examples/2_Base_Commands.ipynb | 53 +++++++++++++++++++++++++++++----- js/models/event_handler.js | 1 + js/models/message_handler.js | 17 +++++++++++ js/utils.js | 4 +-- src/ipyaladin/widget.py | 32 ++++++++++++++++++-- 6 files changed, 101 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ce1523..879a12e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/2_Base_Commands.ipynb b/examples/2_Base_Commands.ipynb index e75a4f46..e7970c2f 100644 --- a/examples/2_Base_Commands.ipynb +++ b/examples/2_Base_Commands.ipynb @@ -14,7 +14,8 @@ "metadata": {}, "outputs": [], "source": [ - "from ipyaladin import Aladin" + "from ipyaladin import Aladin\n", + "from pathlib import Path" ] }, { @@ -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" ] }, { @@ -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, @@ -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, @@ -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", @@ -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": { diff --git a/js/models/event_handler.js b/js/models/event_handler.js index 8aa6f45b..d3c3bb8c 100644 --- a/js/models/event_handler.js +++ b/js/models/event_handler.js @@ -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, diff --git a/js/models/message_handler.js b/js/models/message_handler.js index d421c621..b5387d7f 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -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; @@ -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)); diff --git a/js/utils.js b/js/utils.js index 746ba336..41cc3660 100644 --- a/js/utils.js +++ b/js/utils.js @@ -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; diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 17beb478..735fee5a 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -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 @@ -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: @@ -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: @@ -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(