diff --git a/example_notebooks/ginga/bqplot_widget.ipynb b/example_notebooks/ginga/bqplot_widget.ipynb new file mode 100644 index 0000000..d305e0f --- /dev/null +++ b/example_notebooks/ginga/bqplot_widget.ipynb @@ -0,0 +1,978 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Widget Example Using bqplot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See https://astrowidgets.readthedocs.io for additional details about the widget, including installation notes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from astrowidgets.bqplot import ImageWidget" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# from ginga.misc.log import get_logger\n", + "\n", + "# logger = get_logger('my viewer', log_stderr=True,\n", + "# log_file=None, level=30)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "w = ImageWidget()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this example, we use an image from Astropy data repository and load it as `CCDData`. Feel free to modify `filename` to point to your desired image.\n", + "\n", + "Alternately, for local FITS file, you could load it like this instead:\n", + "```python\n", + "w.load_fits(filename, numhdu=numhdu)\n", + "``` \n", + "Or if you wish to load a data array natively (without WCS):\n", + "```python\n", + "from astropy.io import fits\n", + "# NOTE: memmap=False is needed for remote data on Windows.\n", + "with fits.open(filename, memmap=False) as pf:\n", + " arr = pf[numhdu].data.copy()\n", + "w.load_array(arr)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "filename = 'http://data.astropy.org/photometry/spitzer_example_image.fits'\n", + "numhdu = 0\n", + "\n", + "# Loads NDData\n", + "# NOTE: Some file also requires unit to be explicitly set in CCDData.\n", + "from astropy.nddata import CCDData\n", + "ccd = CCDData.read(filename, hdu=numhdu, format='fits')\n", + "w.load_nddata(ccd)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ginga key bindings documented at http://ginga.readthedocs.io/en/latest/quickref.html . Note that not all documented bindings would work here. Please use an alternate binding, if available, if the chosen one is not working.\n", + "\n", + "Here are the ones that worked during testing with Firefox 52.8.0 on RHEL7 64-bit:\n", + "\n", + "Key | Action | Notes\n", + "--- | --- | ---\n", + "`+` | Zoom in |\n", + "`-` | Zoom out |\n", + "Number (0-9) | Zoom in to specified level | 0 = 10\n", + "Shift + number | Zoom out to specified level | Numpad does not work\n", + "` (backtick) | Reset zoom |\n", + "Space > `q` > arrow | Pan |\n", + "ESC | Exit mode (pan, etc) |\n", + "`c` | Center image\n", + "Space > `d` > up/down arrow | Cycle through color distributions\n", + "Space > `d` > Shift + `d` | Go back to linear color distribution\n", + "Space > `s` > Shift + `s` | Set cut level to min/max\n", + "Space > `s` > Shift + `a` | Set cut level to 0/255 (for 8bpp RGB images)\n", + "Space > `s` > up/down arrow | Cycle through cuts algorithms\n", + "Space > `l` | Toggle no/soft/normal lock |\n", + "\n", + "*NOTE: This list is not exhaustive.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A viewer will be shown after running the next cell.\n", + "In Jupyter Lab, you can split it out into a separate view by right-clicking on the viewer and then select\n", + "\"Create New View for Output\". Then, you can drag the new\n", + "\"Output View\" tab, say, to the right side of the workspace. Both viewers are connected to the same events." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "446131d7e9a24639b5a1be203c32e159", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "ImageWidget(children=(_AstroImage(children=(Figure(axes=[Axis(scale=LinearScale(allow_padding=False, max=1025.ā€¦" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "w" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This next cell captures print outputs. You can pop it out like the viewer above. It is very convenient for debugging purpose." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ImageWidget' object has no attribute 'print_out'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Capture print outputs from the widget\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mdisplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprint_out\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'ImageWidget' object has no attribute 'print_out'" + ] + } + ], + "source": [ + "# Capture print outputs from the widget\n", + "display(w.print_out)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell changes the visibility or position of the cursor info bar.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "top\n" + ] + } + ], + "source": [ + "w.cursor = 'top' # 'top', 'bottom', None\n", + "print(w.cursor)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ FIXED! šŸ˜ƒ -- cursor does not move" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The rest of the calls demonstrate how the widget API works. Comment/uncomment as needed. Feel free to experiment." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Programmatically center to (X, Y) on viewer\n", + "w.center_on((1, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# Programmatically offset w.r.t. current center\n", + "w.offset_by(10, 10)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.coordinates import SkyCoord\n", + "\n", + "# Change the values here if you are not using given\n", + "# example image.\n", + "ra_str = '01h13m23.193s'\n", + "dec_str = '+00d12m32.19s'\n", + "frame = 'galactic'\n", + "\n", + "# Programmatically center to SkyCoord on viewer\n", + "w.center_on(SkyCoord(ra_str, dec_str, frame=frame))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ šŸ˜• -- issue is that the test image is in Galactic coordinates so either the frame needs to be galactic here or a different center is needed -- image moves completely out of view" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from astropy import units as u\n", + "\n", + "# Change the values if needed.\n", + "deg_offset = 0.1 * u.deg\n", + "\n", + "# Programmatically offset (in degrees) w.r.t.\n", + "# SkyCoord center\n", + "w.offset_by(deg_offset, deg_offset)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.4878048780487805\n" + ] + } + ], + "source": [ + "# Show zoom level\n", + "print(w.zoom_level)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ šŸ˜ƒ fixed! -- zoom_level should never be zero!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "w.zoom_level = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ -- setting zoom_level to 1 took image out of view -- šŸ˜• now it works, but not sure what I did to fix it\n", + "\n", + "with X: -349501.59 Y: -186964.83\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Programmatically zoom image on viewer\n", + "w.zoom(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# Capture what viewer is showing and save RGB image.\n", + "# Need https://github.com/ejeschke/ginga/pull/688 to work.\n", + "w.save('test.png', overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- saving *downloads* the image but does not put it in the directory notebook is in" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ImageWidget' object has no attribute 'stretch_options'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Get all available image stretch options\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstretch_options\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'ImageWidget' object has no attribute 'stretch_options'" + ] + } + ], + "source": [ + "# Get all available image stretch options\n", + "print(w.stretch_options)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- There should be stretch_options" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Get image stretch algorithm in use\n", + "print(w.stretch)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "histeq\n" + ] + } + ], + "source": [ + "# Change the stretch\n", + "w.stretch = 'histeq'\n", + "print(w.stretch)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ -- changing stretch does not change display -- šŸ˜• the change looks terrible but it does change" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ImageWidget' object has no attribute 'autocut_options'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Get all available image cuts options\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mautocut_options\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'ImageWidget' object has no attribute 'autocut_options'" + ] + } + ], + "source": [ + "# Get all available image cuts options\n", + "print(w.autocut_options)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- There should be autocut options I guess" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Get image cut levels in use\n", + "print(w.cuts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- this isn't *wrong* but maybe a __str__ would be nice" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(10, 15)\n" + ] + } + ], + "source": [ + "# Change the cuts by providing explicit low/high values\n", + "w.cuts = (10, 15)\n", + "print(w.cuts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ -- yeah, it is a failure...works now, though šŸ¤·ā€ā™‚ļø HA HA HA NO -- fails again, WTF? -- AAAH, the issue was with whether the stretch had been set to something." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "w.stretch = 'log'" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Cut levels must be given as (low, high).zscale is not a valid value. cuts must be one of None, an astropy interval, or list/tuple of length 2.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Change the cuts with an autocut algorithm\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcuts\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'zscale'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcuts\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traitlets/traitlets.py\u001b[0m in \u001b[0;36m__set__\u001b[0;34m(self, obj, value)\u001b[0m\n\u001b[1;32m 602\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTraitError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'The \"%s\" trait is read-only.'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 603\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 604\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 605\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 606\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traitlets/traitlets.py\u001b[0m in \u001b[0;36mset\u001b[0;34m(self, obj, value)\u001b[0m\n\u001b[1;32m 576\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 577\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 578\u001b[0;31m \u001b[0mnew_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 579\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 580\u001b[0m \u001b[0mold_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_trait_values\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traitlets/traitlets.py\u001b[0m in \u001b[0;36m_validate\u001b[0;34m(self, obj, value)\u001b[0m\n\u001b[1;32m 610\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalidate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 611\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cross_validation_lock\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 612\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cross_validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 613\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 614\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traitlets/traitlets.py\u001b[0m in \u001b[0;36m_cross_validate\u001b[0;34m(self, obj, value)\u001b[0m\n\u001b[1;32m 616\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_trait_validators\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 617\u001b[0m \u001b[0mproposal\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mBunch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m'trait'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'value'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'owner'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 618\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_trait_validators\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mproposal\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 619\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'_%s_validate'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 620\u001b[0m \u001b[0mmeth_name\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'_%s_validate'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traitlets/traitlets.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 973\u001b[0m \u001b[0;34m\"\"\"Pass `*args` and `**kwargs` to the handler's function if it exists.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 974\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'func'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 975\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 976\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 977\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_init_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/development/astronomy/astrowidgets/astrowidgets/bqplot.py\u001b[0m in \u001b[0;36m_validate_cuts\u001b[0;34m(self, proposal)\u001b[0m\n\u001b[1;32m 578\u001b[0m \u001b[0mlength\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mproposed_cuts\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 579\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlength\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 580\u001b[0;31m raise ValueError('Cut levels must be given as (low, high).'\n\u001b[0m\u001b[1;32m 581\u001b[0m + bad_value_error)\n\u001b[1;32m 582\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: Cut levels must be given as (low, high).zscale is not a valid value. cuts must be one of None, an astropy interval, or list/tuple of length 2." + ] + } + ], + "source": [ + "# Change the cuts with an autocut algorithm\n", + "w.cuts = 'zscale'\n", + "print(w.cuts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- yeah, it is a failure..." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "# This enables click to center.\n", + "w.click_center = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, click on the image to center it." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ -- clicking does nothing -- FIXED šŸ˜ƒ\n", + "\n", + "Actually, I knew this would be the case...." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "# Turn it back off so marking (next cell) can be done.\n", + "w.click_center = False" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "# This enables marking mode.\n", + "w.start_marking()\n", + "print(w.is_marking)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ šŸ˜ƒ Fixed -- yeah, it is a failure..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, click on the image to mark a point of interest." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "# When done, set back to False.\n", + "w.stop_marking()\n", + "print(w.is_marking)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " X Y Coordinates \n", + " 119.00 242.00 01h13m23.32s +00d12m37.8s\n", + " 691.00 209.00 01h12m37.56s +00d11m58.2s\n", + " 710.00 305.00 01h12m36.04s +00d13m53.4s\n", + " 68.00 291.00 01h13m27.4s +00d13m36.6s\n" + ] + } + ], + "source": [ + "# Get table of markers\n", + "markers_table = w.get_all_markers()\n", + "\n", + "# Default display might be hard to read, so we do this\n", + "print(f'{\"X\":^8s} {\"Y\":^8s} {\"Coordinates\":^28s}')\n", + "for row in markers_table:\n", + " c = row['coord'].to_string('hmsdms')\n", + " print(f'{row[\"x\"]:8.2f} {row[\"y\"]:8.2f} {c}')" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [], + "source": [ + "# Erase markers from display\n", + "w.remove_all_markers()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following works even when we have set `w.is_marking=False`. This is because `w.is_marking` only controls the interactive marking and does not affect marking programmatically." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/mattcraig/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traittypes/traittypes.py:97: UserWarning: Given trait value dtype \"int32\" does not match required type \"float64\". A coerced copy has been created.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "# Programmatically re-mark from table using X, Y.\n", + "# To be fancy, first 2 points marked as bigger\n", + "# and thicker red circles.\n", + "w.marker = {'type': 'circle', 'color': 'red', 'radius': 50,\n", + " 'linewidth': 2}\n", + "# šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡ HAD TO ADD DIFFERENT MARKER NAMES FOR THIS TO WORK šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡\n", + "w.add_markers(markers_table[:2], marker_name='first')\n", + "# You can also change the type of marker to cross or plus\n", + "w.marker = {'type': 'cross', 'color': 'cyan', 'radius': 20}\n", + "w.add_markers(markers_table[2:], marker_name='second')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## šŸ‘† FAILURE -- first two are not showing up as big red circles" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'NoneType' object is not subscriptable", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;31m# Programmatically re-mark from table using SkyCoord\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_markers\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmarkers_table\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0muse_skycoord\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/development/astronomy/astrowidgets/astrowidgets/bqplot.py\u001b[0m in \u001b[0;36madd_markers\u001b[0;34m(self, table, x_colname, y_colname, skycoord_colname, use_skycoord, marker_name)\u001b[0m\n\u001b[1;32m 680\u001b[0m 'world coordinates for markers.')\n\u001b[1;32m 681\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 682\u001b[0;31m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_wcs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mworld_to_pixel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtable\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mskycoord_colname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 683\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 684\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtable\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx_colname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: 'NoneType' object is not subscriptable" + ] + } + ], + "source": [ + "# Erase them again\n", + "w.remove_all_markers()\n", + "\n", + "# Programmatically re-mark from table using SkyCoord\n", + "w.add_markers(markers_table, use_skycoord=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Start marking again\n", + "w.start_marking()\n", + "print(w.is_marking)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Stop marking AND clear markers.\n", + "# Note that this deletes ALL of the markers\n", + "w.stop_marking(clear_markers=True)\n", + "print(w.is_marking)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next cell randomly generates some \"stars\" to mark. In the real world, you would probably detect real stars using `photutils` package." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x y \n", + "----- -----\n", + "141.0 451.0\n", + "941.0 239.0\n", + "195.0 321.0\n", + "327.0 287.0\n", + "108.0 101.0\n", + "919.0 352.0\n", + "331.0 194.0\n", + "533.0 464.0\n", + "322.0 298.0\n", + "853.0 398.0\n", + " ... ...\n", + "697.0 47.0\n", + "437.0 327.0\n", + "138.0 393.0\n", + "420.0 335.0\n", + "744.0 445.0\n", + "964.0 480.0\n", + "769.0 340.0\n", + "149.0 302.0\n", + "979.0 399.0\n", + "478.0 423.0\n", + "789.0 153.0\n", + "Length = 482 rows\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from astropy.table import Table\n", + "\n", + "# Maximum umber of \"stars\" to generate randomly.\n", + "max_stars = 1000\n", + "\n", + "# Number of pixels from edge to avoid.\n", + "dpix = 20\n", + "\n", + "# Image from the viewer.\n", + "img = w._data #w.viewer.get_image()\n", + "\n", + "# Random \"stars\" generated.\n", + "bad_locs = np.random.randint(\n", + " dpix, high=img.shape[1] - dpix, size=[max_stars, 2])\n", + "\n", + "# Only want those not near the edges.\n", + "mask = ((dpix < bad_locs[:, 0]) &\n", + " (bad_locs[:, 0] < img.shape[0] - dpix) &\n", + " (dpix < bad_locs[:, 1]) &\n", + " (bad_locs[:, 1] < img.shape[1] - dpix))\n", + "locs = bad_locs[mask]\n", + "\n", + "# Put them in table\n", + "t = Table([locs[:, 1], locs[:, 0]], names=('x', 'y'), dtype=('float', 'float'))\n", + "print(t)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† FAILURE ABOVE -- to be fair, this is relying on an implementation detail of ginga\n", + "\n", + "Fixed temporarily by changing `img`" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/mattcraig/miniconda3/envs/awid-jlab3/lib/python3.9/site-packages/traittypes/traittypes.py:97: UserWarning: Given trait value dtype \"int32\" does not match required type \"float64\". A coerced copy has been created.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "# Mark those \"stars\" based on given table with X and Y.\n", + "w.add_markers(t)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# šŸ‘† ~FAILURE ABOVE~ šŸ˜ƒ fixed! -- Really?! Does anything in here work?! This _should_ have been caught by the tests.... šŸ˜ƒ it is now!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following illustrates how to control number of markers displayed using interactive widget from `ipywidgets`." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [], + "source": [ + "# Set the marker properties as you like.\n", + "w.marker = {'type': 'circle', 'color': 'red', 'radius': 10,\n", + " 'linewidth': 2}\n", + "\n", + "# Define a function to control marker display\n", + "def show_circles(n):\n", + " \"\"\"Show and hide circles.\"\"\"\n", + " w.remove_all_markers()\n", + " t2show = t[:n]\n", + " w.add_markers(t2show)\n", + " with w.print_out:\n", + " print('Displaying {} markers...'.format(len(t2show)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We redisplay the image widget below above the slider. Note that the slider affects both this view of the image widget and the one near the top of the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "446131d7e9a24639b5a1be203c32e159", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "ImageWidget(children=(_AstroImage(children=(Figure(axes=[Axis(scale=LinearScale(allow_padding=False, max=1022.ā€¦" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "51d32b6b02de4103b39e2ef757e3bc49", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, continuous_update=False, description='n', max=482), Output()), _dom_cā€¦" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display\n", + "\n", + "import ipywidgets as ipyw\n", + "from ipywidgets import interactive\n", + "\n", + "# Show the slider widget.\n", + "slider = interactive(show_circles,\n", + " n=ipyw.IntSlider(min=0,max=len(t),step=1,value=0, continuous_update=False))\n", + "display(w, slider)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, use the slider. The chosen `n` represents the first `n` \"stars\" being displayed." + ] + } + ], + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}