From 1af1ae4c111d924ac119cc603ef70c2150bf1429 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Thu, 18 Dec 2025 18:12:00 -0800 Subject: [PATCH 01/17] Add relevant H1 heading and rename example notebooks --- examples/demo-3color.ipynb | 7 ++++ examples/demo-HiPS.ipynb | 7 ++++ examples/demo-basic.ipynb | 2 +- ...-image.ipynb => demo-image-from-url.ipynb} | 40 +++++++++++++++---- ...leload.ipynb => demo-multiple-table.ipynb} | 2 +- ...int.ipynb => demo-overlay-footprint.ipynb} | 7 ++++ examples/demo-region.ipynb | 7 ++++ examples/plot-interface.ipynb | 7 ++++ 8 files changed, 69 insertions(+), 10 deletions(-) rename examples/{demo-show-image.ipynb => demo-image-from-url.ipynb} (62%) rename examples/{basic-demo-tableload.ipynb => demo-multiple-table.ipynb} (99%) rename examples/{demo-lsst-footprint.ipynb => demo-overlay-footprint.ipynb} (97%) diff --git a/examples/demo-3color.ipynb b/examples/demo-3color.ipynb index 70067a1..3f29918 100644 --- a/examples/demo-3color.ipynb +++ b/examples/demo-3color.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Show a 3 color image" + ] + }, { "cell_type": "code", "execution_count": 1, diff --git a/examples/demo-HiPS.ipynb b/examples/demo-HiPS.ipynb index e478c27..0f95fec 100644 --- a/examples/demo-HiPS.ipynb +++ b/examples/demo-HiPS.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Show HiPS Image" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/demo-basic.ipynb b/examples/demo-basic.ipynb index 343fe79..0b27035 100644 --- a/examples/demo-basic.ipynb +++ b/examples/demo-basic.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Intro" + "# Basic API Usage" ] }, { diff --git a/examples/demo-show-image.ipynb b/examples/demo-image-from-url.ipynb similarity index 62% rename from examples/demo-show-image.ipynb rename to examples/demo-image-from-url.ipynb index 9c42e65..9f9312a 100644 --- a/examples/demo-show-image.ipynb +++ b/examples/demo-image-from-url.ipynb @@ -1,8 +1,15 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Show image directly from URL" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -18,23 +25,40 @@ "metadata": {}, "outputs": [], "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "\n" + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "fc.show_fits(URL='http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits')\n" + "fc.show_fits(url='http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits')\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "irsa-tutorials", "language": "python", "name": "python3" }, @@ -48,7 +72,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.11" } }, "nbformat": 4, diff --git a/examples/basic-demo-tableload.ipynb b/examples/demo-multiple-table.ipynb similarity index 99% rename from examples/basic-demo-tableload.ipynb rename to examples/demo-multiple-table.ipynb index 0cb692c..0d4b9ff 100644 --- a/examples/basic-demo-tableload.ipynb +++ b/examples/demo-multiple-table.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Intro" + "# Show a file containing multiple tables" ] }, { diff --git a/examples/demo-lsst-footprint.ipynb b/examples/demo-overlay-footprint.ipynb similarity index 97% rename from examples/demo-lsst-footprint.ipynb rename to examples/demo-overlay-footprint.ipynb index 2b63fab..b6e457e 100644 --- a/examples/demo-lsst-footprint.ipynb +++ b/examples/demo-overlay-footprint.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Overlay a footprint on image" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/demo-region.ipynb b/examples/demo-region.ipynb index 25d3679..2b96089 100644 --- a/examples/demo-region.ipynb +++ b/examples/demo-region.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Add region data to image" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index 881a65b..39889dc 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simpler plot interface: firefly_client.plot module" + ] + }, { "cell_type": "markdown", "metadata": {}, From bc225305c69711ad3d314af5af84c8cd29ac00b7 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Thu, 18 Dec 2025 18:29:49 -0800 Subject: [PATCH 02/17] Delete unusable slate examples and move rest to a deprecated dir --- examples/demo-advanced-steps.ipynb | 369 ------------------ .../demo-advanced-tables-images-upload.ipynb | 294 -------------- .../demo-firefly-slate-module.ipynb} | 7 + .../demo-grid-basic.ipynb} | 7 + .../firefly_slate_demo.py | 0 5 files changed, 14 insertions(+), 663 deletions(-) delete mode 100644 examples/demo-advanced-steps.ipynb delete mode 100644 examples/demo-advanced-tables-images-upload.ipynb rename examples/{demo-advanced-table-images.ipynb => deprecated_grid_layout/demo-firefly-slate-module.ipynb} (97%) rename examples/{demo-advanced-all.ipynb => deprecated_grid_layout/demo-grid-basic.ipynb} (99%) rename examples/{ => deprecated_grid_layout}/firefly_slate_demo.py (100%) diff --git a/examples/demo-advanced-steps.ipynb b/examples/demo-advanced-steps.ipynb deleted file mode 100644 index ac27066..0000000 --- a/examples/demo-advanced-steps.ipynb +++ /dev/null @@ -1,369 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook demonstrates basic usage of the firefly_client API fto render tables, images and charts in a grid layout style.\n", - "\n", - "Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", - "using_lab = True\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "fc.change_triview_layout( FireflyClient.BIVIEW_T_IChCov)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Display tables, images, XY charts, and Histograms in Window/Grid like layout\n", - "\n", - "Each rendered unit on Firefly Slate Viewer is called a'cell'. To render a cell and its content, the cell location (row, column, width, height), element type and cell ID are needed. (row, column) and (width, height) represent the position and size of the cell in terms of the grid blocks on Firefly Slate Viewer. Element types include types of 'tables', images', 'xyPlots', 'tableImageMeta' and 'coverageImage'. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use astropy here for convenience, but firefly_client itself does not depend on astropy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Display tables and catalogs\n", - "\n", - "\n", - "Add some tables into cell 'main' (default cell id for tables)\n", - "\n", - "\n", - "- add first table in cell 'main':\n", - "- 'main' is the cell id currently supported by Firefly for element type 'tables'\n", - "- this cell is shown at row = 0, col = 0 with width = 4, height = 2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl\",\n", - " timeout=120, cache=True)\n", - "meta_info = {'datasetInfoConverterId': 'SimpleMoving','positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000',\n", - " 'datasource': 'image_url'}\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='movingtbl', title='A moving object table', page_size=15, meta=meta_info)\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- add 2nd table in cell 'main' for chart and histogram " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tbl_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl',\n", - " timeout=120, cache=True)\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='tbl_chart', title='table for xyplot and histogram', page_size=15)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- add 3rd table in cell 'main'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl\", \n", - " timeout=120, cache=True)\n", - "meta_info = {'datasource': 'DP'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of simple images', page_size=15, meta=meta_info)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- add 4th table in cell 'main'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl\", \n", - " timeout=120, cache=True)\n", - "\n", - "meta_info = {'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', 'datasource': 'FITS'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of m31 images', page_size=15, meta=meta_info)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add different types of image displays\n", - "\n", - "- show cell with id 'image-meta' continaing image from the active table containing datasource column in cell 'main'\n", - "- the cell is shown at row = 2, col = 0 with width = 4, height = 2 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- show cell 'wise-content' containing fits at row = 0, col = 4 with width = 2, height = 2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "image_name = astropy.utils.data.download_file('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' +\n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", - "fc.show_fits(file_on_server=fc.upload_file(image_name), title='WISE Cutout')\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- show 4 fits of moving objects in cell 'movingStff' at row = 2, col = 4 with width = 2, height = 2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "fc.show_fits(plot_id='m49025b_143_2', OverlayPosition='330.347003;-2.774482;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49025b143-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49273b_134_2', OverlayPosition='333.539702;-0.779310;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49273b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49277b_135_1', OverlayPosition='333.589054;-0.747251;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49277b135-w1', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits')\n", - "fc.show_fits(plot_id='m49289b_134_2', OverlayPosition='333.736578;-0.651222;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49289b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits')\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add charts (xy plot and histogram)\n", - "\n", - "- show the cell 'chart-cell-xy' with xy plot related to the table with id 'tbl_chart' in cell 'main'\n", - "- this cell is shown at row = 4, col = 0 with width = 2, height = 3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "trace1 = {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'x': \"tables::ra1\",\n", - " 'y': \"tables::dec1\",\n", - " 'mode': 'markers',\n", - " 'type': 'scatter', \n", - " 'marker': {'size': 4}}\n", - "trace_data=[trace1]\n", - " \n", - "layout_s = {'title': 'Coordinates', \n", - " 'xaxis': {'title': 'ra1 (deg)'}, 'yaxis': {'title': 'dec1 (deg)'}} \n", - "fc.show_chart( layout=layout_s, data=trace_data ) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- show the cell with histogram related to the table with id 'tbl_chart' in cell 'main', \n", - "- this cell is shown at row = 4, col = 2, with width = 2, height = 3 \n", - "- the cell id is automatically created by FireflyClient in case it is not specified externally. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "histData = [\n", - " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'magzp', \n", - " 'marker': {'color': 'rgba(153, 51, 153, 0.8)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 30,\n", - " 'columnOrExpr': 'magzp'\n", - " }\n", - " },\n", - " }\n", - " ]\n", - "\n", - "layout_hist = {'title': 'Magnitude Zeropoints',\n", - " 'xaxis': {'title': 'magzp'}, 'yaxis': {'title': ''}} \n", - "result = fc.show_chart(layout=layout_hist, data=histData ) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add more images\n", - "\n", - "- show coverage image associated with the active table in cell 'main'\n", - "- ths cell is shown at row = 4, col = 4 with width = 2, height = 3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- show image in random location without passing cell location and cell id (i.e. without calling add_cell)\n", - "- the image is shown in the cell with id 'DEFAULT_FITS_VIEWER_ID' in default, \n", - "- and the cell is automatically located by Firefly" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "img_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits', \n", - " timeout=120, cache=True)\n", - "fc.show_fits(file_on_server=fc.upload_file(img_name))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(plot_id='zzz', file_on_server=fc.upload_file(img_name))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- show second image in random. it is shown in the same cell as the previous one " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(plot_id='xxq', Service='TWOMASS', Title='2mass from service', ZoomType='LEVEL',\n", - " initZoomLevel=2, SurveyKey='asky', SurveyKeyBand='k',\n", - " WorldPt='10.68479;41.26906;EQ_J2000', SizeInDeg='.12')" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "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.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/demo-advanced-tables-images-upload.ipynb b/examples/demo-advanced-tables-images-upload.ipynb deleted file mode 100644 index 1825c59..0000000 --- a/examples/demo-advanced-tables-images-upload.ipynb +++ /dev/null @@ -1,294 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook demonstrates basic usage of the firefly_client API for Firefly to render tables, images and charts in a grid layout style. \n", - "\n", - "Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Imports for firefly_client" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", - "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example tentatively uses 127.0.0.1:8080 as the server " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dispaly tables, images, XY charts, and Histograms in Window/Grid like layout\n", - "\n", - "Each rendered unit on Firefly." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please refer to firefly_slate_demo.py which contains functions to render cells with element tables, images, xy charts or histograms onto Firefly Slate Viewer by using firefly_client API. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import firefly_slate_demo as fs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Display tables and catalogs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Open a browser to the firefly server in a new tab. Only works when running the notebook locally." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Add some tables into cell 'main' (default grid viewer id for tables)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# first table in cell 'main' \n", - "fs.load_moving_table(0,0,4,2, fc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# add table in cell 'main' for chart and histogram \n", - "fs.add_table_for_chart(fc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# add table in cell 'main'\n", - "fs.add_simple_image_table(fc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# add table in cell 'main'\n", - "fs.add_simple_m31_image_table(fc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f= '/Users/roby/fits/2mass-m31-2412rows.tbl'\n", - "file= fc.upload_file(f);" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_table(file)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_table('${cache-dir}/upload_2482826742890803252.fits')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add different types of image displays" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show cell containing the image from the active table with datasource column in cell 'main'\n", - "fs.load_image_metadata(2, 0, 4, 2, fc, 'image-meta')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show cell containing FITS in cell 'wise-cutout'\n", - "fs.load_image(0, 4, 2, 2, fc, 'wise-cutout') # load an image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show cell with 4 FITS of moving objects in cell 'movingStff'\n", - "fs.load_moving(2, 4, 2, 2, fc, 'movingStuff')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add charts (xy plot and histogram)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main'\n", - "fs.load_xy(4, 0, 2, 3, fc, 'chart-cell-xy')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show histogram associated with the table for chart in cell 'main', the cell id is generated by firefly_client\n", - "fs.load_histogram(4, 2, 2, 3, fc)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add more images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show cell containing coverage image associated with the active table in cell 'main'\n", - "fs.load_coverage_image(4, 4, 3, 3, fc, 'image-coverage')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show cell containing image in ranmon location without passing the location and cell id\n", - "fs.load_first_image_in_random(fc)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# show second image in random location. This image is located in the same cell as the previous one \n", - "fs.load_second_image_in_random(fc)" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "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.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/demo-advanced-table-images.ipynb b/examples/deprecated_grid_layout/demo-firefly-slate-module.ipynb similarity index 97% rename from examples/demo-advanced-table-images.ipynb rename to examples/deprecated_grid_layout/demo-firefly-slate-module.ipynb index dbcb27f..bc83d26 100644 --- a/examples/demo-advanced-table-images.ipynb +++ b/examples/deprecated_grid_layout/demo-firefly-slate-module.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Do grid layout rendering using firefly_slate_demo" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/demo-advanced-all.ipynb b/examples/deprecated_grid_layout/demo-grid-basic.ipynb similarity index 99% rename from examples/demo-advanced-all.ipynb rename to examples/deprecated_grid_layout/demo-grid-basic.ipynb index 95991a2..6b5af15 100644 --- a/examples/demo-advanced-all.ipynb +++ b/examples/deprecated_grid_layout/demo-grid-basic.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Do grid layout rendering: basic" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/firefly_slate_demo.py b/examples/deprecated_grid_layout/firefly_slate_demo.py similarity index 100% rename from examples/firefly_slate_demo.py rename to examples/deprecated_grid_layout/firefly_slate_demo.py From c2c1cb7b7317f0af8f98a2b7186dc759ad3da1df Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Mon, 22 Dec 2025 16:07:21 -0800 Subject: [PATCH 03/17] Move callbacks demos from test/ to examples/ Remove duplicate files --- .../demo-simple-callback-in-jupyter.ipynb | 212 ++++++++++++++++++ .../demo-simple-callback-in-terminal.py | 9 +- .../test-5-instances-3-channels.py | 2 +- ...test-socket-not-added-until-listener.ipynb | 7 +- test/cb.ipynb | 107 --------- test/fc-version.py | 4 - test/test-simple-callback-in-lab.ipynb | 146 ------------ 7 files changed, 218 insertions(+), 269 deletions(-) create mode 100644 examples/demo-simple-callback-in-jupyter.ipynb rename test/test-simple-callback-response.py => examples/demo-simple-callback-in-terminal.py (84%) rename {test => examples}/test-5-instances-3-channels.py (97%) rename {test => examples}/test-socket-not-added-until-listener.ipynb (96%) delete mode 100644 test/cb.ipynb delete mode 100644 test/fc-version.py delete mode 100644 test/test-simple-callback-in-lab.ipynb diff --git a/examples/demo-simple-callback-in-jupyter.ipynb b/examples/demo-simple-callback-in-jupyter.ipynb new file mode 100644 index 0000000..1ae377c --- /dev/null +++ b/examples/demo-simple-callback-in-jupyter.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extensions and Callback" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'3.4.0'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from firefly_client import __version__ as v\n", + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "# FireflyClient._debug = True # enable for debug logging" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'http://127.0.0.1:8080/firefly/?__wsch=anNpbmdoYWwyMDI1LTEyLTIy'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# pick some host\n", + "local_host = 'http://127.0.0.1:8080/firefly'\n", + "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", + "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", + "data_lsst_host = 'https://data.lsst.cloud/portal/app/'\n", + "host = local_host\n", + "\n", + "using_lab = False\n", + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(host)\n", + "fc.get_firefly_url()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_fits_image(file_input=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True,\n", + " 'rv_string': '91,1.000000,91,1.000000,NaN,2.000000,44,25,600,120,0,NaN,1.000000'}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_stretch('x1', stype='zscale')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Extensions can be made but there is no web socket connections until a listener is added\n", + "\n", + "fc.add_extension(ext_type='POINT', title='Output Selected Point', shortcut_key='ctrl-p')\n", + "fc.add_extension(ext_type='LINE_SELECT', title='Output Selected line', shortcut_key='meta-b')\n", + "fc.add_extension(ext_type='AREA_SELECT', title='Output Selected Area', shortcut_key='a')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'anNpbmdoYWwyMDI1LTEyLTIy': ['1ad', '1af', '1b0']}\n", + "app_data.wsConnUpdated\n", + "POINT\n", + " image point: 188.09900990093206;112.15841584156112\n", + " world point: 202.50072514443582;47.26622697944404;EQ_J2000\n", + "AREA_SELECT\n", + " image points: 173.7454031117884;130.18387553038747 to 210.1301272982422;95.1343705799178\n", + " world points: 202.50875172959275;47.27314011276992;EQ_J2000 to 202.4883768975841;47.25968005990187;EQ_J2000\n", + "LINE_SELECT\n", + " image points: 67.5954738330976;70.0990099009901 to 144.70438472418672;97.47100424328147\n", + " world points: 202.56865416307835;47.25038336191467;EQ_J2000 to 202.52519055730875;47.260701294534044;EQ_J2000\n" + ] + } + ], + "source": [ + "# A Web socket should not be made until this cell is executed\n", + "\n", + "def example_listener(ev):\n", + " if False: # set to True to see all events\n", + " print(ev)\n", + " if 'data' not in ev:\n", + " print('no data found in ev')\n", + " return\n", + " data = ev['data']\n", + " if 'payload' in data:\n", + " print(data['payload'])\n", + " if 'type' in data:\n", + " print(data['type'])\n", + " if data['type'] == 'POINT':\n", + " print(' image point: %s' % data['ipt'])\n", + " print(' world point: %s' % data['wpt'])\n", + " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", + " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", + " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + "\n", + "\n", + "fc.add_listener(example_listener)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test/test-simple-callback-response.py b/examples/demo-simple-callback-in-terminal.py similarity index 84% rename from test/test-simple-callback-response.py rename to examples/demo-simple-callback-in-terminal.py index 7ba67ab..0ac3c1e 100644 --- a/test/test-simple-callback-response.py +++ b/examples/demo-simple-callback-in-terminal.py @@ -1,21 +1,16 @@ from firefly_client import FireflyClient import firefly_client -import time -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' local_host = 'http://127.0.0.1:8080/firefly' fd = 'https://fireflydev.ipac.caltech.edu/firefly' irsa_host = 'https://irsa.ipac.caltech.edu/irsaviewer' data_lsst_host = 'https://data.lsst.cloud/portal/app/' -# host = 'https://fireflydev.ipac.caltech.edu/firefly-multi-ticket/firefly/' host = local_host -channel1 = 'channel-test-1' v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' print('Version: %s' % v_str) -FireflyClient._debug = False +# FireflyClient._debug = True # enable for debug logging token = None -# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) fc = FireflyClient.make_client(host, launch_browser=True, token=token) print(fc.get_firefly_url()) fc.show_fits(url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits") @@ -52,4 +47,4 @@ def example_listener(ev): # fc.remove_listener(example_listener) # time.sleep(2) # fc.add_listener(example_listener) -fc.wait_for_events() \ No newline at end of file +fc.wait_for_events() # needed to keep the process alive to show callback output in terminal \ No newline at end of file diff --git a/test/test-5-instances-3-channels.py b/examples/test-5-instances-3-channels.py similarity index 97% rename from test/test-5-instances-3-channels.py rename to examples/test-5-instances-3-channels.py index 407d973..993cedd 100644 --- a/test/test-5-instances-3-channels.py +++ b/examples/test-5-instances-3-channels.py @@ -21,7 +21,7 @@ def listener4(ev): print(ev) -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' +lsst_host = 'https://data.lsst.cloud/portal/app' local_host = 'http://127.0.0.1:8080/firefly' host = local_host channel1 = 'channel-test-1' diff --git a/test/test-socket-not-added-until-listener.ipynb b/examples/test-socket-not-added-until-listener.ipynb similarity index 96% rename from test/test-socket-not-added-until-listener.ipynb rename to examples/test-socket-not-added-until-listener.ipynb index 3a18baf..9078b71 100644 --- a/test/test-socket-not-added-until-listener.ipynb +++ b/examples/test-socket-not-added-until-listener.ipynb @@ -12,12 +12,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", + "lsst_host = 'https://data.lsst.cloud/portal/app'\n", "local_host = 'http://127.0.0.1:8080/firefly'\n", "host = local_host\n", "channel1 = 'channel-test-1'\n", @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -44,7 +44,6 @@ } ], "source": [ - "\n", "fc1_c1.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" ] }, diff --git a/test/cb.ipynb b/test/cb.ipynb deleted file mode 100644 index 624af28..0000000 --- a/test/cb.ipynb +++ /dev/null @@ -1,107 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import firefly_client\n", - "from firefly_client import FireflyClient\n", - "firefly_client.__dict__['__version__'] " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "FireflyClient._debug = False\n", - "fc = FireflyClient.make_lab_client()\n", - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Extensions can be made but there is not web socket connections until a listener is added\n", - "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# A Web socket should not be made until this cell is added\n", - "\n", - "def listener1(ev):\n", - " if False:\n", - " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", - " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", - "\n", - "\n", - " \n", - "fc.add_listener(listener1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/test/fc-version.py b/test/fc-version.py deleted file mode 100644 index 1ca4971..0000000 --- a/test/fc-version.py +++ /dev/null @@ -1,4 +0,0 @@ -from firefly_client import FireflyClient -import firefly_client -v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' -print('Version: %s' % v_str) diff --git a/test/test-simple-callback-in-lab.ipynb b/test/test-simple-callback-in-lab.ipynb deleted file mode 100644 index fc87614..0000000 --- a/test/test-simple-callback-in-lab.ipynb +++ /dev/null @@ -1,146 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import FireflyClient" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", - "\n", - "# some host\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", - "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", - "\n", - "#host = 'http://127.0.0.1:8080/suit'\n", - "host = local_host\n", - "channel1 = 'channel-test-1'\n", - "FireflyClient._debug = False\n", - "#fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", - "fc = FireflyClient.make_lab_client(start_browser_tab=False, start_tab=True, verbose=True )\n", - "#fc = FireflyClient.make_lab_client(start_tab=False)\n", - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.set_stretch('x1', stype='zscale')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Extensions can be made but there is not web socket connections until a listener is added\n", - "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line', shortcut_key='meta-e')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# A Web socket should not be made until this cell is added\n", - "\n", - "def listener1(ev):\n", - " if False:\n", - " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", - " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", - "\n", - "\n", - " \n", - "fc.add_listener(listener1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import __version__ as v\n", - "v" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From a91bfa6d96dce23fc7227fda54e535b44accbe1e Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Tue, 23 Dec 2025 14:29:43 -0800 Subject: [PATCH 04/17] More heading cleanup in notebooks --- examples/demo-overlay-footprint.ipynb | 7 ------- examples/demo-region.ipynb | 7 ------- examples/plot-interface.ipynb | 8 ++++---- examples/test-socket-not-added-until-listener.ipynb | 7 +++++++ 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/examples/demo-overlay-footprint.ipynb b/examples/demo-overlay-footprint.ipynb index b6e457e..afbf40d 100644 --- a/examples/demo-overlay-footprint.ipynb +++ b/examples/demo-overlay-footprint.ipynb @@ -7,13 +7,6 @@ "# Overlay a footprint on image" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Intro" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/demo-region.ipynb b/examples/demo-region.ipynb index 2b96089..25a4119 100644 --- a/examples/demo-region.ipynb +++ b/examples/demo-region.ipynb @@ -7,13 +7,6 @@ "# Add region data to image" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Intro" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index 39889dc..fde478e 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -160,7 +160,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Upload a table directly from disk." + "## Upload a table directly from disk." ] }, { @@ -242,7 +242,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Viewing a Python image object" + "## Viewing a Python image object" ] }, { @@ -308,7 +308,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Viewing a Numpy array" + "## Viewing a Numpy array" ] }, { @@ -370,7 +370,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Uploading a Python table object" + "## Uploading a Python table object" ] }, { diff --git a/examples/test-socket-not-added-until-listener.ipynb b/examples/test-socket-not-added-until-listener.ipynb index 9078b71..3fff1ad 100644 --- a/examples/test-socket-not-added-until-listener.ipynb +++ b/examples/test-socket-not-added-until-listener.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test socket not added until listener is added" + ] + }, { "cell_type": "code", "execution_count": 1, From 33543e7b6d6bd6ddb85683f40358ff0b2cef9846 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Sun, 28 Dec 2025 17:09:18 -0800 Subject: [PATCH 05/17] Render example notebooks and scripts as html More restructure of test notebooks and cleanup --- docs/_ext/script_pages.py | 223 +++++++ docs/conf.py | 26 +- docs/usage/demo-notebooks.rst | 24 +- docs/usage/examples | 1 + examples/demo-simple-callback-in-terminal.py | 1 + .../test-5-instances-3-channels.py | 0 .../{ => development_tests}/test-access.ipynb | 0 .../development_tests/test-show_data.ipynb | 564 ++++++++++++++++++ ...test-socket-not-added-until-listener.ipynb | 0 examples/filetable.py | 11 +- examples/multi-moc.py | 1 + examples/plot-interface.ipynb | 2 +- pyproject.toml | 5 +- 13 files changed, 844 insertions(+), 14 deletions(-) create mode 100644 docs/_ext/script_pages.py create mode 120000 docs/usage/examples rename examples/{ => development_tests}/test-5-instances-3-channels.py (100%) rename examples/{ => development_tests}/test-access.ipynb (100%) create mode 100644 examples/development_tests/test-show_data.ipynb rename examples/{ => development_tests}/test-socket-not-added-until-listener.ipynb (100%) diff --git a/docs/_ext/script_pages.py b/docs/_ext/script_pages.py new file mode 100644 index 0000000..958ba7b --- /dev/null +++ b/docs/_ext/script_pages.py @@ -0,0 +1,223 @@ +"""Sphinx extension: generate per-script RST pages, copy .py into built _sources, and rewrite source links.""" +import os +import glob +import io +from sphinx.util import logging + +logger = logging.getLogger(__name__) + +MARKER_TEMPLATE = '.. AUTO-GENERATED from {}' + + +def _title_from_script(path): + """Extract a title from the first commented heading line; fallback to filename.""" + title = os.path.splitext(os.path.basename(path))[0] + try: + with io.open(path, 'r', encoding='utf-8') as fh: + first_line = fh.readline() + # Skip shebang (#!) if present + if first_line and first_line.lstrip().startswith('#!'): + first_line = fh.readline() + # Look for first comment line + title_comment = None + if first_line and first_line.lstrip().startswith('#'): + title_comment = first_line.strip().lstrip('#').strip() + if title_comment: + title = title_comment + except Exception: + pass + return title + + +def _generate_rst_for_scripts(app): + """Create a .rst next to each .py under docs/usage/examples/. + + - If `.rst` already exists and does NOT start with our auto-generated marker, + we leave it alone (so user-written RST isn't overwritten). + - Otherwise we create or overwrite the RST to include the script via literalinclude. + """ + logger = logging.getLogger(__name__) + srcdir = app.srcdir + + patterns = [ + os.path.join(srcdir, 'usage', 'examples', '*.py'), + ] + py_files = [] + for pat in patterns: + py_files.extend(glob.glob(pat)) + py_files = sorted(set(py_files)) + + generated = 0 + + for script_fpath in py_files: + script_basename = os.path.basename(script_fpath) + # Skip conf.py and other build files + if script_basename in ('conf.py',): + continue + + rst_path = os.path.splitext(script_fpath)[0] + '.rst' + marker = MARKER_TEMPLATE.format(script_basename) + + # If rst exists and not generated by us, skip it + if os.path.exists(rst_path): + try: + with io.open(rst_path, 'r', encoding='utf-8') as rh: + first = rh.readline().strip() + if first != marker: + logger.info(f'Skipping existing RST: {rst_path}') + continue + except Exception: + # If we can't read, skip to be safe + logger.warning(f'Could not read existing RST {rst_path}; skipping') + continue + + # Build content + title = _title_from_script(script_fpath) + underline = '=' * len(title) + # Make the literalinclude path relative to the generated RST location + rst_dir = os.path.dirname(rst_path) + rel = os.path.relpath(script_fpath, rst_dir) + lines = [ + marker, + '', + title, + underline, + '', + f'.. literalinclude:: {rel}', + ' :language: python', + ' :linenos:', + '', + ] + + try: + with io.open(rst_path, 'w', encoding='utf-8') as oh: + oh.write('\n'.join(lines)) + generated += 1 + logger.info(f'Generated {rst_path} from {script_basename}') + except Exception as e: + logger.warning(f'Failed to write {rst_path}: {e}') + + logger.info(f'Generated {generated} script RST files') + + +def _remove_generated_rst(app, exception): + """Remove RST files that were auto-generated for scripts and copy .py into built _sources. + + Safety rules: + - Only remove files whose first line starts with the auto-generated marker + ('.. AUTO-GENERATED from ...'). This avoids deleting user-authored RST. + - Only remove files after a successful build (exception is None). + """ + logger = logging.getLogger(__name__) + + # If build failed, do not remove files so devs can inspect outputs + if exception is not None: + logger.info('Build failed; leaving auto-generated RST files in place') + return + + srcdir = app.srcdir + patterns = [ + os.path.join(srcdir, 'usage', 'examples', '*.rst'), + ] + + rst_files = [] + for pat in patterns: + rst_files.extend(glob.glob(pat)) + + removed = 0 + for rst in sorted(set(rst_files)): + try: + with io.open(rst, 'r', encoding='utf-8') as fh: + first = fh.readline().strip() + if first.startswith(MARKER_TEMPLATE.format('')): + # If building HTML, copy the original .py into the built _sources + # directory so the "View source" link can serve the real Python file. + try: + py_full = os.path.splitext(rst)[0] + '.py' + if os.path.exists(py_full) and getattr(app, 'builder', None) is not None: + try: + builder_name = getattr(app.builder, 'name', None) + outdir = getattr(app.builder, 'outdir', None) + except Exception: + builder_name = None + outdir = None + + if builder_name == 'html' and outdir: + dest_dir = os.path.join(outdir, '_sources') + rel_py = os.path.relpath(py_full, app.srcdir) + dest_path = os.path.join(dest_dir, rel_py) + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + # Copy file contents + try: + with io.open(py_full, 'r', encoding='utf-8') as rf, io.open(dest_path, 'w', encoding='utf-8') as wf: + wf.write(rf.read()) + logger.info(f'Copied Python source to built _sources: {dest_path}') + except Exception as e: + logger.warning(f'Failed to write built source {dest_path}: {e}') + + except Exception: + # non-fatal; continue to removal attempt + pass + + try: + os.remove(rst) + removed += 1 + logger.info(f'Removed generated RST: {rst}') + except Exception as e: + logger.warning(f'Failed to remove generated RST {rst}: {e}') + except Exception as e: + logger.warning(f'Could not read {rst}; skipping removal: {e}') + + logger.info(f'Removed {removed} auto-generated script RST files') + + +def _rewrite_sourcelink_to_py(app, pagename, templatename, context, doctree): + """If the current page was generated from a script RST, point "View source" to the .py. + + This mirrors nbsphinx behaviour for notebooks where the "Show source" link + points to the original notebook instead of the generated RST. + """ + try: + # doc2path with base=None returns a path relative to srcdir + rst_rel = app.env.doc2path(pagename, base=None) + except Exception: + return + + rst_full = os.path.join(app.srcdir, rst_rel) + if not os.path.exists(rst_full): + return + + try: + with io.open(rst_full, 'r', encoding='utf-8') as fh: + first = fh.readline().strip() + except Exception: + return + + # Only rewrite for our auto-generated script RST files + if not first.startswith(MARKER_TEMPLATE.format('')): + return + + py_full = os.path.splitext(rst_full)[0] + '.py' + if not os.path.exists(py_full): + return + + # Make the path relative to the source directory (what Sphinx expects) + rel_py = os.path.relpath(py_full, app.srcdir) + + # Replace the template context variable so the HTML theme will link to .py + context['sourcename'] = rel_py + + +def setup(app): + # Generate per-script RST files before reading sources + app.connect('builder-inited', _generate_rst_for_scripts) + # Remove the generated per-script RST files after a successful build + app.connect('build-finished', _remove_generated_rst) + # Make "View source" point to the original .py for auto-generated script pages + app.connect('html-page-context', _rewrite_sourcelink_to_py) + + return { + 'version': '1.0', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/conf.py b/docs/conf.py index b84657f..bf9cc8d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,11 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +# Make local extension modules under _ext/ importable +import os +import sys +sys.path.insert(0, os.path.abspath('_ext')) + project = 'firefly_client' copyright = '2024, Caltech/IPAC Firefly Developers' author = 'Caltech/IPAC Firefly Developers' @@ -15,11 +20,18 @@ extensions = [ 'sphinx_automodapi.automodapi', - 'myst_parser' + 'myst_parser', + 'nbsphinx', + 'script_pages', # custom extension for auto-generating RST files for examples/*.py scripts ] templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [ + '_build', 'Thumbs.db', '.DS_Store', + # exclude any docs in subdirectories under usage/examples/ since they are + # not included in any toctree and are for internal use only + 'usage/examples/*/**' +] # -- Options for HTML output ------------------------------------------------- @@ -52,3 +64,13 @@ # -- Options for extensions ------------------------------------------------- myst_heading_anchors = 3 + +# nbsphinx configuration: render notebooks and Python scripts +# Do not execute notebooks during docs build; use stored outputs if present. +nbsphinx_execute = 'never' + +# Allow build to continue even if some notebooks error (useful for demos). +nbsphinx_allow_errors = True + +# Remove the .txt suffix that gets added to source files +html_sourcelink_suffix = '' diff --git a/docs/usage/demo-notebooks.rst b/docs/usage/demo-notebooks.rst index 1e72ee7..e97f0c3 100644 --- a/docs/usage/demo-notebooks.rst +++ b/docs/usage/demo-notebooks.rst @@ -6,7 +6,12 @@ Demo Notebooks Reference Guide ----------------- -For a high-level overview of the Firefly Python client API and important methods, refer to this `reference guide notebook `_. +For a high-level overview of the Firefly Python client API and important methods, refer to this notebook: + +.. toctree:: + :maxdepth: 2 + + examples/reference-guide Science Use-cases @@ -25,13 +30,16 @@ Other Examples --------------- For a full list of notebooks/scripts that demonstrate the use of -Firefly Python client, refer to |nbviewer_examples| (rendered in nbviewer). - -.. warning:: - Several of the notebooks in the examples directory use deprecated API (or discouraged API that is about to be deprecated) and will be updated soon. +Firefly Python client, refer to the following: +.. toctree:: + :maxdepth: 1 + :glob: -.. |nbviewer_examples| raw:: html + examples/demo-* + examples/plot-interface + examples/filetable + examples/multi-moc - this examples directory +.. note:: + All of these notebooks/scripts are also available in the `examples directory `_ of firefly-client GitHub repository. diff --git a/docs/usage/examples b/docs/usage/examples new file mode 120000 index 0000000..d15735c --- /dev/null +++ b/docs/usage/examples @@ -0,0 +1 @@ +../../examples \ No newline at end of file diff --git a/examples/demo-simple-callback-in-terminal.py b/examples/demo-simple-callback-in-terminal.py index 0ac3c1e..ce6a9ac 100644 --- a/examples/demo-simple-callback-in-terminal.py +++ b/examples/demo-simple-callback-in-terminal.py @@ -1,3 +1,4 @@ +# Extensions and Callback via Terminal from firefly_client import FireflyClient import firefly_client diff --git a/examples/test-5-instances-3-channels.py b/examples/development_tests/test-5-instances-3-channels.py similarity index 100% rename from examples/test-5-instances-3-channels.py rename to examples/development_tests/test-5-instances-3-channels.py diff --git a/examples/test-access.ipynb b/examples/development_tests/test-access.ipynb similarity index 100% rename from examples/test-access.ipynb rename to examples/development_tests/test-access.ipynb diff --git a/examples/development_tests/test-show_data.ipynb b/examples/development_tests/test-show_data.ipynb new file mode 100644 index 0000000..e07e16b --- /dev/null +++ b/examples/development_tests/test-show_data.ipynb @@ -0,0 +1,564 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ed946441", + "metadata": {}, + "source": [ + "# Show Any Data File" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7cce6ff0", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "id": "192cb924", + "metadata": {}, + "source": [ + "## Create a FireflyClient instance" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "55ec43a3", + "metadata": {}, + "outputs": [], + "source": [ + "using_lab = True\n", + "# url = 'https://irsadev.ipac.caltech.edu/irsaviewer'\n", + "# url = 'http://localhost:8080/firefly'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ede61748", + "metadata": {}, + "outputs": [], + "source": [ + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2d64edfa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.reinit_viewer()" + ] + }, + { + "cell_type": "markdown", + "id": "f93c68b5", + "metadata": {}, + "source": [ + "## File inputs " + ] + }, + { + "cell_type": "markdown", + "id": "a63b7082", + "metadata": {}, + "source": [ + "### Local file" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d5e77f7c", + "metadata": {}, + "outputs": [], + "source": [ + "# local_fpath = '/Users/jsinghal/dev/cm/__test_data/MF.20210502.18830.fits' # MEF\n", + "local_fpath = '/Users/jsinghal/dev/cm/__test_data/test_image.fits' # single ext FITS\n", + "# local_fpath = '/Users/jsinghal/dev/cm/__test_data/m31-2mass-2412-row.tbl' # table\n", + "# local_fpath = '/Users/jsinghal/Downloads/spherex_obscore.moc.fits' # MOC" + ] + }, + { + "cell_type": "markdown", + "id": "266c1776", + "metadata": {}, + "source": [ + "#### Default naming" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0e3beac7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(local_fpath) # image ✕, table ✓ " + ] + }, + { + "cell_type": "markdown", + "id": "647c4dcf", + "metadata": {}, + "source": [ + "#### Default naming with metadata preview" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "edf7ff0b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(local_fpath, preview_metadata=True) # image ✓, table ✓ " + ] + }, + { + "cell_type": "markdown", + "id": "612dbcb4", + "metadata": {}, + "source": [ + "#### Custom naming" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9226455f", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "FireflyClient.show_data() got an unexpected keyword argument 'displayName'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[8], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mfc\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshow_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlocal_fpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdisplayName\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43msome data\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# image ✕, table ✓ \u001b[39;00m\n", + "\u001b[0;31mTypeError\u001b[0m: FireflyClient.show_data() got an unexpected keyword argument 'displayName'" + ] + } + ], + "source": [ + "fc.show_data(local_fpath, displayName='some data') # image ✕, table ✓ " + ] + }, + { + "cell_type": "markdown", + "id": "86fd67fd", + "metadata": {}, + "source": [ + "#### Custom naming with metadata preview" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7653dd95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(local_fpath, displayName='some data', preview_metadata=True) # image ✓, table ✓ " + ] + }, + { + "cell_type": "markdown", + "id": "ea629b9a", + "metadata": {}, + "source": [ + "### URL" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1d4540cd", + "metadata": {}, + "outputs": [], + "source": [ + "# FITS image (Single ext)\n", + "# remote_path = 'http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a/149/02206a149-w1-int-1b.fits'\n", + "\n", + "# Table query\n", + "remote_path = \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',70.0,20.0,0.1))=1\"\n", + "\n", + "# Table file\n", + "# remote_path = \"https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/00766a/006/00766a006-fflag-1b.tbl\"" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "4c714ff0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# file_payload = {'fileOnServer': 'dsfsf'}\n", + "# file_payload = {'url': 'asasds'}\n", + "file_payload = {}\n", + "\n", + "source = file_payload.get('fileOnServer') or file_payload.get('url')\n", + "source is None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc496c04", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dea58a4c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, '02206a149-w1-int-1b.some.fits')" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "url_points_to_file('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a/149/02206a149-w1-int-1b.some.fits')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "564952df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "os.path.isfile(remote_path.split('?', 1)[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "fb2781bd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',70.0,20.0,0.1))=1\"" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "os.path.basename(remote_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "38d0beb7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(remote_path) # image ✕, table file ✓, table query ✕" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce99fa2d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(remote_path, preview_metadata=True) # image ✓, table file ✓, table query ✕" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "35eb6eb7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(remote_path, displayName='remote data') # image ✕, table file ✓, table query ✕" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5f1c2fbc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_data(remote_path, displayName='remote data', preview_metadata=True) # image ✕, table file ✕, table query ✕" + ] + }, + { + "cell_type": "markdown", + "id": "12933161", + "metadata": {}, + "source": [ + "### File already on server\n", + "\n", + "Is already covered by local file" + ] + }, + { + "cell_type": "markdown", + "id": "c9749e31", + "metadata": {}, + "source": [ + "### File stream\n", + "\n", + "Is similar to file upload, so covered by local file?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "739d2ff8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: show_fits() is deprecated. Use show_fits_image() instead.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "size_in_arcsec = 30\n", + "ra = 150.00983\n", + "dec = 2.59783\n", + "target = '{};{};EQ_J2000'.format(ra, dec)\n", + "\n", + "service_params = dict(Title='COSMOS 3.6um',\n", + " Type='SERVICE',\n", + " Service='ATLAS',\n", + " SurveyKey='cosmos.cosmos_irac',\n", + " SurveyKeyBand='IRAC1',\n", + " WorldPt=target,\n", + " SizeInDeg=30/3600,\n", + " ColorTable=1,\n", + " ZoomType='ARCSEC_PER_SCREEN_PIX',\n", + " ZoomArcsecPerScreenPix=0.3,)\n", + "\n", + "fc.show_fits(plot_id='plot-id',\n", + " url='http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a/149/02206a149-w1-int-1b.fits',\n", + " file_on_server=fc.upload_file('/Users/jsinghal/dev/cm/__test_data/test_image.fits'),\n", + " # title='test image',\n", + " # **service_params,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "3b18d864", + "metadata": {}, + "source": [ + "### Issues identified\n", + "- displayName does not work for any URL input (it seems to work for default naming because that's what it assigns too)\n", + " - yet to debug, probably is getting ignored since no special handling like file_on_server case\n", + "- displayName does not work for immediate FITS image file on server but only when stopped at metadata preview step\n", + " - debugger tells me that the server sets the display name correctly in the response (that's why it shows up in metadata preview) so probably a client issue?" + ] + }, + { + "cell_type": "markdown", + "id": "8b7fc624", + "metadata": {}, + "source": [ + "## Test setting ID that can be controlled later" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "02bb34ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True,\n", + " 'rv_string': '91,1.000000,91,1.000000,NaN,2.000000,44,25,600,120,0,NaN,1.000000'}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_stretch('img1', 'zscale', 'linear')" + ] + }, + { + "cell_type": "markdown", + "id": "759471f7", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/test-socket-not-added-until-listener.ipynb b/examples/development_tests/test-socket-not-added-until-listener.ipynb similarity index 100% rename from examples/test-socket-not-added-until-listener.ipynb rename to examples/development_tests/test-socket-not-added-until-listener.ipynb diff --git a/examples/filetable.py b/examples/filetable.py index 32b031e..9ce2065 100755 --- a/examples/filetable.py +++ b/examples/filetable.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# Load files as a table of data products from argparse import ArgumentParser, HelpFormatter import csv @@ -57,7 +58,11 @@ def filetable_to_firefly( Dictionary of metadata items """ - filelist = glob(topdir + "/**/" + pattern, recursive=recursive) + if recursive: + file_lookup_path = os.path.join(topdir, "**", pattern) + else: + file_lookup_path = os.path.join(topdir, pattern) + filelist = glob(file_lookup_path, recursive=recursive) if sort: filelist = sorted(filelist) metadict = {"datasource": "path"} @@ -154,6 +159,10 @@ def main(): if printurl: input("Press Enter after you have opened the Firefly URL printed above...") + # TODO: figure out how to activate data products (meta) tab in Bi-View + # if "slate" not in html_file: + # fc.change_triview_layout(firefly_client.FireflyClient.BIVIEW_T_IChCov) + # fc.dispatch('layout.updateLayout', {'images':{'selectedTab':'meta'}}) r = fc.add_cell(0, 0, 1, 2, "tables", "main") fc.show_table(tbl_val, meta=metainfo) r = fc.add_cell(0, 1, 1, 2, "tableImageMeta", "image-meta") diff --git a/examples/multi-moc.py b/examples/multi-moc.py index b046570..9930957 100644 --- a/examples/multi-moc.py +++ b/examples/multi-moc.py @@ -1,3 +1,4 @@ +# Load multiple MOCs import astropy.utils.data from firefly_client import FireflyClient fc = FireflyClient.make_client('http://127.0.0.1:8080/firefly', channel_override='moc-channel') diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index fde478e..1c91045 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Simpler plot interface: firefly_client.plot module" + "# Plot interface: `firefly_client.plot` module" ] }, { diff --git a/pyproject.toml b/pyproject.toml index a187fc3..6c49210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,8 +45,9 @@ Repository = "http://github.com/Caltech-IPAC/firefly_client.git" [project.optional-dependencies] docs = [ - "Sphinx~=7.1.0", + "Sphinx>=7.3,<8.0", "sphinx-automodapi", "pydata-sphinx-theme", - "myst-parser" + "myst-parser", + "nbsphinx", ] From b0929fe33695198c365d7df973bbf064d201d43f Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Tue, 6 Jan 2026 16:46:51 -0800 Subject: [PATCH 06/17] Fix links of science use case notebooks --- docs/usage/demo-notebooks.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/usage/demo-notebooks.rst b/docs/usage/demo-notebooks.rst index e97f0c3..1e94946 100644 --- a/docs/usage/demo-notebooks.rst +++ b/docs/usage/demo-notebooks.rst @@ -17,13 +17,13 @@ For a high-level overview of the Firefly Python client API and important methods Science Use-cases ----------------- -IRSA Tutorials is collection of multiple notebooks that demonstrate the use of IRSA data services and tools for science use-cases. -The following tutorial notebooks demonstrates the use of Firefly Python client: +IRSA Notebook Tutorials is collection of notebooks that demonstrate the use of IRSA data services and tools for science use-cases. +The following notebook tutorials demonstrate the use of Firefly Python client: -- `Using Firefly visualization tools in Python to vet SEDs `_ -- `Using Firefly visualization tools to understand the light curves of Solar System objects `_ -- `Using Firefly to Explore OpenUniverse2024 Data Preview Simulated Roman and Rubin Images `_ -- `Euclid Q1: PHZ catalogs — Visualize with Firefly section `_ +- `Using Firefly visualization tools in Python to vet SEDs `_ +- `Using Firefly visualization tools to understand the light curves of Solar System objects `_ +- `Using Firefly to Explore OpenUniverse2024 Data Preview Simulated Roman and Rubin Images `_ +- `Euclid Q1: PHZ catalogs — Visualize with Firefly section `_ Other Examples From d1061c5469bc0af62360567b92e08cce14e921b7 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Tue, 6 Jan 2026 17:30:59 -0800 Subject: [PATCH 07/17] Update local development docs --- docs/development/guide.rst | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/development/guide.rst b/docs/development/guide.rst index 825dce7..4774dc8 100644 --- a/docs/development/guide.rst +++ b/docs/development/guide.rst @@ -4,18 +4,37 @@ Local development Get latest source from ``master`` branch at https://github.com/Caltech-IPAC/firefly_client. +.. code-block:: shell + + git clone https://github.com/Caltech-IPAC/firefly_client.git + cd firefly_client + Environment Setup ----------------- -TBD +Create a Python virtual environment and install required dependencies. +The folllowing commands demonstrate how to do this using ``conda`` (assuming you have `miniconda `_ installed on your system): + +.. code-block:: shell + + conda create -n ffpy -c conda-forge python jupyter astropy # jupyter and astropy are needed for running examples + conda activate ffpy + pip install -e ".[docs]" # editable installation with docs dependencies + + +Now you can run the examples notebooks/scripts in the ``examples/`` directory. +This can be done by starting a Jupyter Notebook or JupyterLab session from the terminal (``jupyter notebook`` or ``jupyter lab``), or by using an IDE that supports running Python notebooks or scripts (like VSCode, IntelliJ, etc.). +.. note:: + The changes you make to the source code will be reflected when you run the examples since the package is installed in editable mode. + But make sure to restart the active Python kernel/session to pick up the changes. Building documentation ---------------------- -Make sure you have the virtual/conda environment activated and documentation -dependencies installed in that environment. +Make sure you have the virtual environment activated and documentation +dependencies installed in that environment (``[docs]``). Then do: @@ -28,4 +47,9 @@ Open ``docs/_build/html/index.html`` in your browser to see the documentation website. Each time you make a change in documentation source, build it using -above command and reload the above file in browser. \ No newline at end of file +above command and reload the above html file in browser. + +Development Tests/Examples +-------------------------- + +Refer to the `examples/development_tests directory `_ of firefly-client GitHub repository. From c5a82c1a08ff3e113fa10befc386e6855c6dc5fe Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Tue, 6 Jan 2026 17:33:57 -0800 Subject: [PATCH 08/17] Remove demo notebook for displaying images from URL --- examples/demo-image-from-url.ipynb | 80 ------------------------------ 1 file changed, 80 deletions(-) delete mode 100644 examples/demo-image-from-url.ipynb diff --git a/examples/demo-image-from-url.ipynb b/examples/demo-image-from-url.ipynb deleted file mode 100644 index 9f9312a..0000000 --- a/examples/demo-image-from-url.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Show image directly from URL" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import FireflyClient\n", - "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'success': True}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fc.show_fits(url='http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "irsa-tutorials", - "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.12.11" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 1b577e79f9c5b74cced80f48702a0396314b24bd Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 7 Jan 2026 17:41:33 -0800 Subject: [PATCH 09/17] Update initialization documentation from rst to notebook --- docs/conf.py | 2 +- docs/index.rst | 8 +- docs/usage/initializing-vanilla.ipynb | 255 ++++++++++++++++++++++++++ docs/usage/initializing-vanilla.rst | 79 -------- 4 files changed, 260 insertions(+), 84 deletions(-) create mode 100644 docs/usage/initializing-vanilla.ipynb delete mode 100644 docs/usage/initializing-vanilla.rst diff --git a/docs/conf.py b/docs/conf.py index bf9cc8d..25d31fa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,7 @@ sys.path.insert(0, os.path.abspath('_ext')) project = 'firefly_client' -copyright = '2024, Caltech/IPAC Firefly Developers' +copyright = '2026, Caltech/IPAC Firefly Developers' author = 'Caltech/IPAC Firefly Developers' # -- General configuration --------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 8c85d0e..6bc58ea 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ firefly_client - Python API to Firefly visualization ##################################################### -`firefly_client` provides a Python API to the Firefly visualization framework. +``firefly_client`` provides a Python API to the Firefly visualization framework. You can find it on Github at https://github.com/Caltech-IPAC/firefly_client .. _firefly_client-intro: @@ -21,9 +21,9 @@ framework for the Portal Aspect of the LSST Science User Platform. Its client-server architecture is designed to enable a user to easily visualize images and catalog data from a remote site. -The `firefly_client` package provides a lightweight client class that includes -a Python interface to `Firefly's Javascript API `_. -This document contains examples of how to start a `FireflyClient` instance +The ``firefly_client`` package provides a lightweight client class that includes +a Python interface to Firefly's Javascript API. +This document contains examples of how to start a ``FireflyClient`` instance with details about the server location, and how to use the instance to upload and display astronomical images, tables and catalogs. diff --git a/docs/usage/initializing-vanilla.ipynb b/docs/usage/initializing-vanilla.ipynb new file mode 100644 index 0000000..657702f --- /dev/null +++ b/docs/usage/initializing-vanilla.ipynb @@ -0,0 +1,255 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "90c41059", + "metadata": {}, + "source": [ + "# Initializing a FireflyClient instance\n", + "This notebook focuses only on the initialization of a `FireflyClient` object. Later notebooks will demonstrate how to use it for visualizing your data.\n", + "\n", + "## Pick a Firefly server URL\n", + "\n", + "[Firefly](https://github.com/Caltech-IPAC/firefly), the interactive data visualization tool, is a web application (with a URL) that can be controlled from Python with a `FireflyClient` object. When we create the `FireflyClient` instance, we need to provide the URL of the Firefly server we want to connect to.\n", + "\n", + "By default, the value of the environment variable `FIREFLY_URL` will be used as the Firefly server URL. It can be set to one of the following:\n", + "1. a local Firefly server which can be run via a [Firefly Docker image](https://hub.docker.com/r/ipac/firefly), typically at http://localhost:8080/firefly\n", + "2. a public Firefly server such as:\n", + " - IRSA Viewer at https://irsa.ipac.caltech.edu/irsaviewer\n", + " - Rubin Science Platform Portal at https://data.lsst.cloud/portal/app/ (requires authentication; the `ACCESS_TOKEN` environment variable must be set)\n", + "\n", + "If `FIREFLY_URL` is not defined, the default server URL is `http://localhost:8080/firefly`, which is often used for a Firefly server running locally.\n", + "\n", + ".. note::\n", + " The environment variable must be defined in the shell where Jupyter (or the Python session) was started. It may not work if defined in a notebook cell through `%env` or `os.environ`.\n" + ] + }, + { + "cell_type": "markdown", + "id": "0a7a82bb", + "metadata": {}, + "source": [ + "## Imports\n", + "The only required import here is `FireflyClient`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c676470f", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient" + ] + }, + { + "cell_type": "markdown", + "id": "f1da6b4e", + "metadata": {}, + "source": [ + "## Initialize FireflyClient\n", + "There are two ways to initialize `FireflyClient` from Python, depending on whether you're running the notebook in JupyterLab or not:" + ] + }, + { + "cell_type": "markdown", + "id": "c5726946", + "metadata": {}, + "source": [ + "### 1. From Jupyter Notebook (or a Python shell)\n", + "You can use `make_client()` in a Jupyter Notebook (or even a Python shell), which will open the Firefly viewer in a new web browser tab. `make_client()` also allows you to pass the URL directly (other than through environment variable) as the `url` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f337056e", + "metadata": {}, + "outputs": [], + "source": [ + "# fc = FireflyClient.make_client() # URL is taken from FIREFLY_URL env variable\n", + "\n", + "# URL can be defined explicitly as a parameter\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")" + ] + }, + { + "cell_type": "markdown", + "id": "9af544ca", + "metadata": {}, + "source": [ + ".. note::\n", + " When running locally, `make_client()` will usually try to open a browser tab automatically. If it can’t (for example, if you're running on a remote machine), it will print or display a URL that you can click to open." + ] + }, + { + "cell_type": "markdown", + "id": "7e62001a", + "metadata": {}, + "source": [ + ".. warning::\n", + " After initializing `fc`, make sure you’ve opened the Firefly viewer URL before running methods which update the UI." + ] + }, + { + "cell_type": "markdown", + "id": "7d171495", + "metadata": {}, + "source": [ + "### 2. From JupyterLab\n", + "You can use `make_lab_client()` in JupyterLab to open the Firefly viewer in a new tab within the Lab, which provides a more integrated UI experience. This requires you to have [jupyter_firefly_extensions](https://github.com/Caltech-IPAC/jupyter_firefly_extensions/blob/master/README.md) set up in your environment:\n", + "1. In your Python environment, run `pip install jupyter_firefly_extensions`.\n", + "2. Set `FIREFLY_URL` environment variable before running `jupyter lab`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "187e0078", + "metadata": {}, + "outputs": [], + "source": [ + "# fc = FireflyClient.make_lab_client()" + ] + }, + { + "cell_type": "markdown", + "id": "7ae06c1c", + "metadata": {}, + "source": [ + "## Other helpful methods\n", + "\n", + "### Reinitialize the viewer\n", + "To clean the state of the Firefly server (i.e., remove all displayed components and start fresh), you can reinitialize the viewer. This is specifically helpful if a Python connection with the server is already open and re-running `make_lab_client` (or `make_client`) leads to stale state." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6eee34fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.reinit_viewer()" + ] + }, + { + "cell_type": "markdown", + "id": "b5a95d6c", + "metadata": {}, + "source": [ + "### Explicitly open a browser tab\n", + "By default, `make_client()` opens a browser tab for Firefly. You can also trigger that behavior explicitly for a `FireflyClient` object by using the `launch_browser` method. It will return two values: a boolean indicating whether the web browser open was successful, and the URL for your web browser." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c4e3e890", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True,\n", + " 'https://irsa.ipac.caltech.edu/irsaviewer/?__wsch=anNpbmdoYWwyMDI2LTAxLTA3')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.launch_browser()" + ] + }, + { + "cell_type": "markdown", + "id": "a92a4290", + "metadata": {}, + "source": [ + "### Display Firefly URL\n", + "You can use `display_url()` to print the browser URL if running in a terminal, and to show a clickable link if running in a Jupyter notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b9fcbab6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Open your web browser to this link" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fc.display_url()" + ] + }, + { + "cell_type": "markdown", + "id": "118244d2", + "metadata": {}, + "source": [ + "### Set a custom channel\n", + "`FireflyClient` opens a connection to the Firefly server on a particular WebSocket channel. You can override it when using `make_client()` as shown below.\n", + "\n", + "In typical usage, it is unnecessary to set the channel when instantiating `FireflyClient`, as a unique string will be auto-generated. If you do wish to set the channel explicitly (e.g., for sharing your Firefly viewer/display with someone else), take care to make the channel unique." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bc6dbbcf", + "metadata": {}, + "outputs": [], + "source": [ + "fc2 = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\",\n", + " channel_override=\"my-custom-channel\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/initializing-vanilla.rst b/docs/usage/initializing-vanilla.rst deleted file mode 100644 index d93acf6..0000000 --- a/docs/usage/initializing-vanilla.rst +++ /dev/null @@ -1,79 +0,0 @@ -##################################### -Initializing a FireflyClient instance -##################################### - -Once a Firefly server has been identified, the connection parameters can be -used to initialize a :class:`FireflyClient` instance. By default, the value -of the environment variable `FIREFLY_URL` will be used as the server URL, if defined. If -`FIREFLY_URL` is not defined, the default server URL is `http://localhost:8080/firefly` -which is often used for a Firefly server running locally. - -Optional arguments for initializing a `FireflyClient` instance include `channel` -and `html_file`. - -For a default server running locally, use `localhost` or `127.0.0.1` together -with the port that the server is using, and append `/firefly`. The default port is 8080. - -.. code-block:: py - :name: using-localhost - - import firefly_client - fc = firefly_client.FireflyClient('http://127.0.0.1:8080/firefly') - -If the Python session is running on your own machine, you can use the -:meth:`FireflyClient.launch_browser` method to open up a browser tab. - -.. code-block:: py - :name: launch-browser - - fc.launch_browser() - -The :meth:`FireflyClient.launch_browser` method will return two values: a boolean -indicating whether the web browser open was successful, and the URL for your -web browser. - -.. warning:: - - On Mac OS X 10.12.5, an error message may be displayed with a URL and - a note that it doesn't understand the "open location message". If a - browser tab is not automatically opened, copy and paste the displayed - URL into the address bar of your browser. This issue has been fixed - in Mac OS X 10.12.6. - -If your Python session is not running on your local machine, the -:meth:`FireflyClient.launch_browser` -method will display the URL for your web browser. Alternatively, you can use -the :meth:`FireflyClient.display_url` method to print the browser URL if -running in a terminal, and to show a clickable link if running in a -Jupyter notebook. - -.. code-block:: py - - fc.display_url() - -In typical usage, it is unnecessary to set the `channel` parameter when -instantiating `FireflyClient`. A unique string will be auto-generated. -If you do wish to set the channel explicitly, e.g. for sharing your display -with someone else, take care to make the channel unique. - -.. warning:: - - After initializing :class:`FireflyClient`, make sure you have opened a web browser - to the appropriate URL, before proceeding to use the Python API described - in the following sections. - - -Initializing with the make_client Factory Function --------------------------------------------------- - -For an easier initialization, you can use the :meth:`FireflyClient.make_client` -factor function. This function will use the value of the FIREFLY_URL -environment variable for the Firefly server. Additionally, it will attempt -to start a Firefly browser tab or window if possible, and if not, it will -display a link for the Firefly display. - -.. code-block:: py - - fc = FireflyClient.make_client() - - From f2cbdd6fa271bdea451fca4f56e86d7283309286 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Tue, 13 Jan 2026 20:46:38 -0800 Subject: [PATCH 10/17] Replace rst usage docs with up-to-date notebooks Fix minor bug/docs in firefly_client --- docs/usage/callbacks-extensions.ipynb | 174 ++++++++ docs/usage/callbacks-extensions.rst | 40 -- docs/usage/charting.ipynb | 263 +++++++++++ docs/usage/charting.rst | 39 -- docs/usage/displaying-3color.ipynb | 253 +++++++++++ docs/usage/displaying-hips.ipynb | 196 +++++++++ docs/usage/displaying-images.ipynb | 608 ++++++++++++++++++++++++++ docs/usage/displaying-images.rst | 232 ---------- docs/usage/index.rst | 2 + docs/usage/overlaying-regions.ipynb | 262 +++++++++++ docs/usage/overlaying-regions.rst | 40 -- docs/usage/viewing-tables.ipynb | 472 ++++++++++++++++++++ docs/usage/viewing-tables.rst | 71 --- examples/reference-guide.ipynb | 2 +- firefly_client/firefly_client.py | 13 +- 15 files changed, 2238 insertions(+), 429 deletions(-) create mode 100644 docs/usage/callbacks-extensions.ipynb delete mode 100644 docs/usage/callbacks-extensions.rst create mode 100644 docs/usage/charting.ipynb delete mode 100644 docs/usage/charting.rst create mode 100644 docs/usage/displaying-3color.ipynb create mode 100644 docs/usage/displaying-hips.ipynb create mode 100644 docs/usage/displaying-images.ipynb delete mode 100644 docs/usage/displaying-images.rst create mode 100644 docs/usage/overlaying-regions.ipynb delete mode 100644 docs/usage/overlaying-regions.rst create mode 100644 docs/usage/viewing-tables.ipynb delete mode 100644 docs/usage/viewing-tables.rst diff --git a/docs/usage/callbacks-extensions.ipynb b/docs/usage/callbacks-extensions.ipynb new file mode 100644 index 0000000..2f4c444 --- /dev/null +++ b/docs/usage/callbacks-extensions.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a50e0ce4", + "metadata": {}, + "source": [ + "# Advanced: Callbacks and Extensions\n", + "So far we have been sending data to Firefly to visualize. This notebook shows how to add extensions to Firefly to bring back data into our Python session through callback functions. \n", + "\n", + ".. note::\n", + " Callbacks involve interactive UI actions (e.g., point selection), so running the code is only half the story—you must also trigger the UI interaction in the Firefly viewer." + ] + }, + { + "cell_type": "markdown", + "id": "dac26d1a", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details. Then, we display a FITS image from a URL so that we have an image for triggering point-selection event. See [Displaying a FITS Image](./displaying-fits-image.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6f079cb4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", + "\n", + "# Display a FITS image (from URL of WISE W2 cutout of M51 galaxy)\n", + "img_url = 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-w2-int-1b.fits?center=202.4841667,47.23055556&size=400pix'\n", + "plot_id = 'wise-cutout'\n", + "fc.show_fits_image(file_input=img_url, plot_id=plot_id, title='WISE Cutout')" + ] + }, + { + "cell_type": "markdown", + "id": "3551d0f9", + "metadata": {}, + "source": [ + "## Define and register a callback\n", + "A callback receives an event dictionary from Firefly. This example prints out world coordinates when a point-selection event occurs." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "80c57513", + "metadata": {}, + "outputs": [], + "source": [ + "def print_coords(event):\n", + " if 'data' in event and 'wpt' in event['data']:\n", + " wpt = event['data']['wpt'] # world point\n", + " wdata = wpt.split(';')\n", + " ra = float(wdata[0])\n", + " dec = float(wdata[1])\n", + " print(f'ra={ra:.6f}, dec={dec:.6f}')\n", + "\n", + "fc.add_listener(print_coords)" + ] + }, + { + "cell_type": "markdown", + "id": "0fb30b23", + "metadata": {}, + "source": [ + "## Add an extension to trigger the callback\n", + "To activate the callback in point-selection mode, add it as an extension with `ext_type='POINT'`. The title of the extension will appear as a clickable text-button when the Firefly viewer is in point-selection mode." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "21d7963b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ra=202.395939, dec=47.228118\n", + "ra=202.510304, dec=47.185369\n", + "ra=202.496007, dec=47.267874\n" + ] + } + ], + "source": [ + "fc.add_extension(ext_type='POINT', plot_id=None, title='Print coords', extension_id='pickit')" + ] + }, + { + "cell_type": "markdown", + "id": "eca88603", + "metadata": {}, + "source": [ + "## Trigger the callback (manual step)\n", + "In the Firefly browser tab:\n", + "1. Enable point-selection mode (enable 'Lock by click' switch in the bottom right of image display).\n", + "2. Click on the image to select a point.\n", + "3. Click the **Print coords** button to trigger the callback.\n", + "\n", + "You should see RA/Dec printed in your Python session output (which is the last code cell executed in the context of Python notebooks)." + ] + }, + { + "cell_type": "markdown", + "id": "65c42c6b", + "metadata": {}, + "source": [ + "## Remove the callback\n", + "When you’re done, remove the listener:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ca66dbb9", + "metadata": {}, + "outputs": [], + "source": [ + "fc.remove_listener(print_coords)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/callbacks-extensions.rst b/docs/usage/callbacks-extensions.rst deleted file mode 100644 index 710383a..0000000 --- a/docs/usage/callbacks-extensions.rst +++ /dev/null @@ -1,40 +0,0 @@ -Advanced: Callbacks and Extensions ----------------------------------- - -Callback functions can be added to :class:`FireflyClient`. Here is a simple -example of defining and registering a callback that prints world coordinates -to the Python session: - -.. code-block:: py - - def print_coords(event): - if 'wpt' in event['data']: - wpt = event['data']['wpt'] - wdata = wpt.split(';') - ra = float(wdata[0]) - dec = float(wdata[1]) - print('ra={:.6f}, dec={:.6f}'.format(ra,dec)) - - fc.add_listener(print_coords) - -To activate the callback in point-selection mode, add it as an extension -with `ext_type='POINT'`. By default, the title of the extension will appear -as a clickable icon when the Firefly viewer is in point mode. A small image -can be passed in instead via the `img_src` parameter. - -.. code-block:: py - - fc.add_extension(ext_type='POINT', plot_id=None, title='Print coords', - extension_id='pickit') - -To activate point-selection mode, in the Firefly browser tab, select -the 'Lock by click' checkbox at the upper right. Then selecting any point -in an image will overlay a box at that point. Finally, click on the -'Print coords' icon in the image display window to cause the callback -to be triggered, and coordinates to be printed out. - -The callback can be deleted with `FireflyClient.remove_listener`: - -.. code-block:: py - - fc.remove_listener(print_coords) diff --git a/docs/usage/charting.ipynb b/docs/usage/charting.ipynb new file mode 100644 index 0000000..cdaecbb --- /dev/null +++ b/docs/usage/charting.ipynb @@ -0,0 +1,263 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ff215e10", + "metadata": {}, + "source": [ + "# Visualizing tables as Charts\n", + "\n", + "This notebook shows you how to plot columns of a table quickly as scatter charts or histograms in Firefly. You will also learn how to customize the charts using Plotly-style configuration and display spectra from supported type of table." + ] + }, + { + "cell_type": "markdown", + "id": "5c82d0f9", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details. Then, we display a catalog table from a URL so that we have table data to create charts. See [Displaying Tables and Catalogs](./viewing-tables.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d26d722f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"http://localhost:8080/firefly\")\n", + "\n", + "# Display a catalog table (from URL of 2MASS TAP query for M51)\n", + "table_url = \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',202.4841667,47.23055556,0.125))=1\"\n", + "table_id = '2mass-catalog'\n", + "fc.show_table(file_input=table_url, tbl_id=table_id, title='2MASS Catalog')" + ] + }, + { + "cell_type": "markdown", + "id": "1d035b65", + "metadata": {}, + "source": [ + ".. note::\n", + " Firefly automatically tries to plot the displayed table in the \"Active Chart\" tab (typically a ra-dec scatter plot for catalogs). But for full control over plotting, we need the following methods which will create new charts in the \"Pinned Charts\" tab. So make sure to switch to \"Pinned Charts\" tab to see the charts created in this notebook." + ] + }, + { + "cell_type": "markdown", + "id": "8ae74274", + "metadata": {}, + "source": [ + "## Quick scatter plot\n", + "Use `show_xyplot` method of FireflyClient object for a straightforward scatter plot from table columns. X and Y axis column parameters (`xCol` and `yCol`) can be an expression involving one or more table columns. See API docs for more parameters to customize the scatter plot." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3209b784", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_xyplot(tbl_id=table_id, # specifies which table to plot from\n", + " xCol='j_m', yCol='h_m-k_m')" + ] + }, + { + "cell_type": "markdown", + "id": "43225da9", + "metadata": {}, + "source": [ + "## Quick histogram\n", + "Use `show_histogram` method of FireflyClient object for a straightforward histogram from a table column. See API docs for more parameters to customize the histogram." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9642b55b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_histogram(tbl_id=table_id, col='j_m', numBins=30)" + ] + }, + { + "cell_type": "markdown", + "id": "43e3e798", + "metadata": {}, + "source": [ + "## Configurable plots\n", + "Alternatively, more generic method `show_chart` can be used to create similar plot but with a lot more configuration parameters as [Plotly data/layout structure](https://plotly.com/javascript/reference/index/). This gives more control over marker style, axes' titles, etc. \n", + "\n", + "The `trace` dictionary in `data` array should have:\n", + "1. a Firefly-specific key `tbl_id` to specify which table to plot from\n", + "2. the `x` and `y` keys assigned to the column names or expressions prefixed with `tables::`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0a990b0c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trace0 = {\n", + " # Firefly-specific key and values\n", + " 'tbl_id': table_id, 'x': \"tables::j_m\", 'y': \"tables::h_m-k_m\",\n", + " # Plotly keys and values\n", + " 'type': 'scatter', 'mode': 'markers',\n", + " 'marker': dict(size=4, color='red', opacity=0.6)\n", + " }\n", + "layout = {'title': '2MASS color-mag',\n", + " 'xaxis': dict(title='J'), 'yaxis': dict(title='H - K')} \n", + "\n", + "fc.show_chart(data=[trace0], layout=layout)" + ] + }, + { + "cell_type": "markdown", + "id": "d59d2c75", + "metadata": {}, + "source": [ + "Similarly, histograms can also be created using `show_chart` method but it has more Firefly-specific keys as shown below:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d0205c2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trace0 = dict(type='fireflyHistogram',\n", + " marker=dict(color='rgba(153, 51, 153, 0.8)'),\n", + " firefly=dict(\n", + " tbl_id=table_id,\n", + " options=dict(\n", + " algorithm='fixedsizeBins',\n", + " fixedBinSizeSelection='numBins',\n", + " numBins=30,\n", + " columnOrExpr='j_m'\n", + " )))\n", + "\n", + "layout_hist = dict(title='Photometry histogram',\n", + " xaxis=dict(title='J (mag)'),\n", + " yaxis=dict(title='Number'))\n", + "\n", + "fc.show_chart(layout=layout_hist, data=[trace0])" + ] + }, + { + "cell_type": "markdown", + "id": "a9517c84", + "metadata": {}, + "source": [ + "## Spectra\n", + "If a table follows [IVOA Spectrum Data Model](https://www.ivoa.net/documents/SpectrumDM/), simply displaying the table will automatically create a spectrum plot in the \"Active Chart\" tab (make sure to switch to this tab). You can fine tune it directly in Firefly UI using the chart options dialog in the toolbar." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "58730b96", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "euclid_spec_url = 'http://irsa.ipac.caltech.edu/api/spectrumdm/convert/euclid/q1/SIR/102159776/EUC_SIR_W-COMBSPEC_102159776_2024-11-05T16:21:17.235160Z.fits?dataset_id=euclid_combspec&hdu=217'\n", + "\n", + "fc.show_table(euclid_spec_url, title='Euclid Spectrum')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/charting.rst b/docs/usage/charting.rst deleted file mode 100644 index 3c6cdea..0000000 --- a/docs/usage/charting.rst +++ /dev/null @@ -1,39 +0,0 @@ -Making Plots and Histograms ---------------------------- - -Once a table has been uploaded to Firefly, :meth:`FireflyClient.show_chart` -will display a scatter plot. The `x` and `y` parameters can be set -to use names of columns in the table, or to arithmetic expressions that combine -table columns. - -.. code-block:: py - - trace1 = dict(tbl_id='2mass-tbl', x='tables::j_m', - y='tables::h_m-k_m', mode='markers', type='scatter', - marker=dict(size=4)) - - layout1 = dict(title='2MASS color-mag', - xaxis=dict(title='J'), yaxis=dict(title='H - K')) - - fc.show_chart(layout=layout1, data=[trace1]) - -A histogram can be displayed with :meth:`FireflyClient.show_chart`: - -.. code-block:: py - - trace2 = dict(type='fireflyHistogram', - name='j_m', - marker=dict(color='rgba(153, 51, 153, 0.8)'), - firefly=dict(tbl_id='2mass-tbl', - options=dict(algorithm='fixedsizeBins', - fixedBinSizeSelection='numBins', - numBins=30, - columnOrExpr='j_m'))) - - layout_hist = dict(title='Photometry histogram', - xaxis=dict(title='j_m (mag)'), - yaxis=dict(title='Number')) - - fc.show_chart(layout=layout_hist, data=[trace2]) - -Both plot types include options for log scaling as well as other settings. diff --git a/docs/usage/displaying-3color.ipynb b/docs/usage/displaying-3color.ipynb new file mode 100644 index 0000000..4a018c2 --- /dev/null +++ b/docs/usage/displaying-3color.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cab445d6", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "067800d8", + "metadata": {}, + "source": [ + "# Displaying 3-Color FITS Images\n", + "\n", + "This notebook shows how to create a 3-color composite image (RGB) in Firefly." + ] + }, + { + "cell_type": "markdown", + "id": "110faec8", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4bd1902a", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")" + ] + }, + { + "cell_type": "markdown", + "id": "03bd3396", + "metadata": {}, + "source": [ + "## Show 3-color image\n", + "A 3-color composite is built by providing per-band inputs to the `show_fits_3color` method.\n", + "\n", + "This method accepts a `three_color_params` parameter, which takes a list of dictionaries (one per band) in the order **[R, G, B]**. The dictionary should have key names defined in [FITS Plotting Parameters](https://github.com/Caltech-IPAC/firefly/blob/dev/docs/fits-plotting-parameters.md) for the Firefly JavaScript. Key names can be *case-insensitive*.\n", + "\n", + ".. warning::\n", + " The 3-color image creation from FireflyClient uses low-level API at the moment. Hence, `show_fits_3color` is not symmetric to the API of `show_fits_image` method and may be unstable (in some cases).\n", + "\n", + "### From a URL\n", + "Provide `url` key in each band's dictionary:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5707c31e", + "metadata": {}, + "outputs": [], + "source": [ + "# [W4, W3, W2] from WISE all-sky as [R, G, B]\n", + "rgb_urls = [f'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-{band}-int-1b.fits'\n", + " for band in ['w4', 'w3', 'w2']]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a6e7b037", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "three_color_params = [\n", + " {\n", + " 'url': url,\n", + " 'title': '3 color image'\n", + " } for url in rgb_urls\n", + "]\n", + "\n", + "fc.show_fits_3color(three_color_params=three_color_params,\n", + " plot_id='3color-img')" + ] + }, + { + "cell_type": "markdown", + "id": "eb60d37e", + "metadata": {}, + "source": [ + "### From a local file\n", + "Provide `file` key in each band's dictionary. The `file` value is the location on Firefly server instead of location on your machine, so it must be the return value of `upload_file` method." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "55e25051", + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.utils.data import download_file\n", + "rgb_local_paths = [download_file(url, cache=True, timeout=120) for url in rgb_urls]\n", + "\n", + "three_color_params = [\n", + " {\n", + " 'file': fc.upload_file(fpath),\n", + " 'title': '3 color image'\n", + " } for fpath in rgb_local_paths\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0440c11e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_fits_3color(three_color_params=three_color_params,\n", + " plot_id='3color-img')" + ] + }, + { + "cell_type": "markdown", + "id": "28728b53", + "metadata": {}, + "source": [ + "### From IRSA-Specific Searches\n", + "\n", + "Since `show_fits_3color` uses low-level API, you can also retrieve images on-the-fly (instead of providing a url or local path) by utilizing Firefly's image search processors that powers the IRSA archives. See more details in [Retrieving Images Using IRSA-Specific Searches](#)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b53fb1c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", + "ra = 202.4841667\n", + "dec = 47.23055556\n", + "target = f'{ra};{dec};EQ_J2000'\n", + "size_deg = 0.25\n", + "\n", + "threeC = [\n", + " {\n", + " 'Type': 'SERVICE',\n", + " 'Service': 'WISE',\n", + " 'Title': '3 color',\n", + " 'SurveyKey': '3a',\n", + " 'SurveyKeyBand': '4',\n", + " 'WorldPt': target,\n", + " 'RangeValues': rv,\n", + " 'SizeInDeg': size_deg\n", + " },\n", + " {\n", + " 'Type': 'SERVICE',\n", + " 'Service': 'WISE',\n", + " 'Title': '3 color',\n", + " 'SurveyKey': '3a',\n", + " 'SurveyKeyBand': '2',\n", + " 'WorldPt': target,\n", + " 'RangeValues': rv,\n", + " 'SizeInDeg': size_deg\n", + " },\n", + " {\n", + " 'Type': 'SERVICE',\n", + " 'Service': 'WISE',\n", + " 'Title': '3 color',\n", + " 'SurveyKey': '3a',\n", + " 'SurveyKeyBand': '1',\n", + " 'WorldPt': target,\n", + " 'RangeValues': rv,\n", + " 'SizeInDeg': size_deg\n", + " }\n", + "]\n", + "\n", + "fc.show_fits_3color(three_color_params=threeC,\n", + " plot_id='wise_m51')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22f07cce", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/displaying-hips.ipynb b/docs/usage/displaying-hips.ipynb new file mode 100644 index 0000000..9533bbb --- /dev/null +++ b/docs/usage/displaying-hips.ipynb @@ -0,0 +1,196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f3a8081", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "f73449da", + "metadata": {}, + "source": [ + "# Displaying HiPS Images\n", + "\n", + "This notebook shows how to display HiPS (Hierarchical Progressive Survey) images in Firefly and how to add MOC layer to them." + ] + }, + { + "cell_type": "markdown", + "id": "396f5621", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3fc5ef0", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")" + ] + }, + { + "cell_type": "markdown", + "id": "579df16f", + "metadata": {}, + "source": [ + "## Show HiPS image\n", + "HiPS images are fetched and rendered progressively from a HiPS survey endpoint. To display a HiPS image, use `show_hips` method of the FireflyClient object and provide:\n", + "\n", + "- `hips_root_url`: the HiPS survey endpoint\n", + "- `WorldPt`: the target position as a serialized string: `';;EQ_J2000'`\n", + "- `SizeInDeg`: (optional) the field of view size in degrees" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa9883ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hips_url = 'https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color/'\n", + "# hips_url = 'http://alasky.u-strasbg.fr/DSS/DSSColor'\n", + "\n", + "ra = 202.4841667\n", + "dec = 47.23055556\n", + "size_in_deg = 1.6\n", + "target = f'{ra};{dec};EQ_J2000'\n", + "\n", + "hips_plot_id = 'hips-img-id' # to identify this particular image display for later operations\n", + "\n", + "fc.show_hips(plot_id=hips_plot_id, \n", + " hips_root_url=hips_url,\n", + " Title='HiPS image', \n", + " WorldPt=target,\n", + " SizeInDeg=size_in_deg # can be omitted\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "4f1bba1c", + "metadata": {}, + "source": [ + "## Modify displayed HiPS\n", + "You can modify this image display similar to how you would [modify a FITS image display](#)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fc110e4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_pan(plot_id=hips_plot_id, # note we use the plot id we defined above\n", + " x=ra+0.25, y=dec-0.25, coord='j2000')" + ] + }, + { + "cell_type": "markdown", + "id": "72a9ca79", + "metadata": {}, + "source": [ + "It's often more convinient to modify image display through UI controls in the image toolbar instead." + ] + }, + { + "cell_type": "markdown", + "id": "b9d746d1", + "metadata": {}, + "source": [ + "## Overlay MOC (sky coverage map)\n", + "You can overlay a MOC (Multi-Order Coverage map) on the HiPS image to visually compare the sky coverage. A MOC is typically a single-column table in FITS format, so we can display it using the `show_table` method. It is recommended to set the `visible` parameter to `False` so as not to show a table display in Firefly after fetching the table data for the provided MOC file." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7539af31", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "moc_url = 'https://irsa.ipac.caltech.edu/data/MOC/spitzer_seip.moc.fits'\n", + "\n", + "fc.show_table(file_input=moc_url)" + ] + }, + { + "cell_type": "markdown", + "id": "b3f8ebd1", + "metadata": {}, + "source": [ + ".. note::\n", + " It may take some time to load the MOC file, and since there's no feedback from the table display, look for the spinner on the top left of the HiPS image display." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/displaying-images.ipynb b/docs/usage/displaying-images.ipynb new file mode 100644 index 0000000..0ad50aa --- /dev/null +++ b/docs/usage/displaying-images.ipynb @@ -0,0 +1,608 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e3b6f6e2", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "5eb7e6e3", + "metadata": {}, + "source": [ + "# Displaying FITS Images\n", + "\n", + "This notebook shows how to display FITS images in Firefly and how to modify the image display (pan/zoom/stretch/align)." + ] + }, + { + "cell_type": "markdown", + "id": "3b560f3a", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4fb58c6", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Following imports are just for example data fetching and processing (not needed always)\n", + "from astropy.utils.data import download_file\n", + "from astropy.io import fits\n", + "from astropy.convolution import convolve, Gaussian2DKernel\n", + "import io\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")" + ] + }, + { + "cell_type": "markdown", + "id": "aa994567", + "metadata": {}, + "source": [ + "## Show FITS Image in Firefly\n", + "\n", + "Use the `show_fits_image` method of the FireflyClient object to display a FITS image. It can take a local file path, URL, or a file-like object as input via the `file_input` parameter.\n", + "\n", + "It is recommended to explicitly specify the `plot_id` parameter for later use in modifying the image display." + ] + }, + { + "cell_type": "markdown", + "id": "ad969ddf", + "metadata": {}, + "source": [ + "### From a URL\n", + "You can specify url of any FITS image. Here we use cutout of a WISE all-sky image of M51 galaxy in W2 band:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d34e928b", + "metadata": {}, + "outputs": [], + "source": [ + "cutout_image_url = 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-w2-int-1b.fits?center=202.4841667,47.23055556&size=400pix'" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a1e5eb4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cutout_image_plot_id = 'wise-cutout'\n", + "fc.show_fits_image(file_input=cutout_image_url,\n", + " plot_id=cutout_image_plot_id,\n", + " title='WISE Cutout')" + ] + }, + { + "cell_type": "markdown", + "id": "99723675", + "metadata": {}, + "source": [ + "### From a local file\n", + "You can specify path of any local FITS file. Here we download the full WISE all-sky image of M51 in W2 and use its file path:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f242a994", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/Users/jsinghal/.astropy/cache/download/url/ef686a664b2c17e3d8590b511be8a400/contents'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_image_url = 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-w2-int-1b.fits'\n", + "full_image_fpath = download_file(full_image_url, cache=True, timeout=120)\n", + "full_image_fpath" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d59743bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "full_image_plot_id = 'wise-fullimage'\n", + "fc.show_fits_image(file_input=full_image_fpath,\n", + " plot_id=full_image_plot_id, # using a different plot id to not overwrite previous plot\n", + " title='WISE Full Image')" + ] + }, + { + "cell_type": "markdown", + "id": "7b3ca9c2", + "metadata": {}, + "source": [ + "### From an in-memory file-like object\n", + "You can also use a file-like object (e.g. `BytesIO` stream) instead of a local file path or URL. This is useful when you are working with a FITS file in memory and don't want to write it to disk. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a5e54b24", + "metadata": {}, + "outputs": [], + "source": [ + "processed_fits = io.BytesIO()\n", + "\n", + "with fits.open(cutout_image_url) as hdul:\n", + " # Do some processing on FITS image data (like simple Gaussian smoothing here)\n", + " img_data = hdul[0].data\n", + " gaussian_kernel = Gaussian2DKernel(x_stddev=2)\n", + " img_data = convolve(img_data, gaussian_kernel)\n", + "\n", + " new_hdu = fits.PrimaryHDU(data=img_data, header=hdul[0].header)\n", + " new_hdu.writeto(processed_fits, overwrite=True)\n", + " processed_fits.seek(0) # to bring reading pointer to the beginning of file" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3c8b16be", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_fits_image(file_input=processed_fits,\n", + " plot_id='wise-processed',\n", + " title='Processed WISE Cutout')" + ] + }, + { + "cell_type": "markdown", + "id": "478b5067", + "metadata": {}, + "source": [ + "## Modify the displayed image(s)\n", + "Zooming, panning, and changing the stretch is accomplished by following methods, passing in the `plot_id` as the first argument.\n", + "\n", + "### Panning to a coordinate\n", + "To pan the full image to center on the M51 cutout's center:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "06df4d66", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_pan(plot_id=full_image_plot_id, # note we use the plot id we defined above\n", + " x=202.4841667, y=47.23055556, coord='j2000')" + ] + }, + { + "cell_type": "markdown", + "id": "d0838835", + "metadata": {}, + "source": [ + "### Zooming\n", + "To zoom into the full image by a factor of 2:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6ee07da8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_zoom(plot_id=full_image_plot_id, factor=2)" + ] + }, + { + "cell_type": "markdown", + "id": "2c88e71b", + "metadata": {}, + "source": [ + "### Changing the color stretch\n", + "Set the stretch for the full image based on IRAF zscale interval with a linear algorithm:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "047d45e6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True,\n", + " 'rv_string': '91,1.000000,91,1.000000,NaN,2.000000,44,25,600,120,0,NaN,1.000000'}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.set_stretch(plot_id=full_image_plot_id, stype='zscale', algorithm='linear')\n", + "\n", + "# Or for a log stretch with sigma limits:\n", + "# fc.set_stretch(plot_id=full_image_plot_id, stype='sigma', algorithm='log', lower_value=-2, upper_value=30)" + ] + }, + { + "cell_type": "markdown", + "id": "af1be2fb", + "metadata": {}, + "source": [ + "### Aligning multiple images\n", + "You can align mutliple images covering the same sky location by target, WCS, pixels, etc. Optionally, you can also lock the alignment so that panning/zooming one image will pan/zoom the other images in sync.\n", + "\n", + "To align both full image and cutout image by WCS and to lock the WCS matching:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "364d0b32", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.align_images(lock_match=True)" + ] + }, + { + "cell_type": "markdown", + "id": "ae7bf7dc", + "metadata": {}, + "source": [ + "## Advanced Usage (Low-level, Unstable API)\n", + "In most cases, you do not need the low-level API to create or modify image displays, since the higher-level methods shown above cover common workflows and you can always directly use the UI controls in Firefly. For completeness, the sections below document low-level operations that may be useful to advanced users. Note that the low-level API is unstable and may change, so examples here might not work in all cases." + ] + }, + { + "cell_type": "markdown", + "id": "be52e9e5", + "metadata": {}, + "source": [ + "### Changing the color map\n", + "FireflyClient doesn't provide a high-level method to change the color map but you can do so by\n", + "using the lower-level API: dispatch the relevant JavaScript action with the required parameters in the payload." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ae0feb19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.dispatch(action_type='ImagePlotCntlr.ColorChange',\n", + " payload={\n", + " 'plotId': full_image_plot_id,\n", + " 'cbarId': 24 # skyview color map\n", + " })" + ] + }, + { + "cell_type": "markdown", + "id": "13496ec5", + "metadata": {}, + "source": [ + "### Specifying Zoom and Stretch Values When First Displaying an Image\n", + "It can be advantageous to specify the zoom, stretch, and color table parameters in the same command that displays the image, to avoid problems with the later commands being ignored when the image display has not completed.\n", + "\n", + "\n", + "To specify the stretch as part of the image display, you can generate a range values string. This example is for an asinh stretch with a Q value of 6 and an upper level of 98 percent:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "de44bb1e", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import RangeValues\n", + "rvstring = RangeValues.create_rv_standard(algorithm='asinh',\n", + " asinh_q_value=6,\n", + " upper_value=98.0)" + ] + }, + { + "cell_type": "markdown", + "id": "40863744", + "metadata": {}, + "source": [ + "The zoom value can be specified in several different ways. When displaying images with different pixel scales, the best practice is to specify `ZoomType='ARCSEC_PER_SCREEN_PIX'` and then the numerical value, e.g. `ZoomArcsecPerScreenPix=0.3`.\n", + "\n", + "These parameters use the key names defined in [FITS Plotting Parameters](https://github.com/Caltech-IPAC/firefly/blob/dev/docs/fits-plotting-parameters.md) for the Firefly JavaScript. They can be passed to the `show_fits_image` method as additional keyword arguments:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58155b58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_fits_image(file_input=cutout_image_url,\n", + " plot_id=cutout_image_plot_id,\n", + " title='WISE Cutout',\n", + " # additional keyword arguments\n", + " ColorTable=4,\n", + " ZoomType='ARCSEC_PER_SCREEN_PIX',\n", + " ZoomArcsecPerScreenPix=0.3,\n", + " RangeValues=rvstring\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "a689462c", + "metadata": {}, + "source": [ + "### Retrieving Images Using IRSA-Specific Searches\n", + "Instead of providing FITS image file (as a local file path, URL, or file-like object), you can also retrieve images by utilizing Firefly's image search processors that powers the IRSA archives.\n", + "\n", + "A table of available projects for image searches is maintained in the Firefly code base. The\n", + "information can be retrieved into an Astropy table:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "ef8b6b99", + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.table import Table\n", + "surveys = Table.read('https://raw.githubusercontent.com/Caltech-IPAC/firefly/dev/src/firefly/java/edu/caltech/ipac/firefly/resources/irsa-image-master-table.csv',\n", + " format='csv')" + ] + }, + { + "cell_type": "markdown", + "id": "04d74c05", + "metadata": {}, + "source": [ + "To select images available for the WISE AllSky project:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "f8a82d3e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Table length=4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
missionIdprojectsubProjectsurveyKeyacronymwavebandIdimageIdtitlewavelengthwavelengthDescwaveTypeprojectTypeKeyprojectTypeDescminRangeDegmaxRangeDeghelpUrltooltipapiTypefilterdataTypedefaultRgbColor
str8str115str8str33str25str11str2str42float64str15str7str13str13float64float64str74str46str7str80str5str5
WISEWISE AllSky Atlas--1bWISE AllSky W11--W1 (3.4 microns)3.43.4 micronsmid-IRall-skyall-sky0.010.2https://irsa.ipac.caltech.edu/docs/irsaviewer_datasets.html#WISEAll-SkyWISE AllSky SIngle Exposure W1 3.4 micronsWISE--image--
WISEWISE AllSky Atlas--1bWISE AllSky W22--W2 (4.6 microns)4.64.6 micronsmid-IRall-skyall-sky0.010.2https://irsa.ipac.caltech.edu/docs/irsaviewer_datasets.html#WISEAll-SkyWISE AllSky SIngle Exposure W2WISE--image--
WISEWISE AllSky Atlas--1bWISE AllSky W33--W3 (12 microns)12.012 micronsmid-IRall-skyall-sky0.010.2https://irsa.ipac.caltech.edu/docs/irsaviewer_datasets.html#WISEAll-SkyWISE AllSky SIngle Exposure W3WISE--image--
WISEWISE AllSky Atlas--1bWISE AllSky W44--W4 (22 microns)22.022 micronsmid-IRall-skyall-sky0.010.2https://irsa.ipac.caltech.edu/docs/irsaviewer_datasets.html#WISEAll-SkyWISE AllSky SIngle Exposure W4WISE--image--
" + ], + "text/plain": [ + "\n", + "missionId project subProject ... filter dataType defaultRgbColor\n", + " str8 str115 str8 ... str80 str5 str5 \n", + "--------- ------------------ ---------- ... ------ -------- ---------------\n", + " WISE WISE AllSky Atlas -- ... -- image --\n", + " WISE WISE AllSky Atlas -- ... -- image --\n", + " WISE WISE AllSky Atlas -- ... -- image --\n", + " WISE WISE AllSky Atlas -- ... -- image --" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wise_surveys = surveys[surveys['missionId'] == 'WISE']\n", + "wise_surveys[['AllSky' in project for project in wise_surveys['project']]]" + ] + }, + { + "cell_type": "markdown", + "id": "4024ab85", + "metadata": {}, + "source": [ + "To search the WISE AllSky survey in W1 band:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58c4e5f8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "size_in_arcsec = 600\n", + "ra = 202.4841667\n", + "dec = 47.23055556\n", + "target = f'{ra};{dec};EQ_J2000'\n", + "fc.show_fits_image(plot_id='IRAC1',\n", + " Title='WISE AllSky W1 Search',\n", + " Type='SERVICE',\n", + " Service='WISE', # apiType\n", + " SurveyKey='1b', # surveyKey\n", + " SurveyKeyBand='1', # wavebandId\n", + " WorldPt=target,\n", + " SizeInDeg=size_in_arcsec/3600,\n", + " ColorTable=1,\n", + " ZoomType='ARCSEC_PER_SCREEN_PIX',\n", + " ZoomArcsecPerScreenPix=0.3,\n", + " RangeValues=rvstring)" + ] + }, + { + "cell_type": "markdown", + "id": "9134fee2", + "metadata": {}, + "source": [ + "Since we added additional images to the viewer, we re-align and lock them by WCS so panning/zooming stays in sync:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a635fb7", + "metadata": {}, + "outputs": [], + "source": [ + "fc.align_images(lock_match=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/displaying-images.rst b/docs/usage/displaying-images.rst deleted file mode 100644 index 128033d..0000000 --- a/docs/usage/displaying-images.rst +++ /dev/null @@ -1,232 +0,0 @@ -################# -Displaying Images -################# - -Since Firefly was originally developed to visualize data products from -astronomical archives, displaying an image is a two-step process of -uploading a file and then showing it. The examples here use the data -products downloaded in :ref:`firefly_client-getting-started`. - -The :meth:`FireflyClient.upload_file` method uploads the file and -returns a location understood by the Firefly server. The location -string is passed to :meth:`FireflyClient.show_fits` to display -the image. In the display step, -it is recommended to explicitly specify the `plot_id` parameter -for later use in modifying the image display. - -.. code-block:: py - - fval = fc.upload_file('2mass-m31-green.fits') - fc.show_fits(fval, plot_id='m31-green', title='2MASS H, M31') - - -Modifying an Image Display --------------------------- - -Zooming, panning, and changing the stretch (mapping of pixels to display -values) is accomplished by corresponding methods, passing in the `plot_id` -as the first argument. - -.. code-block:: py - - fc.set_zoom('m31-green', 4) - fc.set_pan('m31-green', 200, 195) - fc.set_stretch('m31-green', 'sigma', 'log', lower_value=-2, upper_value=30) - -A "zscale" stretch may be commanded with alternate parameters: - -.. code-block:: py - - fc.set_stretch('m31-green', 'zscale', 'linear') - -At present the API does not include a function for changing the color map -for the image. The relevant Javascript action may be invoked using -:meth:`FireflyClient.dispatch`: - -.. code-block:: py - - fc.dispatch(action_type='ImagePlotCntlr.ColorChange', - payload={'plotId': 'm31-green', - 'cbarId': 16}) - -The colormap can be reverted to the original grayscale by using the -command above together with `'cbarId' : 0`. - - -Specifying Zoom and Stretch Values When First Displaying an Image ------------------------------------------------------------------ - -It is often advantageous to specify the zoom, stretch, and color table -parameters in the same command that displays the image, to avoid problems -with the later commands being ignored when the image display has not completed. - - -To specify the stretch as part of the image display, you can generate -a range values string. This example is for an asinh stretch with a Q -value of 6 and an upper level of 98 percent: - -.. code-block:: py - - rvstring = fc._create_rangevalues_standard(algorithm='asinh', - asinh_q_value=6, - upper_value=98.0) - -The zoom value can be -specified in several different ways. When displaying images with different -pixel scales, the best practice is to specify -`ZoomType='ARCSEC_PER_SCREEN_PIX'` and then the numerical value, -e.g. `ZoomArcsecPerScreenPix=0.3`. - -The color table is specified with an integer. The pan point can be -specified with parameter `WorldPt`. For example: - -.. code-block:: py - - size_in_arcsec = 400 - ra = 150.00983 - dec = 2.59783 - target = '{};{};EQ_J2000'.format(ra, dec) - fc.show_fits(plot_id='IRAC1', - Title='COSMOS 3.6um', - Type='SERVICE', - Service='ATLAS', - SurveyKey='cosmos.cosmos_irac', - SurveyKeyBand='IRAC1', - WorldPt=target, - SizeInDeg=size_in_arcsec/3600, - ColorTable=1, - ZoomType='ARCSEC_PER_SCREEN_PIX', - ZoomArcsecPerScreenPix=0.3, - RangeValues=rvstring) - -Turning On WCS Locking ----------------------- - -When multiple images covering the same sky location are displayed, you can -align and lock by sky coordinates. To lock and align by world coordinates: - -.. code-block:: py - - fc.align_images(lock_match=True) - -Retrieving Images Using IRSA-Specific Searches ----------------------------------------------- - -Firefly includes image search processors for Infrared Science Archive (IRSA) -image holdings. A table of available projects for image searches is -maintained in the Firefly code base. The information can be retrieved -into an Astropy table: - -.. code-block:: py - - from astropy.table import Table - surveys = Table.read('https://raw.githubusercontent.com/Caltech-IPAC/firefly/dev/src/firefly/' + - 'java/edu/caltech/ipac/firefly/resources/irsa-image-master-table.csv', - format='csv') - -To select images available for the COSMOS project: - -.. code-block:: py - - cosmos_surveys = surveys[surveys['missionId'] == 'COSMOS'] - -To search the survey: - -.. code-block:: py - - size_in_arcsec = 200 - ra = 150.00983 - dec = 2.59783 - target = '{};{};EQ_J2000'.format(ra, dec) - fc.show_fits(plot_id='IRAC1', - Title='COSMOS 3.6um', - Type='SERVICE', - Service='ATLAS', - SurveyKey='cosmos.cosmos_irac', - SurveyKeyBand='IRAC1', - WorldPt=target, - SizeInDeg=size_in_arcsec/3600, - ColorTable=1, - ZoomType='ARCSEC_PER_SCREEN_PIX', - ZoomArcsecPerScreenPix=0.3, - RangeValues=rvstring) - - -Displaying HiPS Images ----------------------- - -While the above examples involve displaying FITS images, it's also possible to view a HiPS image using a different method that involves specifying the base URL where the target search is performed. -To search the survey: - -.. code-block:: py - - hips_url = 'https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color/' - size_in_deg = 0.2 - ra = 274.700727 - dec = -13.807228 - target = '{};{};EQ_J2000'.format(ra, dec) - fc.show_hips(viewer_id='hipspc1', - plot_id='HipsID1-1', - hips_root_url = hips_url, - Title='HiPS-2m', - WorldPt=target, - SizeInDeg=size_in_deg) - - -Displaying 3-Color Images -------------------------- - -It is possible to create a 3-color composite image using a list of dictionaries containing included parameters on all given bands (or simply one dictionary for that band). For example: - -.. code-block:: py - - rv = '92,-2,92,8,NaN,2,44,25,600,120' - ra = 210.80227 - dec = 54.34895 - target = '{};{};EQ_J2000'.format(ra, dec) - threeC= [ - { - 'Type' : 'SERVICE', - 'Service' : 'WISE', - 'Title' : '3 color', - 'SurveyKey' : '3a', - 'SurveyKeyBand': '3', - 'WorldPt' : target, - 'RangeValues': rv, - 'SizeInDeg' : '.14' - }, - { - 'Type' : 'SERVICE', - 'Service' : 'WISE', - 'Title' : '3 color', - 'SurveyKey' : '3a', - 'SurveyKeyBand': '2', - 'WorldPt' : target, - 'RangeValues': rv, - 'SizeInDeg' : '.14' - }, - { - 'Type' : 'SERVICE', - 'Service' : 'WISE', - 'Title' : '3 color', - 'SurveyKey' : '3a', - 'SurveyKeyBand': '1', - 'WorldPt' : target, - 'RangeValues': rv, - 'SizeInDeg' : '.14' - }] - fc.show_fits_3color(threeC, - plot_id='wise_m101', - viewer_id='3C') - - -Displaying Image from a URL ---------------------------- - -If you have the URL of an image, you can pass it directly instead of -downloading it and then uploading it to firefly: - -.. code-block:: py - - image_url = 'http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix' - fc.show_fits(url=image_url, plot_id='wise-allsky', title='WISE all-sky') diff --git a/docs/usage/index.rst b/docs/usage/index.rst index 8bbe48b..06d27ac 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -8,6 +8,8 @@ Using firefly_client getting-started initializing-vanilla displaying-images + displaying-3color + displaying-hips overlaying-regions viewing-tables charting diff --git a/docs/usage/overlaying-regions.ipynb b/docs/usage/overlaying-regions.ipynb new file mode 100644 index 0000000..003c59e --- /dev/null +++ b/docs/usage/overlaying-regions.ipynb @@ -0,0 +1,262 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "caa4d3cd", + "metadata": {}, + "source": [ + "# Visualizing Regions on Images\n", + "\n", + "Firefly supports [DS9-style region](https://ds9.si.edu/doc/ref/region.html) overlays. This notebook demonstrates how to create a region layer, add/remove region data, and delete a layer." + ] + }, + { + "cell_type": "markdown", + "id": "90681d7f", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details. Then, we display a FITS image from a URL so that we have an image to overlay regions on. See [Displaying a FITS Image](./displaying-fits-image.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b195f0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from firefly_client import FireflyClient\n", + "import tempfile\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", + "\n", + "# Display a FITS image (from URL of WISE W2 cutout of M51 galaxy)\n", + "img_url = 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-w2-int-1b.fits?center=202.4841667,47.23055556&size=400pix'\n", + "plot_id = 'wise-cutout'\n", + "fc.show_fits_image(file_input=img_url, plot_id=plot_id, title='WISE Cutout')" + ] + }, + { + "cell_type": "markdown", + "id": "46b50b8c", + "metadata": {}, + "source": [ + "## Create a region layer\n", + "\n", + "Regions are drawn as layers on top of images. Typically, you create a layer with `overlay_region_layer()`, then add more items with `add_region_data()`.\n", + "\n", + "### From region commands (string)\n", + "\n", + "A string or list of strings containing [region commands](https://ds9.si.edu/doc/ref/region.html) can be added as an overlay layer on an image." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8ff1f3df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "region_data = [\"icrs\",\n", + " \"circle 202.4841667d 47.23055556d 6' # color=red\",\n", + " \"text 202.5608556d 47.3021292d {M51} # color=red\",\n", + " ]\n", + "region_layer_id = 'wise-regions'\n", + "fc.overlay_region_layer(region_data=region_data,\n", + " title='WISE Annotations',\n", + " region_layer_id=region_layer_id,\n", + " plot_id=plot_id)" + ] + }, + { + "cell_type": "markdown", + "id": "e9c02ca6", + "metadata": {}, + "source": [ + "### From a region file\n", + "If you have a region file (.reg or .txt), you can directly upload that using `upload_file` method and overlay it by using `file_on_server` parameter of `overlay_region_layer` method." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "13f8a78f", + "metadata": {}, + "outputs": [], + "source": [ + "# Set it to region file you have locally\n", + "reg_fpath = None\n", + "\n", + "# Here we genrate a region file using region data defined above\n", + "with tempfile.NamedTemporaryFile(mode='w+t', delete=False, suffix=\".txt\") as reg_file:\n", + " reg_file.write(\"\\n\".join(region_data))\n", + " reg_fpath = reg_file.name" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4d7fa9e6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.overlay_region_layer(file_on_server=fc.upload_file(reg_fpath),\n", + " title='WISE Annotations',\n", + " region_layer_id=region_layer_id, # same ID to replace previous one\n", + " plot_id=plot_id)" + ] + }, + { + "cell_type": "markdown", + "id": "93cb573e", + "metadata": {}, + "source": [ + "## Add region data to an existing layer\n", + "\n", + "To add more regions to the same layer, use `add_region_data()`. Pass the ID of that layer as `region_layer_id` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f6794996", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ra, dec = 202.49808, 47.26617\n", + "point_source = f'icrs;point {ra}d {dec}d # point=cross 25 text={{NGC 5195}} color=cyan'\n", + "fc.add_region_data(region_data=point_source,\n", + " region_layer_id=region_layer_id)" + ] + }, + { + "cell_type": "markdown", + "id": "fb678124", + "metadata": {}, + "source": [ + "## Remove a region command\n", + "\n", + "You can use `remove_region_data()` to remove an individual region command if you saved the exact string you added." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b3b1cc99", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.remove_region_data(region_data=point_source, region_layer_id=region_layer_id)" + ] + }, + { + "cell_type": "markdown", + "id": "76646355", + "metadata": {}, + "source": [ + "## Delete an entire region layer\n", + "\n", + "To remove the entire region layer, use `delete_region_layer()`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0359b1c7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.delete_region_layer(region_layer_id)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/overlaying-regions.rst b/docs/usage/overlaying-regions.rst deleted file mode 100644 index 9f39273..0000000 --- a/docs/usage/overlaying-regions.rst +++ /dev/null @@ -1,40 +0,0 @@ -Visualizing Regions -------------------- - -The API includes detailed support for ds9-style regions. A list of strings -containing region commands can be added to an overlay layer on an image. - -.. code-block:: py - - region_data = ['J2000', - 'text 10.6845417 41.2689167{M31 Nucleus} #color=red', - 'point 10.7035000 41.2535833 # point=circle 20', - 'image;line(249.5 164.1 283.8 206.6) # color=blue width=5'] - fc.add_region_data(region_data=region_data, region_layer_id='layer1', - title='my_marks', plot_id='m31-green') - -An individual region command can be removed using -:meth:`FireflyClient.remove_region_data`. To remove the blue line: - -.. code-block:: py - - fc.remove_region_data(region_data=region_data[-1], - region_layer_id='layer1') - -An entire region layer can be deleted with :meth:`FireflyClient.delete_region_layer`: - -.. code-block:: py - - fc.delete_region_layer(region_layer_id='layer1', plot_id='m31-green') - -A region layer can be uploaded from a file and overlaid on an image by -using :meth:`FireflyClient.upload_file` and :meth:`FireflyClient.overlay_region_layer`: - -.. code-block:: py - - with open('myreg.txt', 'w') as fh: - fh.write('\n'.join(region_data)) - rval = fc.upload_file('myreg.txt') - fc.overlay_region_layer(file_on_server=rval, - region_layer_id='layer2', - plot_id='m31-green') diff --git a/docs/usage/viewing-tables.ipynb b/docs/usage/viewing-tables.ipynb new file mode 100644 index 0000000..316f8a1 --- /dev/null +++ b/docs/usage/viewing-tables.ipynb @@ -0,0 +1,472 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "73088cb6", + "metadata": {}, + "source": [ + "# Displaying Tables and Catalogs\n", + "\n", + "This notebook shows how to display tables in Firefly and how to modify the table display (filtering/sorting). Catalog tables are overlaid on images by default if Firefly recognizes celestial coordinates in the table." + ] + }, + { + "cell_type": "markdown", + "id": "b53c9bca", + "metadata": {}, + "source": [ + "## Setup\n", + "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details. Then, we display a FITS image from a URL so that we have an image for catalog overlays. See [Displaying a FITS Image](./displaying-fits-image.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "95cdcb78", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from firefly_client import FireflyClient\n", + "\n", + "# Following imports are just for example data fetching and processing (not needed always)\n", + "from astropy.utils.data import download_file\n", + "from astropy.table import Table\n", + "import io\n", + "\n", + "# Initialize a FireflyClient instance\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", + "\n", + "# Display a FITS image (from URL of WISE W2 cutout of M51 galaxy)\n", + "cutout_image_url = 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/05379a141-w2-int-1b.fits?center=202.4841667,47.23055556&size=400pix'\n", + "plot_id = 'wise-cutout'\n", + "fc.show_fits_image(file_input=cutout_image_url, plot_id=plot_id, title='WISE Cutout')" + ] + }, + { + "cell_type": "markdown", + "id": "074f5bd7", + "metadata": {}, + "source": [ + "## Display a Table\n", + "\n", + "Use `show_table` method of the FireflyClient object to display a table. Similar to `show_fits_image`, it can take a URL, a local file path, or a file-like object as input via the `file_input` parameter." + ] + }, + { + "cell_type": "markdown", + "id": "5fa64b5a", + "metadata": {}, + "source": [ + "### From a local file\n", + "You can specify path of any local table file. Here we download the Gaia DR3 catalog for 300\" cone around M51 and use its file path:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2555a125", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/Users/jsinghal/.astropy/cache/download/url/55fa7a5603170b3f0b7f826748efe73a/contents'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tbl_url_gaia = \"https://gea.esac.esa.int/tap-server/tap/sync?LANG=ADQL&QUERY=SELECT+*+FROM+gaiadr3.gaia_source+WHERE+CONTAINS(POINT('ICRS',ra,dec),CIRCLE('ICRS',202.4841667,47.23055556,0.083333))=1\"\n", + "tbl_fpath_gaia = download_file(tbl_url_gaia, cache=True, timeout=120)\n", + "tbl_fpath_gaia" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ce7a9c19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tbl_id_gaia = 'gaia-catalog'\n", + "fc.show_table(file_input=tbl_fpath_gaia,\n", + " tbl_id=tbl_id_gaia, \n", + " title='Gaia Catalog')" + ] + }, + { + "cell_type": "markdown", + "id": "98c42f09", + "metadata": {}, + "source": [ + "### From an in-memory file-like object\n", + "\n", + "You can also use a file-like object (e.g. `StringIO` or `BytesIO` stream) instead of a local file path or URL. This is useful when you are working with a Table file in memory (for e.g. as an Astropy Table) and don't want to write it to disk. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "51aa7844", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Table length=5\n", + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
solution_idDESIGNATIONSOURCE_IDrandom_indexref_epochrara_errordecdec_errorparallaxparallax_errorparallax_over_errorpmpmrapmra_errorpmdecpmdec_errorra_dec_corrra_parallax_corrra_pmra_corrra_pmdec_corrdec_parallax_corrdec_pmra_corrdec_pmdec_corrparallax_pmra_corrparallax_pmdec_corrpmra_pmdec_corrastrometric_n_obs_alastrometric_n_obs_acastrometric_n_good_obs_alastrometric_n_bad_obs_alastrometric_gof_alastrometric_chi2_alastrometric_excess_noiseastrometric_excess_noise_sigastrometric_params_solvedastrometric_primary_flagnu_eff_used_in_astrometrypseudocolourpseudocolour_errorra_pseudocolour_corrdec_pseudocolour_corrparallax_pseudocolour_corrpmra_pseudocolour_corrpmdec_pseudocolour_corrastrometric_matched_transitsvisibility_periods_usedastrometric_sigma5d_maxmatched_transitsnew_matched_transitsmatched_transits_removedipd_gof_harmonic_amplitudeipd_gof_harmonic_phaseipd_frac_multi_peakipd_frac_odd_winruwescan_direction_strength_k1scan_direction_strength_k2scan_direction_strength_k3scan_direction_strength_k4scan_direction_mean_k1scan_direction_mean_k2scan_direction_mean_k3scan_direction_mean_k4duplicated_sourcephot_g_n_obsphot_g_mean_fluxphot_g_mean_flux_errorphot_g_mean_flux_over_errorphot_g_mean_magphot_bp_n_obsphot_bp_mean_fluxphot_bp_mean_flux_errorphot_bp_mean_flux_over_errorphot_bp_mean_magphot_rp_n_obsphot_rp_mean_fluxphot_rp_mean_flux_errorphot_rp_mean_flux_over_errorphot_rp_mean_magphot_bp_rp_excess_factorphot_bp_n_contaminated_transitsphot_bp_n_blended_transitsphot_rp_n_contaminated_transitsphot_rp_n_blended_transitsphot_proc_modebp_rpbp_gg_rpradial_velocityradial_velocity_errorrv_method_usedrv_nb_transitsrv_nb_deblended_transitsrv_visibility_periods_usedrv_expected_sig_to_noiserv_renormalised_gofrv_chisq_pvaluerv_time_durationrv_amplitude_robustrv_template_teffrv_template_loggrv_template_fe_hrv_atm_param_originvbroadvbroad_errorvbroad_nb_transitsgrvs_maggrvs_mag_errorgrvs_mag_nb_transitsrvs_spec_sig_to_noisephot_variable_flaglbecl_lonecl_latin_qso_candidatesin_galaxy_candidatesnon_single_starhas_xp_continuoushas_xp_sampledhas_rvshas_epoch_photometryhas_epoch_rvhas_mcmc_gspphothas_mcmc_mscin_andromeda_surveyclassprob_dsc_combmod_quasarclassprob_dsc_combmod_galaxyclassprob_dsc_combmod_starteff_gspphotteff_gspphot_lowerteff_gspphot_upperlogg_gspphotlogg_gspphot_lowerlogg_gspphot_uppermh_gspphotmh_gspphot_lowermh_gspphot_upperdistance_gspphotdistance_gspphot_lowerdistance_gspphot_upperazero_gspphotazero_gspphot_lowerazero_gspphot_upperag_gspphotag_gspphot_lowerag_gspphot_upperebpminrp_gspphotebpminrp_gspphot_lowerebpminrp_gspphot_upperlibname_gspphot
yrdegmasdegmasmasmasmas / yrmas / yrmas / yrmas / yrmas / yrmas1 / um1 / um1 / ummasdegdegdegdegdegelectron / selectron / smagelectron / selectron / smagelectron / selectron / smagmagmagmagkm / skm / sdkm / sKlog(cm.s**-2)dexkm / skm / smagmagdegdegdegdegKKKlog(cm.s**-2)log(cm.s**-2)log(cm.s**-2)dexdexdexpcpcpcmagmagmagmagmagmagmagmagmag
int64objectint64int64float64float64float32float64float32float64float32float32float32float64float32float64float32float32float32float32float32float32float32float32float32float32float32int16int16int16int16float32float32float32float32int16boolfloat32float32float32float32float32float32float32float32int16int16float32int16int16int16float32float32int16int16float32float32float32float32float32float32float32float32float32boolint16float64float32float32float32int16float64float32float32float32int16float64float32float32float32float32int16int16int16int16int16float32float32float32float32float32int16int16int16int16float32float32float32float32float32float32float32float32int16float32float32int16float32float32int16float32objectfloat64float64float64float64boolboolint16boolboolboolboolboolboolboolboolfloat32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32object
1636148068921376768Gaia DR3 155198829213154956815519882921315495688900820522016.0202.47621835713810.748323247.151972776228120.90635014-1.55288169676193121.2272083-1.26537742.435153-2.4330676697812940.994036850.100757503706651461.26996960.37464455-0.189580160.1645490.16169423-0.267974230.140673730.170917-0.16200161-0.377165970.27786323258025622.6322927328.2862.32079221.056778695False--1.3422680.26838365-0.06818995-0.025997920.19759205-0.068285316-0.140469131241.8951416381700.04526214396.571175021.11861560.184248670.373258770.174519910.3911204-74.23466-63.91142337.65227521.430153True29395.722430396933180.9790209597.7736320.73483326181.2285475622398417.1616810.5600719.69297625139.12624891666614.4043859.65860419.3893723.346705702602500.30360413-1.04185681.3454609--------------------------------------------NOT_AVAILABLE104.7920549886698968.59840109705154175.1697559722966650.90282916020732FalseTrue0FalseFalseFalseFalseFalseFalseFalseFalse0.000613337850.998519960.0008667134------------------------------------------
1636148068921376768Gaia DR3 1551988463929387648155198846392938764816361497722016.0202.436023552909940.444228647.1665352226328240.597321-0.210152053103638050.7318001-0.287171424.13046170.88223862278057660.56147894.0351415228795810.75259410.27231008-0.15583270.0490657130.08984825-0.214837010.0243773630.3666216-0.06945767-0.373181760.20351215446043797.8904915884.07934.623820.21786395False--1.09234140.150204180.00750743360.107977310.036282983-0.048866370.03252402752271.1143742703980.09647766111.126526141.27961210.211497130.3069240.118643630.28504333-56.36863-64.5628545.92181816.223856False519200.253152925281461.3851699144.5693819.933418221406.211639888164254.93128225.59946817.468416231191.584613295731732.5869336.56633817.05758312.97256102202300.41083336-2.4650022.8758354--------------------------------------------NOT_AVAILABLE104.8767057535582168.59620898462525175.1210834453790450.898730580732106FalseTrue0FalseFalseFalseFalseFalseFalseFalseFalse5.039107e-110.99999633.7102905e-06------------------------------------------
1636148068921376768Gaia DR3 1551988773167190784155198877316719078410193545502016.0202.473203779877340.385525647.189644985715040.45663941.52114523855006480.61758242.46306471.2724876-0.463938104979195630.48734134-1.18489920389850760.5858320.21224469-0.112964990.0393107720.08014045-0.0568773750.050551110.26251525-0.10948872-0.33805060.2342358445943873.5123873574.45332.74379943.949466295False--1.77056810.12830798-0.046022233-0.03372772-0.0159144140.03260910.01230576252260.87323254651930.04841678680.55671301.1210413----------------False535196.272227645192661.3947663140.7205219.9552251945.707503741238839.50697323.93773717.89915501248.815933364719367.42367618.5219217.00664911.18101905105000.89250183-2.05606842.9485703--------------------------------------------NOT_AVAILABLE104.8391760547594668.56482582796096175.1322755827956750.93224110096643FalseTrue0FalseFalseFalseFalseFalseFalseFalseFalse1.2306821e-101.01.5448892e-08------------------------------------------
1636148068921376768Gaia DR3 1551988807529534208155198880752953420815801030352016.0202.476360314691650.2781962847.201814460503580.31006017-1.29368957897934740.41817343-3.09366774.04422661.66627712921698110.34709073.68500901716005740.401813060.14083208-0.17493783-0.00223527620.1443171-0.0854718460.1000377460.25123927-0.058198392-0.2681360.107605435494049048.797426856.99832.5235798.44341995False--1.76908730.08837092-0.009559230.0111100910.067102350.020670226-0.01831183458300.58091736747400.0751268442.541615611.29494320.144107610.181373430.083373840.31451-95.37658-61.8711242.6610814.672581False596297.468649268002651.9929224149.2625419.50376331988.544402607739143.888522.52399617.85105131891.162016105026944.44282520.05187617.3730036.31900702502500.47804832-1.65271192.1307602--------------------------------------------NOT_AVAILABLE104.8473541831317368.55283585204637175.1237760788416450.94337703119712FalseTrue0FalseFalseFalseFalseFalseFalseFalseFalse4.911622e-100.99905010.0009499131------------------------------------------
1636148068921376768Gaia DR3 155199042243653811215519904224365381122031187922016.0202.504637459728540.5857174447.229141001353160.7143791-1.27758908286871130.8813056-1.4496552.8226283-2.7475935445175160.7255988-0.64649817814629550.87648620.23443323-0.112172480.138224560.115287475-0.0347322080.0933008640.38000315-0.032184172-0.226375710.223657194640458638.1626972947.44687.54979447.09826395False--1.61621610.18061645-0.014007429-0.068259280.024061043-0.0184590560.0789985256291.3104724702100.19378218153.90639202.50748370.218877510.247038630.095638780.3500768-93.71961-64.6571447.03715.638713False554231.605305833965451.702601136.0302919.77549660669.712400811333319.92728833.60780318.2738260515.752988859784821.50603923.98177517.9667915.118472606006000.30702972-1.50167471.8087044--------------------------------------------NOT_AVAILABLE104.829794916154568.52005828530262175.123225696429950.97677659075816FalseTrue0FalseFalseFalseFalseFalseFalseFalseFalse1.2920486e-080.99892770.0010722713------------------------------------------
" + ], + "text/plain": [ + "\n", + " solution_id DESIGNATION ... libname_gspphot\n", + " ... \n", + " int64 object ... object \n", + "------------------- ---------------------------- ... ---------------\n", + "1636148068921376768 Gaia DR3 1551988292131549568 ... \n", + "1636148068921376768 Gaia DR3 1551988463929387648 ... \n", + "1636148068921376768 Gaia DR3 1551988773167190784 ... \n", + "1636148068921376768 Gaia DR3 1551988807529534208 ... \n", + "1636148068921376768 Gaia DR3 1551990422436538112 ... " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gaia_tbl = Table.read(tbl_fpath_gaia)\n", + "gaia_galaxy_tbl = gaia_tbl[gaia_tbl['classprob_dsc_combmod_galaxy'] >= 0.95]\n", + "gaia_galaxy_tbl[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "673d095b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: W50: ?:?:?: W50: Invalid unit string 'log(cm.s**-2)' [astropy.io.votable.tree]\n" + ] + } + ], + "source": [ + "tbl_stream = io.BytesIO()\n", + "gaia_galaxy_tbl.write(tbl_stream, format='votable')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a78ae323", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_table(file_input=tbl_stream,\n", + " tbl_id=tbl_id_gaia+'-galaxy',\n", + " title='Gaia Galaxy Catalog')" + ] + }, + { + "cell_type": "markdown", + "id": "e0dc369e", + "metadata": {}, + "source": [ + "### From a URL\n", + "\n", + "You can specify URL of any table. Here we use an IRSA TAP query to retrieve a 2MASS catalog for 0.125 degree cone at M51." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ad28bca6", + "metadata": {}, + "outputs": [], + "source": [ + "tbl_url_2mass = \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',202.4841667,47.23055556,0.125))=1\"" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d7fb13ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tbl_id_2mass = '2mass-catalog'\n", + "fc.show_table(file_input=tbl_url_2mass,\n", + " tbl_id=tbl_id_2mass,\n", + " title='2MASS Catalog')" + ] + }, + { + "cell_type": "markdown", + "id": "6886f4a1", + "metadata": {}, + "source": [ + ".. note::\n", + " Displaying a catalog table also caused Firefly to overlay the image with markers for each row of the table by default. Also we see the active chart and coverage tab as bonus! If we have more than one images displayed, the catalog overlay will appear to all images that cover the catalog area." + ] + }, + { + "cell_type": "markdown", + "id": "8ef1d9e4", + "metadata": {}, + "source": [ + "## Modify the displayed table\n", + "After displaying a table in Firefly, you can apply filters and sorting by `tbl_id`." + ] + }, + { + "cell_type": "markdown", + "id": "609acf80", + "metadata": {}, + "source": [ + "### Sorting by a column\n", + "You can sort the table by a column in ascending (`ASC`) or descending (`DESC`) order:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1b2cd933", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.sort_table_column(tbl_id=tbl_id_2mass, # note: we use the table id we defined above\n", + " column_name='j_m', sort_direction='ASC')" + ] + }, + { + "cell_type": "markdown", + "id": "8e0cebb3", + "metadata": {}, + "source": [ + "### Filtering the rows\n", + "Filters can be specified as an SQL WHERE clause-like string with column names quoted:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2a4d4245", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.apply_table_filters(tbl_id=tbl_id_2mass,\n", + " filters='\"j_m\"<15 and \"j_cmsig\"<0.05')" + ] + }, + { + "cell_type": "markdown", + "id": "32a84282", + "metadata": {}, + "source": [ + "To remove the filters:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e57a13b5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.apply_table_filters(tbl_id=tbl_id_2mass, filters='')" + ] + }, + { + "cell_type": "markdown", + "id": "de39fc06", + "metadata": {}, + "source": [ + "## Tweaking the table display parameters\n", + "`show_table` method offers several parameters other than the ones we used above, to control how to display the table." + ] + }, + { + "cell_type": "markdown", + "id": "2d78bc35", + "metadata": {}, + "source": [ + "### Load table without displaying it\n", + "By default, `show_table` loads the provided table and displays it in a table viewer. If it is desired to overlay the table on an image, or to make plots from it without showing the table in the viewer, you can set the `visible` parameter to False. \n", + "\n", + ".. tip::\n", + " This is especially useful for loading a MOC file (a single-column table) since you only want to overlay the MOC map on the HiPS image without displaying the table. See [Overlay MOC on HiPS](./displaying-hips.html)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "85a78e22", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tbl_url_spitzer = \"https://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-query?outfmt=1&catalog=slphotdr4&objstr=202.484167d+47.230556d+eq+j2000&spatial=Cone&radius=240\"\n", + "fc.show_table(file_input=tbl_url_spitzer,\n", + " title='Spitzer catalog',\n", + " visible=False)" + ] + }, + { + "cell_type": "markdown", + "id": "f8e11a84", + "metadata": {}, + "source": [ + "### Display table without catalog overlay\n", + "\n", + "If the table contains celestial coordinates recognized by Firefly, the catalog overlay will appear by default on the displayed image. But if you specifically do not want the table overlaid on the image, you can set the `is_catalog` parameter to False" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc.show_table(file_input=tbl_url_spitzer,\n", + " title='Spitzer: no overlay',\n", + " is_catalog=False\n", + " )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/viewing-tables.rst b/docs/usage/viewing-tables.rst deleted file mode 100644 index 6d87145..0000000 --- a/docs/usage/viewing-tables.rst +++ /dev/null @@ -1,71 +0,0 @@ -############################### -Visualizing Tables and Catalogs -############################### - -Tables can be uploaded to the Firefly server with :meth:`FireflyClient.upload_file`, -and displayed in a table viewer component with :meth:`FireflyClient.show_table`. -The default is to overlay symbols for each row of the table (`is_catalog=True`) -if the table contains recognizable celestial coordinates. - -.. code-block:: py - - tval = fc.upload_file('m31-2mass-2412-row.tbl') - fc.show_table(file_on_server=tval, tbl_id='m31-table') - -Modifying Table Display Parameters ----------------------------------- - -If it is desired to overlay the table on an image, or to make plots from it, -without showing the table in the viewer, use :meth:`FireflyClient.fetch_table`: - -.. code-block:: py - - fc.fetch_table(file_on_server=tval, tbl_id='invisible-table') - -Alternatively, you can turn off the `visible` parameter in :meth:`FireflyClient.show_table`: - -.. code-block:: py - - fc.show_table(file_on_server=tval, tbl_id='invisible-table', visible=False) - -If the table does not contain celestial coordinates recognized by Firefly, -the image overlay will not appear. But if you specifically do not want -the table overlaid on the image, `is_catalog=False` can be specified (it is -`True` by default): - -.. code-block:: py - - fc.show_table(file_on_server=tval, tbl_id='2mass-tbl', is_catalog=False) - - -Displaying Table from a URL ---------------------------- - -If you have the URL of a table, you can pass it directly instead of -downloading it and then uploading it to firefly: - -.. code-block:: py - - table_url = "http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',70.0,20.0,0.1))=1" - tbl_id_2mass_psc = '2mass-point-source-catalog' - fc.show_table(url=table_url, tbl_id=tbl_id_2mass_psc) - -Filtering/Sorting a loaded Table --------------------------------- - -After displaying a table in firefly, you can also apply filters on it. -You will need to pass the `tbl_id` of that table and specify `filters` as an -SQL WHERE clause-like string with column names quoted: - -.. code-block:: py - - fc.apply_table_filters(tbl_id=tbl_id_2mass_psc, filters='"j_m">15 and "j_m"<16 and "j_cmsig"<0.06') - -You can sort the table by a column in ascending (`ASC`) or descending (`DESC`) -order: - -.. code-block:: py - - fc.sort_table_column(tbl_id=tbl_id_2mass_psc, column_name='j_m', sort_direction='ASC') - -If a column has sorting, it can be removed by specifying `sort_direction=''`. diff --git a/examples/reference-guide.ipynb b/examples/reference-guide.ipynb index cb20160..c112c09 100644 --- a/examples/reference-guide.ipynb +++ b/examples/reference-guide.ipynb @@ -652,7 +652,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [ { diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index cfc3fcc..f8478ab 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -1006,7 +1006,8 @@ def show_table(self, file_input=None, file_on_server=None, url=None, is_catalog : `bool`, optional If the table file is a catalog (the default is *True*) or not. meta : `dict` - META_INFO for the table search request. + META_INFO for the table search request. The meta data allowed in + this dict are defined in https://github.com/Caltech-IPAC/firefly/blob/master/src/firefly/js/data/MetaConst.js target_search_info : `dict`, optional The information for target search, it may contain the following fields: @@ -1078,12 +1079,12 @@ def show_table(self, file_input=None, file_on_server=None, url=None, title = target_search_info.get('catalog', tbl_id) meta_info = {'title': title, 'tbl_id': tbl_id} + if not is_catalog: + meta_info.update({'CatalogOverlayType': False}) meta and meta_info.update(meta) tbl_req = {'startIdx': 0, 'pageSize': page_size, 'tbl_id': tbl_id} if has_file_input: - tbl_type = 'table' if not is_catalog else 'catalog' - # Handle different params of file input source = None if file_input: @@ -1096,7 +1097,7 @@ def show_table(self, file_input=None, file_on_server=None, url=None, warn('url is deprecated, use file_input parameter instead') source = url - tbl_req.update({'source': source, 'tblType': tbl_type, + tbl_req.update({'source': source, 'id': 'IpacTableFromSource'}) table_index and tbl_req.update({'tbl_index': table_index}) elif target_search_info: @@ -1235,7 +1236,7 @@ def show_xyplot(self, tbl_id, standalone=False, group_id=None, **chart_params): warning and r.update({'warning': warning}) return r - def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_params): + def show_histogram(self, tbl_id, group_id=None, **histogram_params): """ Show a histogram @@ -1286,7 +1287,7 @@ def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_pa warning and r.update({'warning': warning}) return r - def show_chart(self, group_id=PINNED_CHART_VIEWER_ID, **chart_params): + def show_chart(self, group_id=None, **chart_params): """ Show a plot.ly chart From 7f4eb1069d2dfa322269896d7c5625d8a81c0887 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 14:19:01 -0800 Subject: [PATCH 11/17] Replace getting-started rst with notebook. Cleanup and execute other notebook doc pages --- docs/index.rst | 2 +- docs/usage/displaying-3color.ipynb | 21 ++-- docs/usage/displaying-hips.ipynb | 12 +-- docs/usage/displaying-images.ipynb | 79 ++++++++------ docs/usage/getting-started.ipynb | 157 ++++++++++++++++++++++++++++ docs/usage/getting-started.rst | 67 ------------ docs/usage/overlaying-regions.ipynb | 24 ++--- docs/usage/viewing-tables.ipynb | 26 +++-- 8 files changed, 247 insertions(+), 141 deletions(-) create mode 100644 docs/usage/getting-started.ipynb delete mode 100644 docs/usage/getting-started.rst diff --git a/docs/index.rst b/docs/index.rst index 6bc58ea..c70ccfd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,7 +25,7 @@ The ``firefly_client`` package provides a lightweight client class that includes a Python interface to Firefly's Javascript API. This document contains examples of how to start a ``FireflyClient`` instance with details about the server location, and how to use the instance -to upload and display astronomical images, tables and catalogs. +to upload and display astronomical images, tables and charts. .. _firefly_client-using: diff --git a/docs/usage/displaying-3color.ipynb b/docs/usage/displaying-3color.ipynb index 4a018c2..25fce01 100644 --- a/docs/usage/displaying-3color.ipynb +++ b/docs/usage/displaying-3color.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "4bd1902a", "metadata": {}, "outputs": [], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "id": "55e25051", "metadata": {}, "outputs": [], @@ -129,7 +129,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "0440c11e", "metadata": {}, "outputs": [ @@ -139,14 +139,15 @@ "{'success': True}" ] }, - "execution_count": 10, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fc.show_fits_3color(three_color_params=three_color_params,\n", - " plot_id='3color-img')" + " plot_id='3color-img' # same id to replace previous image plot\n", + " )" ] }, { @@ -161,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "b53fb1c2", "metadata": {}, "outputs": [ @@ -219,14 +220,6 @@ "fc.show_fits_3color(three_color_params=threeC,\n", " plot_id='wise_m51')" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22f07cce", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/usage/displaying-hips.ipynb b/docs/usage/displaying-hips.ipynb index 9533bbb..d05d3b2 100644 --- a/docs/usage/displaying-hips.ipynb +++ b/docs/usage/displaying-hips.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a3fc5ef0", "metadata": {}, "outputs": [], @@ -57,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "fa9883ee", "metadata": {}, "outputs": [ @@ -102,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "fc110e4d", "metadata": {}, "outputs": [ @@ -112,7 +112,7 @@ "{'success': True}" ] }, - "execution_count": 6, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "7539af31", "metadata": {}, "outputs": [ @@ -159,7 +159,7 @@ "source": [ "moc_url = 'https://irsa.ipac.caltech.edu/data/MOC/spitzer_seip.moc.fits'\n", "\n", - "fc.show_table(file_input=moc_url)" + "fc.show_table(file_input=moc_url, visible=False)" ] }, { diff --git a/docs/usage/displaying-images.ipynb b/docs/usage/displaying-images.ipynb index 0ad50aa..53e28b1 100644 --- a/docs/usage/displaying-images.ipynb +++ b/docs/usage/displaying-images.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b4fb58c6", "metadata": {}, "outputs": [], @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "id": "d34e928b", "metadata": {}, "outputs": [], @@ -81,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "id": "a1e5eb4d", "metadata": {}, "outputs": [ @@ -91,7 +91,7 @@ "{'success': True}" ] }, - "execution_count": 7, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -114,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "f242a994", "metadata": {}, "outputs": [ @@ -124,7 +124,7 @@ "'/Users/jsinghal/.astropy/cache/download/url/ef686a664b2c17e3d8590b511be8a400/contents'" ] }, - "execution_count": 8, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -137,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "id": "d59743bc", "metadata": {}, "outputs": [ @@ -147,7 +147,7 @@ "{'success': True}" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -170,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "id": "a5e54b24", "metadata": {}, "outputs": [], @@ -190,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "id": "3c8b16be", "metadata": {}, "outputs": [ @@ -200,7 +200,7 @@ "{'success': True}" ] }, - "execution_count": 11, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -225,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "id": "06df4d66", "metadata": {}, "outputs": [ @@ -235,7 +235,7 @@ "{'success': True}" ] }, - "execution_count": 12, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -256,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "id": "6ee07da8", "metadata": {}, "outputs": [ @@ -266,7 +266,7 @@ "{'success': True}" ] }, - "execution_count": 13, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -286,7 +286,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "047d45e6", "metadata": {}, "outputs": [ @@ -297,7 +297,7 @@ " 'rv_string': '91,1.000000,91,1.000000,NaN,2.000000,44,25,600,120,0,NaN,1.000000'}" ] }, - "execution_count": 15, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -322,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "id": "364d0b32", "metadata": {}, "outputs": [ @@ -332,7 +332,7 @@ "{'success': True}" ] }, - "execution_count": 14, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -362,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 12, "id": "ae0feb19", "metadata": {}, "outputs": [ @@ -372,7 +372,7 @@ "{'success': True}" ] }, - "execution_count": 29, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -381,7 +381,7 @@ "fc.dispatch(action_type='ImagePlotCntlr.ColorChange',\n", " payload={\n", " 'plotId': full_image_plot_id,\n", - " 'cbarId': 24 # skyview color map\n", + " 'cbarId': 24 # veridis color map\n", " })" ] }, @@ -399,7 +399,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 13, "id": "de44bb1e", "metadata": {}, "outputs": [], @@ -422,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "58155b58", "metadata": {}, "outputs": [ @@ -432,14 +432,14 @@ "{'success': True}" ] }, - "execution_count": 27, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fc.show_fits_image(file_input=cutout_image_url,\n", - " plot_id=cutout_image_plot_id,\n", + " plot_id=cutout_image_plot_id, # same plot id to replace the previous image\n", " title='WISE Cutout',\n", " # additional keyword arguments\n", " ColorTable=4,\n", @@ -463,7 +463,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 15, "id": "ef8b6b99", "metadata": {}, "outputs": [], @@ -483,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 16, "id": "f8a82d3e", "metadata": {}, "outputs": [ @@ -491,7 +491,7 @@ "data": { "text/html": [ "
Table length=4\n", - "
\n", + "
\n", "\n", "\n", "\n", @@ -511,7 +511,7 @@ " WISE WISE AllSky Atlas -- ... -- image --" ] }, - "execution_count": 41, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -531,7 +531,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "58c4e5f8", "metadata": {}, "outputs": [ @@ -541,7 +541,7 @@ "{'success': True}" ] }, - "execution_count": 46, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -575,10 +575,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "0a635fb7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "fc.align_images(lock_match=True)" ] diff --git a/docs/usage/getting-started.ipynb b/docs/usage/getting-started.ipynb new file mode 100644 index 0000000..7e61108 --- /dev/null +++ b/docs/usage/getting-started.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9535bf07", + "metadata": {}, + "source": [ + "# Getting Started" + ] + }, + { + "cell_type": "markdown", + "id": "da017587", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "- Python 3\n", + "- `firefly_client` (installable with `pip install firefly_client`)\n", + "- URL for a Firefly server" + ] + }, + { + "cell_type": "markdown", + "id": "37ed2705", + "metadata": {}, + "source": [ + "## Connect to Firefly and open the viewer\n", + "Create a `FireflyClient` instance by providing the URL of the Firefly server you want to connect to. Here we use the public [IRSA Viewer](https://irsa.ipac.caltech.edu/irsaviewer):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "836ce9e7", + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "fc = FireflyClient.make_client(url='https://irsa.ipac.caltech.edu/irsaviewer')" + ] + }, + { + "cell_type": "markdown", + "id": "04d46eda", + "metadata": {}, + "source": [ + "`make_client()` usually opens a browser tab with the Firefly viewer automatically. If it didn’t, run `launch_browser()` which returns a success flag and the viewer URL." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "19e45530", + "metadata": {}, + "outputs": [], + "source": [ + "# fc.launch_browser()" + ] + }, + { + "cell_type": "markdown", + "id": "819addb2", + "metadata": {}, + "source": [ + "## Display a FITS image\n", + "Now, use the Firefly client we created above (`fc`) to show your image in Firefly." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "885fd22b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Image file can be a local path, URL, or file-like object - replace with your file\n", + "image_fpath = (\n", + " 'https://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/9a/05379a/141/'\n", + " '05379a141-w2-int-1b.fits?center=202.4841667,47.23055556&size=400pix'\n", + ")\n", + "fc.show_fits_image(image_fpath)" + ] + }, + { + "cell_type": "markdown", + "id": "fac78b11", + "metadata": {}, + "source": [ + "## Display a catalog table\n", + "Similarly, you can show your table in Firefly.\n", + "\n", + "The sources are also overlaid automatically on the image if the table is a catalog containing celestial coordinates, and a default chart is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bf10066", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Table file can be a local path, URL, or file-like object - replace with your file\n", + "table_fpath = (\n", + " \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=\"\n", + " \"SELECT+*+FROM+fp_psc+\"\n", + " \"WHERE+CONTAINS(POINT('J2000',ra,dec),\"\n", + " \"CIRCLE('J2000',202.4841667,47.23055556,0.125))=1\"\n", + ")\n", + "fc.show_table(table_fpath)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffpy", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/usage/getting-started.rst b/docs/usage/getting-started.rst deleted file mode 100644 index 41ee913..0000000 --- a/docs/usage/getting-started.rst +++ /dev/null @@ -1,67 +0,0 @@ -############### -Getting Started -############### - -Prerequisites -============= - -* Python 3 - -* firefly_client (installable with `pip install firefly_client`) - -* URL for a Firefly server - -.. _firefly_client-getting-started: - -Getting Started -=============== - -First, download some data by using wget or curl: - -.. code-block:: shell - :name: gs-download - - wget http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits - wget http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl - - curl -o 2mass-m31-green.fits http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits - curl -o m31-2mass-2412-row.tbl http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl - -Second, start a Python session and connect an instance of :class:`firefly_client.FireflyClient` -to a Firefly server. In this example, we will use a public server. For ease of demonstration, -please start the Python session in the directory from which you downloaded the sample files. - -.. code-block:: py - :name: gs-start - - from firefly_client import FireflyClient - fc = FireflyClient.make_client('https://irsa.ipac.caltech.edu/irsaviewer') - -Third, open a new browser tab using the :meth:`FireflyClient.launch_browser` method. If -a browser cannot be opened, a URL will be displayed for your web browser. - -.. code-block:: py - :name: gs-launch - - fc.launch_browser() - -Fourth, display an image in a FITS file by uploading the -file to the server with :meth:`FireflyClient.upload_file` -and then showing the image. - -.. code-block:: py - :name: gs-image - - fval = fc.upload_file('2mass-m31-green.fits') - fc.show_fits(fval) - -Fifth, display a table by uploading a catalog table (here, in IPAC format) and -then showing the table. The sources are also overlaid automatically on the -image since the catalog contains celestial coordinates, and a default -chart is displayed. - -.. code-block:: py - :name: gs-table - - tval = fc.upload_file('m31-2mass-2412-row.tbl') - fc.show_table(tval) diff --git a/docs/usage/overlaying-regions.ipynb b/docs/usage/overlaying-regions.ipynb index 003c59e..bd7a3d3 100644 --- a/docs/usage/overlaying-regions.ipynb +++ b/docs/usage/overlaying-regions.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "14b195f0", "metadata": {}, "outputs": [ @@ -65,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "8ff1f3df", "metadata": {}, "outputs": [ @@ -75,7 +75,7 @@ "{'success': True}" ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -103,7 +103,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "13f8a78f", "metadata": {}, "outputs": [], @@ -119,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "4d7fa9e6", "metadata": {}, "outputs": [ @@ -129,7 +129,7 @@ "{'success': True}" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -153,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "f6794996", "metadata": {}, "outputs": [ @@ -163,7 +163,7 @@ "{'success': True}" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -187,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "b3b1cc99", "metadata": {}, "outputs": [ @@ -197,7 +197,7 @@ "{'success': True}" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -218,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "0359b1c7", "metadata": {}, "outputs": [ @@ -228,7 +228,7 @@ "{'success': True}" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } diff --git a/docs/usage/viewing-tables.ipynb b/docs/usage/viewing-tables.ipynb index 316f8a1..335c5d6 100644 --- a/docs/usage/viewing-tables.ipynb +++ b/docs/usage/viewing-tables.ipynb @@ -139,7 +139,7 @@ "data": { "text/html": [ "
Table length=5\n", - "
missionIdprojectsubProjectsurveyKeyacronymwavebandIdimageIdtitlewavelengthwavelengthDescwaveTypeprojectTypeKeyprojectTypeDescminRangeDegmaxRangeDeghelpUrltooltipapiTypefilterdataTypedefaultRgbColor
str8str115str8str33str25str11str2str42float64str15str7str13str13float64float64str74str46str7str80str5str5
WISEWISE AllSky Atlas--1bWISE AllSky W11--W1 (3.4 microns)3.43.4 micronsmid-IRall-skyall-sky0.010.2https://irsa.ipac.caltech.edu/docs/irsaviewer_datasets.html#WISEAll-SkyWISE AllSky SIngle Exposure W1 3.4 micronsWISE--image--
\n", + "
\n", "\n", "\n", "\n", @@ -238,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "d7fb13ba", "metadata": {}, "outputs": [ @@ -248,7 +248,7 @@ "{'success': True}" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -345,6 +345,9 @@ "id": "32a84282", "metadata": {}, "source": [ + ".. note::\n", + " You can see these filters in the table display. The catalog markers on the image also reduce in number as well as the active chart updates too.\n", + "\n", "To remove the filters:" ] }, @@ -392,7 +395,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "id": "85a78e22", "metadata": {}, "outputs": [ @@ -402,7 +405,7 @@ "{'success': True}" ] }, - "execution_count": 10, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -414,6 +417,15 @@ " visible=False)" ] }, + { + "cell_type": "markdown", + "id": "1626fa0c", + "metadata": {}, + "source": [ + ".. note::\n", + " It may take some time to load this catalog table, and since there's no feedback from the table display, look for the spinner on the top left of the image display." + ] + }, { "cell_type": "markdown", "id": "f8e11a84", @@ -426,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -435,7 +447,7 @@ "{'success': True}" ] }, - "execution_count": 11, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } From fcd1f01a61fb0737edebf1b8e516c225328a5512 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 15:23:51 -0800 Subject: [PATCH 12/17] Update publish docs workflow to include PR previews --- .github/workflows/publish-docs.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 1298df9..ba8be13 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -4,6 +4,10 @@ on: branches: - master + pull_request: + branches: + - master + workflow_dispatch: # Manual trigger jobs: @@ -33,9 +37,25 @@ jobs: make clean make html - - name: Deploy docs + # Deploy stable docs (root of gh-pages) only from master pushes + - name: Deploy docs (stable) + if: github.event_name == 'push' && github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/_build/html + # Deploy PR preview docs to gh-pages under pr// + - name: Deploy docs (PR preview) + if: github.event_name == 'pull_request' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_build/html + destination_dir: pr/${{ github.event.pull_request.number }} + keep_files: true + + - name: Print PR preview URL + if: github.event_name == 'pull_request' + run: | + echo "Deployed PR docs preview at: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr/${{ github.event.pull_request.number }}/" From 994c7458cb7123c14e20903c2788a65610f05cc0 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 16:00:34 -0800 Subject: [PATCH 13/17] Add pandoc installation to workflow and documentation --- .github/workflows/publish-docs.yml | 5 +++++ docs/development/guide.rst | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index ba8be13..6650db2 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -17,6 +17,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install system dependencies (pandoc) + run: | + sudo apt-get update + sudo apt-get install -y pandoc + - uses: actions/setup-python@v5 with: python-version: '3.10' diff --git a/docs/development/guide.rst b/docs/development/guide.rst index 4774dc8..adbca7c 100644 --- a/docs/development/guide.rst +++ b/docs/development/guide.rst @@ -49,6 +49,10 @@ website. Each time you make a change in documentation source, build it using above command and reload the above html file in browser. +.. note:: + The Sphinx docs include rendered Jupyter notebooks (via ``nbsphinx``), which can require **pandoc**. + If you see an error like ``PandocMissing``, install pandoc first (e.g., ``brew install pandoc`` on macOS). + Development Tests/Examples -------------------------- From f2cb81d3d899faf2ba368817ad594edb9e0cfa77 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 18:38:36 -0800 Subject: [PATCH 14/17] Add what to expect section in landing page Fix nbsphinx syntax highlighting for code cell --- docs/_static/firefly-in-python-flow.png | Bin 0 -> 206654 bytes docs/conf.py | 3 +++ docs/index.rst | 34 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 docs/_static/firefly-in-python-flow.png diff --git a/docs/_static/firefly-in-python-flow.png b/docs/_static/firefly-in-python-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..2a419416542b6cf0c56842f774592570bd9cb1e5 GIT binary patch literal 206654 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
    Uq!(4ULt(ym9N;rt;?DHjk_1oITTxAKne7 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 + +.. image:: _static/firefly-in-python-flow.png + :alt: Firefly in Python Flow Diagram + :align: center + +.. raw:: html + + + +It's important to note that Firefly visualization in Python notebooks is +**NOT a per-cell chart or widget**, unlike common plotting libraries +such as Plotly or Bokeh. + +Under the hood, Firefly is a dedicated web application running at a specific +URL, rather than a plot produced as a notebook cell output. +Your notebook's Python session connects to a Firefly server, either a publicly +hosted instance (such as the `IRSA Viewer`_) or a local server running via Docker_. +Using a server enables advanced, compute-intensive interactivity that is +difficult to support directly within a notebook's UI. + +Multiple notebook cells can then send commands to the same Firefly instance, +which manages linked views of images, charts, and tables, similar to an +**interactive dashboard**. + +.. _IRSA Viewer: https://irsa.ipac.caltech.edu/irsaviewer/ +.. _Docker: https://hub.docker.com/r/ipac/firefly + + .. _firefly_client-using: Using firefly_client From 3e41eee73e42806977699e866a4f8c0e2a605afe Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 18:48:04 -0800 Subject: [PATCH 15/17] Add ipython to docs optional dependencies for notebook syntax highlighting --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6c49210..57ddb0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,4 +50,5 @@ docs = [ "pydata-sphinx-theme", "myst-parser", "nbsphinx", + "ipython" # for syntax highlighting in notebooks html output ] From 941380d3cb4f6eec1a871ab124b899c048bbab44 Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Wed, 14 Jan 2026 18:54:11 -0800 Subject: [PATCH 16/17] Remove note about documentation being a work in progress --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 84eb314..4b47e5a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,5 @@ Python API for [Firefly](http://github.com/Caltech-IPAC/firefly), IPAC's Advance You can find the documentation at https://caltech-ipac.github.io/firefly_client. -NOTE: Many parts of this documentation are a work in progress because the firefly_client API and its use cases evolved since it was written. Please report any issues or suggestions on the [GitHub issues](https://github.com/Caltech-IPAC/firefly_client/issues). From eb7b1ff4e6967cc72aee0b6d2f0b44c583a6b5ca Mon Sep 17 00:00:00 2001 From: Jaladh Singhal Date: Mon, 26 Jan 2026 17:16:12 -0800 Subject: [PATCH 17/17] Add astropy installation comment and env var setting instruction --- docs/usage/charting.ipynb | 4 ++-- docs/usage/displaying-images.ipynb | 11 +++++++++++ docs/usage/initializing-vanilla.ipynb | 13 +++++++++++++ docs/usage/viewing-tables.ipynb | 11 +++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/usage/charting.ipynb b/docs/usage/charting.ipynb index cdaecbb..f4e4ede 100644 --- a/docs/usage/charting.ipynb +++ b/docs/usage/charting.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "d26d722f", "metadata": {}, "outputs": [ @@ -40,7 +40,7 @@ "from firefly_client import FireflyClient\n", "\n", "# Initialize a FireflyClient instance\n", - "fc = FireflyClient.make_client(url=\"http://localhost:8080/firefly\")\n", + "fc = FireflyClient.make_client(url=\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", "\n", "# Display a catalog table (from URL of 2MASS TAP query for M51)\n", "table_url = \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),CIRCLE('J2000',202.4841667,47.23055556,0.125))=1\"\n", diff --git a/docs/usage/displaying-images.ipynb b/docs/usage/displaying-images.ipynb index 53e28b1..67b15bb 100644 --- a/docs/usage/displaying-images.ipynb +++ b/docs/usage/displaying-images.ipynb @@ -29,6 +29,17 @@ "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "42fb0b65", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the next line to install dependencies if needed.\n", + "# !pip install firefly-client astropy" + ] + }, { "cell_type": "code", "execution_count": 1, diff --git a/docs/usage/initializing-vanilla.ipynb b/docs/usage/initializing-vanilla.ipynb index 657702f..0b1597a 100644 --- a/docs/usage/initializing-vanilla.ipynb +++ b/docs/usage/initializing-vanilla.ipynb @@ -18,6 +18,8 @@ " - IRSA Viewer at https://irsa.ipac.caltech.edu/irsaviewer\n", " - Rubin Science Platform Portal at https://data.lsst.cloud/portal/app/ (requires authentication; the `ACCESS_TOKEN` environment variable must be set)\n", "\n", + "For example, in a bash/zsh shell, enter `export FIREFLY_URL=\"https://irsa.ipac.caltech.edu/irsaviewer\"` before launching the notebook via `jupyter lab` or `jupyter notebook` command.\n", + "\n", "If `FIREFLY_URL` is not defined, the default server URL is `http://localhost:8080/firefly`, which is often used for a Firefly server running locally.\n", "\n", ".. note::\n", @@ -33,6 +35,17 @@ "The only required import here is `FireflyClient`." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b61ec8f", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the next line if you have not installed the firefly-client package already.\n", + "# !pip install firefly-client" + ] + }, { "cell_type": "code", "execution_count": 1, diff --git a/docs/usage/viewing-tables.ipynb b/docs/usage/viewing-tables.ipynb index 335c5d6..dd192c8 100644 --- a/docs/usage/viewing-tables.ipynb +++ b/docs/usage/viewing-tables.ipynb @@ -19,6 +19,17 @@ "First, we create a `FireflyClient` instance and open a Firefly viewer. See [Initializing a FireflyClient instance](./initializing-vanilla.html) for more details. Then, we display a FITS image from a URL so that we have an image for catalog overlays. See [Displaying a FITS Image](./displaying-fits-image.html) for more details." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "cad23c03", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the next line to install dependencies if needed.\n", + "# !pip install firefly-client astropy" + ] + }, { "cell_type": "code", "execution_count": 1,

solution_idDESIGNATIONSOURCE_IDrandom_indexref_epochrara_errordecdec_errorparallaxparallax_errorparallax_over_errorpmpmrapmra_errorpmdecpmdec_errorra_dec_corrra_parallax_corrra_pmra_corrra_pmdec_corrdec_parallax_corrdec_pmra_corrdec_pmdec_corrparallax_pmra_corrparallax_pmdec_corrpmra_pmdec_corrastrometric_n_obs_alastrometric_n_obs_acastrometric_n_good_obs_alastrometric_n_bad_obs_alastrometric_gof_alastrometric_chi2_alastrometric_excess_noiseastrometric_excess_noise_sigastrometric_params_solvedastrometric_primary_flagnu_eff_used_in_astrometrypseudocolourpseudocolour_errorra_pseudocolour_corrdec_pseudocolour_corrparallax_pseudocolour_corrpmra_pseudocolour_corrpmdec_pseudocolour_corrastrometric_matched_transitsvisibility_periods_usedastrometric_sigma5d_maxmatched_transitsnew_matched_transitsmatched_transits_removedipd_gof_harmonic_amplitudeipd_gof_harmonic_phaseipd_frac_multi_peakipd_frac_odd_winruwescan_direction_strength_k1scan_direction_strength_k2scan_direction_strength_k3scan_direction_strength_k4scan_direction_mean_k1scan_direction_mean_k2scan_direction_mean_k3scan_direction_mean_k4duplicated_sourcephot_g_n_obsphot_g_mean_fluxphot_g_mean_flux_errorphot_g_mean_flux_over_errorphot_g_mean_magphot_bp_n_obsphot_bp_mean_fluxphot_bp_mean_flux_errorphot_bp_mean_flux_over_errorphot_bp_mean_magphot_rp_n_obsphot_rp_mean_fluxphot_rp_mean_flux_errorphot_rp_mean_flux_over_errorphot_rp_mean_magphot_bp_rp_excess_factorphot_bp_n_contaminated_transitsphot_bp_n_blended_transitsphot_rp_n_contaminated_transitsphot_rp_n_blended_transitsphot_proc_modebp_rpbp_gg_rpradial_velocityradial_velocity_errorrv_method_usedrv_nb_transitsrv_nb_deblended_transitsrv_visibility_periods_usedrv_expected_sig_to_noiserv_renormalised_gofrv_chisq_pvaluerv_time_durationrv_amplitude_robustrv_template_teffrv_template_loggrv_template_fe_hrv_atm_param_originvbroadvbroad_errorvbroad_nb_transitsgrvs_maggrvs_mag_errorgrvs_mag_nb_transitsrvs_spec_sig_to_noisephot_variable_flaglbecl_lonecl_latin_qso_candidatesin_galaxy_candidatesnon_single_starhas_xp_continuoushas_xp_sampledhas_rvshas_epoch_photometryhas_epoch_rvhas_mcmc_gspphothas_mcmc_mscin_andromeda_surveyclassprob_dsc_combmod_quasarclassprob_dsc_combmod_galaxyclassprob_dsc_combmod_starteff_gspphotteff_gspphot_lowerteff_gspphot_upperlogg_gspphotlogg_gspphot_lowerlogg_gspphot_uppermh_gspphotmh_gspphot_lowermh_gspphot_upperdistance_gspphotdistance_gspphot_lowerdistance_gspphot_upperazero_gspphotazero_gspphot_lowerazero_gspphot_upperag_gspphotag_gspphot_lowerag_gspphot_upperebpminrp_gspphotebpminrp_gspphot_lowerebpminrp_gspphot_upperlibname_gspphot
yrdegmasdegmasmasmasmas / yrmas / yrmas / yrmas / yrmas / yrmas1 / um1 / um1 / ummasdegdegdegdegdegelectron / selectron / smagelectron / selectron / smagelectron / selectron / smagmagmagmagkm / skm / sdkm / sKlog(cm.s**-2)dexkm / skm / smagmagdegdegdegdegKKKlog(cm.s**-2)log(cm.s**-2)log(cm.s**-2)dexdexdexpcpcpcmagmagmagmagmagmagmagmagmag
int64objectint64int64float64float64float32float64float32float64float32float32float32float64float32float64float32float32float32float32float32float32float32float32float32float32float32int16int16int16int16float32float32float32float32int16boolfloat32float32float32float32float32float32float32float32int16int16float32int16int16int16float32float32int16int16float32float32float32float32float32float32float32float32float32boolint16float64float32float32float32int16float64float32float32float32int16float64float32float32float32float32int16int16int16int16int16float32float32float32float32float32int16int16int16int16float32float32float32float32float32float32float32float32int16float32float32int16float32float32int16float32objectfloat64float64float64float64boolboolint16boolboolboolboolboolboolboolboolfloat32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32float32object