Created
March 11, 2019 02:46
-
-
Save mwcraig/303cfb6e3e970d137e91789245ff259d to your computer and use it in GitHub Desktop.
A hacky implementation of a bqplot image viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from astropy.nddata import CCDData\n", | |
"from astropy.visualization import simple_norm\n", | |
"import bqplot as bq\n", | |
"import numpy as np\n", | |
"import ipywidgets as widgets\n", | |
"from ipyevents import Event\n", | |
"import matplotlib.image as mimg\n", | |
"from io import BytesIO\n", | |
"import tempfile\n", | |
"from PIL import Image" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"ccd = CCDData.read('/Users/mcraig/Documents/Research/observatory-update-2017-18/tracking_rate/2018-04-16/ey-uma-0001r.fit')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"WARNING: The following attributes were set on the data object, but will be ignored by the function: meta, unit, wcs [astropy.nddata.decorators]\n" | |
] | |
} | |
], | |
"source": [ | |
"from astropy.nddata.utils import block_reduce\n", | |
"\n", | |
"small_data = block_reduce(ccd, 4)\n", | |
"small_data = ccd.data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"norm = simple_norm(small_data, min_percent=22, max_percent=99.9)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"buff = BytesIO()\n", | |
"\n", | |
"# buff = open('temp_img.jpg', 'wb')\n", | |
"norm(small_data)\n", | |
"mimg.imsave(buff, norm(small_data), format='jpg', cmap='viridis')\n", | |
"# buff.close()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "b3399780a53f4a2ebb646056a4d5747d", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"Image(value=b'')" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"i = widgets.Image()\n", | |
"i" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"#pil = Image.fromarray(norm(small_data), 'F')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"i.value = buff.getvalue()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"#buff = open('temp_img.jpg', 'rb')\n", | |
"#i.value = buff.read()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"img = ccd.data" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Basic interactive image with pan/zoom\n", | |
"\n", | |
"This sets up a bqplot figure with pan/zoom enabled using the standard image mark (which does require a jpeg or png)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "a5cb49d8595647fe9afb49cf4b7392d4", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"Figure(axes=[Axis(scale=LinearScale(max=1.0, min=0.0)), Axis(orientation='vertical', scale=LinearScale(max=1.0…" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"max_size = 500\n", | |
"\n", | |
"# The first set of scales would set up the plots in pixel coordinates\n", | |
"scales = {'x': bq.LinearScale(min=0, max=img.shape[0]), 'y': bq.LinearScale(min=0, max=img.shape[1])}\n", | |
"\n", | |
"# This does the scales from 0 to 1. Either way is fine...\n", | |
"scales = {'x': bq.LinearScale(min=0, max=1), 'y': bq.LinearScale(min=0, max=1)}\n", | |
"\n", | |
"# The x/y arguments to the Image mark specify what range of x/y values in the plot\n", | |
"# they should fill.\n", | |
"\n", | |
"#image = bq.Image(image=i, scales=scales, x=(0, img.shape[0]), y=(0, img.shape[1]))\n", | |
"image = bq.Image(image=i, scales=scales, x=(0, 1), y=(0, 1))\n", | |
"\n", | |
"#lines = bq.Lines(x=[0, 1, 1, 0, 0], y=[0, 0, 1, 1, 0], scales=scales, colors=['red'])\n", | |
"\n", | |
"pz = bq.interacts.PanZoom(scales={'x': [scales['x']], 'y': [scales['y']]})\n", | |
"\n", | |
"fig = bq.Figure(marks=[image], padding_x=0, padding_y=0, interaction=pz,) \n", | |
" #fig_margin={'bottom': 0, 'left': 0, 'right': 0, 'top': 0})\n", | |
"fig.layout.width = '500px'\n", | |
"fig.layout.height = '500px'\n", | |
"fig.axes = [bq.Axis(scale=scales['x']), bq.Axis(scale=scales['y'], orientation='vertical')]\n", | |
"#pz = bq.Toolbar(figure=fig)\n", | |
"display(fig)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Other interactions are possible in bqplot...\n", | |
"\n", | |
"...like this brush selector, though it is commented out for the moment." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"brush = bq.interacts.BrushSelector(color='pink')\n", | |
"brush.x_scale = scales['x']\n", | |
"brush.y_scale = scales['y']\n", | |
"# fig.interaction = brush" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Clicks don't work at the moment on images 🙄\n", | |
"\n", | |
"But see https://github.com/bloomberg/bqplot/pull/692\n", | |
"\n", | |
"The upshot is that nothing will be generated in the output widget below." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"out = widgets.Output()\n", | |
"\n", | |
"def got_click(a, b):\n", | |
" with out:\n", | |
" print(a, b)\n", | |
" \n", | |
"#image.on_click(got_click)\n", | |
"image.on_element_click(got_click)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "92ccd2d1e62f4ca99c306cf87e7753c8", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"Output()" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"out" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### In principle, we could use ipyevents on bqplot" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"e = Event()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"e.source = fig\n", | |
"e.watched_events = ['click']" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "c9c79c276c124bca97238db9df9363b8", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"HTML(value='')" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"h = widgets.HTML()\n", | |
"\n", | |
"def display_ev(event):\n", | |
" relative_coords = (event['relativeX'], event['relativeY'])\n", | |
" x_lim = (scales['x'].min, scales['x'].max)\n", | |
" y_lim = (scales['y'].min, scales['y'].max)\n", | |
" try:\n", | |
" image_x = relative_coords[0] / 500 * (x_lim[1] - x_lim[0]) + x_lim[0]\n", | |
" except Exception as e:\n", | |
" h.value = str(e)\n", | |
" else:\n", | |
" h.value = str(image_x)\n", | |
" #lines = ['{}: {}'.format(k, v) for k, v in event.items()]\n", | |
" #content = '<br>'.join(lines)\n", | |
" #h.value = content\n", | |
"\n", | |
"e.on_dom_event(display_ev)\n", | |
"h" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### This removes the ipevents event handler\n", | |
"\n", | |
"Not necessary, don't remember why I removed it." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"e.on_dom_event(display_ev, remove=True)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Let's add some markers...\n", | |
"\n", | |
"Marker positions need to match whatever x/y scales were chosen above for the image" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"random_markers = np.random.rand(100, 2)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"scats = bq.Scatter(x=random_markers[:, 1], y=random_markers[:, 0], scales=scales, colors=['transparent'], \n", | |
" default_opacities=[1.0], stroke='cyan', default_size=200)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Need to change the figure contents to display the markers" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"fig.marks = [image, scats]" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### You can safely ignore the rest...." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 29, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"scats.opacity = [0.2]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"zooming = False" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def changing_zoom(change):\n", | |
" with out:\n", | |
" print(change)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"oob = scales['x']" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"oob.observe(changing_zoom)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"out = widgets.Output()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"out" | |
] | |
}, | |
{ | |
"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.4" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment