diff --git a/CHANGELOG.md b/CHANGELOG.md index 44dd8d80..d6ec4250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The method accepts `astropy.io.fits.HDUList`, `pathlib.Path`, or `string` representing paths (#86) - New way to make a selection on the view with `selection` method (#100) - 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) ### Deprecated diff --git a/examples/02_Base_Commands.ipynb b/examples/02_Base_Commands.ipynb index e7970c2f..6c2ae00d 100644 --- a/examples/02_Base_Commands.ipynb +++ b/examples/02_Base_Commands.ipynb @@ -192,7 +192,7 @@ "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", + "You can 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." ] }, diff --git a/examples/11_Extracting_information_from_the_view.ipynb b/examples/11_Extracting_information_from_the_view.ipynb index 11b0cc49..a23d456a 100644 --- a/examples/11_Extracting_information_from_the_view.ipynb +++ b/examples/11_Extracting_information_from_the_view.ipynb @@ -17,18 +17,15 @@ "cell_type": "code", "execution_count": null, "id": "initial_id", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:43.806466Z", - "start_time": "2024-07-24T07:31:43.804047Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "from astropy.coordinates import SkyCoord\n", "import astropy.units as u\n", + "from astropy.wcs import WCS\n", "from astroquery.simbad import Simbad\n", "from astroquery.vizier import Vizier\n", + "import matplotlib.pyplot as plt\n", "\n", "from ipyaladin import Aladin" ] @@ -37,12 +34,7 @@ "cell_type": "code", "execution_count": null, "id": "2e62d34eb8543145", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:44.595633Z", - "start_time": "2024-07-24T07:31:44.580516Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin = Aladin(fov=5, height=600, target=\"M31\")\n", @@ -63,12 +55,7 @@ "cell_type": "code", "execution_count": null, "id": "84153657cb7cd837", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:46.921817Z", - "start_time": "2024-07-24T07:31:46.915120Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.wcs # Recover the current WCS" @@ -86,12 +73,7 @@ "cell_type": "code", "execution_count": null, "id": "a63f210b-3a64-4860-8e70-42a4c66378fa", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:48.369339Z", - "start_time": "2024-07-24T07:31:48.361272Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.height = 800\n", @@ -113,12 +95,7 @@ "cell_type": "code", "execution_count": null, "id": "2ddc9637-b5c3-4412-8435-2302b6d86816", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:51.277841Z", - "start_time": "2024-07-24T07:31:51.271764Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.wcs" @@ -140,12 +117,7 @@ "cell_type": "code", "execution_count": null, "id": "9595ae02388b245a", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:53.400986Z", - "start_time": "2024-07-24T07:31:53.389449Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.fov_xy # Recover the current field of view for the x and y axis" @@ -164,12 +136,7 @@ "cell_type": "code", "execution_count": null, "id": "bb48cb19a597e262", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:55.769334Z", - "start_time": "2024-07-24T07:31:55.496270Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "m1 = SkyCoord.from_name(\"m1\")\n", @@ -207,12 +174,7 @@ "cell_type": "code", "execution_count": null, "id": "3efb33016d863bf7", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:31:58.508331Z", - "start_time": "2024-07-24T07:31:58.501683Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.selection(\"circle\")" @@ -230,41 +192,67 @@ "cell_type": "code", "execution_count": null, "id": "cda32891dd654568", - "metadata": { - "ExecuteTime": { - "end_time": "2024-07-24T07:32:07.757249Z", - "start_time": "2024-07-24T07:32:07.746282Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "aladin.selected_objects" ] + }, + { + "cell_type": "markdown", + "id": "c84e856d82dbde63", + "metadata": {}, + "source": [ + "## Getting the view as a fits file\n", + "The following method allow you to retrieve the current view as a fits file. If a `path` is given as a second argument, the fits file will be saved." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b0d3e2131e9faa2", + "metadata": {}, + "outputs": [], + "source": [ + "fits = aladin.get_view_as_fits()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "020d2c7f", + "metadata": {}, + "outputs": [], + "source": [ + "fits[0].header" + ] + }, + { + "cell_type": "markdown", + "id": "36a996b424529c09", + "metadata": {}, + "source": [ + "Display the fits file using matplotlib:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8327802bad8e8c87", + "metadata": {}, + "outputs": [], + "source": [ + "wcs = WCS(fits[0].header)\n", + "\n", + "plt.subplot(projection=wcs)\n", + "plt.imshow(fits[0].data, cmap=\"binary_r\", norm=\"asinh\", vmin=0.001)" + ] } ], "metadata": { - "nbsphinx": { - "execute": "never" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.8" - }, - "version_major": 2, - "version_minor": 0 + "name": "python" + } }, "nbformat": 4, "nbformat_minor": 5 diff --git a/js/models/event_handler.js b/js/models/event_handler.js index cc021c41..1b095cab 100644 --- a/js/models/event_handler.js +++ b/js/models/event_handler.js @@ -129,6 +129,13 @@ export default class EventHandler { this.aladin.setFoV(fov); }); + this.aladin.on("layerChanged", (imageLayer, layerName, state) => { + if (layerName !== "base" || state !== "ADDED") return; + this.updateWCS(); + this.model.set("_base_layer_last_view", imageLayer.id); + this.model.save_changes(); + }); + /* Div control */ this.model.on("change:_height", () => { let height = this.model.get("_height"); @@ -151,12 +158,6 @@ export default class EventHandler { this.model.save_changes(); }); - this.aladin.on("layerChanged", (_, layerName, state) => { - if (layerName !== "base" || state !== "ADDED") return; - this.updateWCS(); - this.model.save_changes(); - }); - this.aladin.on("resizeChanged", () => { this.updateWCS(); this.update2AxisFoV(); diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 1df0d312..af4f54a1 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -8,6 +8,7 @@ from collections.abc import Callable import io import pathlib +from json import JSONDecodeError from pathlib import Path from typing import ClassVar, Dict, Final, List, Optional, Tuple, Union import warnings @@ -205,6 +206,11 @@ class Aladin(anywidget.AnyWidget): # overlay survey overlay_survey = Unicode("").tag(sync=True, init_option=True) overlay_survey_opacity = Float(0.0).tag(sync=True, init_option=True) + _base_layer_last_view = Unicode( + survey.default_value, + help="The last view of the base layer. It is used " + "to convert the view to an astropy.HDUList", + ).tag(sync=True) init_options = traitlets.List(trait=Any()).tag(sync=True) @@ -376,6 +382,39 @@ def target(self, target: Union[str, SkyCoord]) -> None: } ) + def get_view_as_fits(self) -> HDUList: + """Get the base layer of the widget as an astropy HDUList object. + + The output FITS image will have the same shape as the + current view of the widget. This uses `astroquery.hips2fits` internally. + This method currently only exports the bottom/base layer. + + Returns + ------- + astropy.io.fits.HDUList + The FITS object containing the image. + + """ + try: + from astroquery.hips2fits import hips2fits + except ImportError as imp: + raise ValueError( + "To use the 'get_view_as_fits' method, you need to install astroquery " + "with 'pip install astroquery -U --pre'." + ) from imp + try: + fits = hips2fits.query_with_wcs( + hips=self._base_layer_last_view, + wcs=self.wcs, + ) + except JSONDecodeError as e: + raise ValueError( + "The FITS image could not be retrieved from the view. " + "This can happen when the widget is scrolled out of the " + "screen." + ) from e + return fits + def add_catalog_from_URL( self, votable_URL: str, votable_options: Optional[dict] = None ) -> None: