Skip to content

Instantly share code, notes, and snippets.

@mwcraig
Created March 11, 2019 02:46
Show Gist options
  • Save mwcraig/303cfb6e3e970d137e91789245ff259d to your computer and use it in GitHub Desktop.
Save mwcraig/303cfb6e3e970d137e91789245ff259d to your computer and use it in GitHub Desktop.
A hacky implementation of a bqplot image viewer
Display the source blob
Display the rendered blob
Raw
{
"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