Files
hands-on/16_reinforcement_learning.ipynb
Haesun Park 0ebefaefbb 16장 실행
2018-04-26 15:23:26 +09:00

13357 lines
892 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPython 3.5.5\n",
"IPython 6.3.0\n",
"\n",
"numpy 1.14.2\n",
"sklearn 0.19.1\n",
"scipy 1.0.1\n",
"matplotlib 2.2.2\n",
"tensorflow 1.7.0\n"
]
}
],
"source": [
"%load_ext watermark\n",
"%watermark -v -p numpy,sklearn,scipy,matplotlib,tensorflow"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**16장 강화 학습**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"_이 노트북은 15장에 있는 모든 샘플 코드와 연습문제 해답을 가지고 있습니다._"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 설정"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"파이썬 2와 3을 모두 지원합니다. 공통 모듈을 임포트하고 맷플롯립 그림이 노트북 안에 포함되도록 설정하고 생성한 그림을 저장하기 위한 함수를 준비합니다:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# 파이썬 2와 파이썬 3 지원\n",
"from __future__ import division, print_function, unicode_literals\n",
"\n",
"# 공통\n",
"import numpy as np\n",
"import os\n",
"import sys\n",
"\n",
"# 일관된 출력을 위해 유사난수 초기화\n",
"def reset_graph(seed=42):\n",
" tf.reset_default_graph()\n",
" tf.set_random_seed(seed)\n",
" np.random.seed(seed)\n",
"\n",
"# 맷플롯립 설정\n",
"%matplotlib nbagg\n",
"import matplotlib\n",
"import matplotlib.animation as animation\n",
"import matplotlib.pyplot as plt\n",
"plt.rcParams['axes.labelsize'] = 14\n",
"plt.rcParams['xtick.labelsize'] = 12\n",
"plt.rcParams['ytick.labelsize'] = 12\n",
"\n",
"# 한글출력\n",
"plt.rcParams['font.family'] = 'NanumBarunGothic'\n",
"plt.rcParams['axes.unicode_minus'] = False\n",
"\n",
"# 그림을 저장할 폴더\n",
"PROJECT_ROOT_DIR = \".\"\n",
"CHAPTER_ID = \"rl\"\n",
"\n",
"def save_fig(fig_id, tight_layout=True):\n",
" path = os.path.join(PROJECT_ROOT_DIR, \"images\", CHAPTER_ID, fig_id + \".png\")\n",
" if tight_layout:\n",
" plt.tight_layout()\n",
" plt.savefig(path, format='png', dpi=300)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# OpenAI 짐(gym)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이 노트북에서는 강화 학습 알고리즘을 개발하고 비교할 수 있는 훌륭한 도구인 [OpenAI 짐(gym)](https://gym.openai.com/)을 사용합니다. 짐은 *에이전트*가 학습할 수 있는 많은 환경을 제공합니다. `gym`을 임포트해 보죠:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import gym"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"그다음 MsPacman 환경 버전 0을 로드합니다."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"env = gym.make('MsPacman-v0')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`reset()` 메서드를 호출하여 환경을 초기화합니다. 이 메서드는 하나의 관측을 반환합니다:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"obs = env.reset()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"관측은 환경마다 다릅니다. 여기에서는 [width, height, channels] 크기의 3D 넘파이 배열로 저장되어 있는 RGB 이미지입니다(채널은 3개로 빨강, 초록, 파랑입니다). 잠시 후에 보겠지만 다른 환경에서는 다른 오브젝트가 반환될 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(210, 160, 3)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"obs.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"환경은 `render()` 메서드를 사용하여 화면에 나타낼 수 있고 렌더링 모드를 고를 수 있습니다(렌더링 옵션은 환경마다 다릅니다). 이 경우에는 `mode=\"rgb_array\"`로 지정해서 넘파이 배열로 환경에 대한 이미지를 받겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"img = env.render(mode=\"rgb_array\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이미지를 그려보죠:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,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\" width=\"500\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(5,4))\n",
"plt.imshow(img)\n",
"plt.axis(\"off\")\n",
"save_fig(\"MsPacman\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"1980년대로 돌아오신 걸 환영합니다! :)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이 환경에서는 렌더링된 이미지가 관측과 동일합니다(하지만 많은 경우에 그렇지 않습니다):"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(img == obs).all()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"환경을 그리기 위한 유틸리티 함수를 만들겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def plot_environment(env, figsize=(5,4)):\n",
" plt.close() # 이렇게 하지 않으면 nbagg 백엔드가 이전 그래프를 그릴 때가 있습니다\n",
" plt.figure(figsize=figsize)\n",
" img = env.render(mode=\"rgb_array\")\n",
" plt.imshow(img)\n",
" plt.axis(\"off\")\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"환경을 어떻게 다루는지 보겠습니다. 에이전트는 \"행동 공간\"(가능한 행동의 모음)에서 하나의 행동을 선택합니다. 이 환경의 액션 공간을 다음과 같습니다:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Discrete(9)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env.action_space"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`Discrete(9)`는 가능한 행동이 정수 0에서부터 8까지있다는 의미입니다. 이는 조이스틱의 9개의 위치(0=중앙, 1=위, 2=오른쪽, 3=왼쪽, 4=아래, 5=오른쪽위, 6=왼쪽위, 7=오른쪽아래, 8=왼쪽아래)에 해당합니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"그다음 환경에게 플레이할 행동을 알려주고 게임의 다음 단계를 진행시킵니다. 왼쪽으로 110번을 진행하고 왼쪽아래로 40번을 진행해 보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"env.reset()\n",
"for step in range(110):\n",
" env.step(3) #왼쪽\n",
"for step in range(40):\n",
" env.step(8) #왼쪽아래"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"어디에 있을까요?"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,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\" width=\"500\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_environment(env)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"사실 `step()` 함수는 여러 개의 중요한 객체를 반환해 줍니다:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"obs, reward, done, info = env.step(0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"앞서 본 것처럼 관측은 보이는 환경을 설명합니다. 여기서는 210x160 RGB 이미지입니다:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(210, 160, 3)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"obs.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"환경은 마지막 스텝에서 받을 수 있는 보상을 알려 줍니다:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.0"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reward"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"게임이 종료되면 환경은 `done=True`를 반환합니다:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"done"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"마지막으로 `info`는 환경의 내부 상태에 관한 추가 정보를 제공하는 딕셔너리입니다. 디버깅에는 유용하지만 에이전트는 학습을 위해서 이 정보를 사용하면 안됩니다(학습이 아니고 속이는 셈이므로)."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'ale.lives': 3}"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"info"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"10번의 스텝마다 랜덤한 방향을 선택하는 식으로 전체 게임(3개의 팩맨)을 플레이하고 각 프레임을 저장해 보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"frames = []\n",
"\n",
"n_max_steps = 1000\n",
"n_change_steps = 10\n",
"\n",
"obs = env.reset()\n",
"for step in range(n_max_steps):\n",
" img = env.render(mode=\"rgb_array\")\n",
" frames.append(img)\n",
" if step % n_change_steps == 0:\n",
" action = env.action_space.sample() # play randomly\n",
" obs, reward, done, info = env.step(action)\n",
" if done:\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이제 애니메이션으로 한번 보죠(주피터에서는 조금 지글거립니다):"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"def update_scene(num, frames, patch):\n",
" patch.set_data(frames[num])\n",
" return patch,\n",
"\n",
"def plot_animation(frames, repeat=False, interval=40):\n",
" plt.close() # 이렇게 하지 않으면 nbagg 백엔드가 이전 그래프를 그릴 때가 있습니다\n",
" fig = plt.figure()\n",
" patch = plt.imshow(frames[0])\n",
" plt.axis('off')\n",
" return animation.FuncAnimation(fig, update_scene, fargs=(frames, patch), frames=len(frames), repeat=repeat, interval=interval)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3dPZIkxpEm7MYxYACvsMLos/LqvAg07hGGWl8E+so7+gh7BZDGY/RacQh0F1BV4ZHp4fH3fMraZ4j0iHjcsxEvEhh+9+XLly+f/H8ECBAgQIAAAQIECBAoEPhOAClQtgUBAgQIECBAgAABAv8UEEAMAgECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1jQgQIECAAAECBAgQEEDMAAECBAgQIECAAAECZQICSBm1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tx4rIICM9VWdQIqAAJLCqAgBAgS6BQSQbjIfINAUEECaRBYQmC8ggMzvgRMQIHCngAByZ9/deqyAADLWV3UCKQICSAqjIgQIEOgWEEC6yXyAQFNAAGkSWUBgvoAAMr8HTkCAwJ0CAsidfXfrsQICyFhf1QmkCAggKYyKECBAoFtAAOkm8wECTQEBpElkAYH5AgLI/B44AQECdwoIIHf23a3HCgggY31VJ5AiIICkMCpCgACBbgEBpJvMBwg0BQSQJpEFBOYLCCDze+AEBAjcKSCA3Nl3tx4rIICM9VWdQIqAAJLCqAgBAgS6BQSQbjIfINAUEECaRBYQmC8ggMzvgRMQIHCngAByZ9/deqyAADLWV3UCKQICSAqjIgQIEOgWEEC6yXyAQFNAAGkSWUBgvoAAMr8HTkCAwJ0CAsidfXfrsQICyFhf1QmkCAggKYyKECBAoFtAAOkm8wECTQEBpElkAYH5AgLI/B44AQECdwoIIHf23a3HCgggY31VJ0CAAAECBAgQIEDgGwEBxDgQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAmIICUUduIAAECBAgQIECAAAEBxAwQIECAAAECBAgQIFAm8P8ByWsYUR6janYAAAAASUVORK5CYII=\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"video = plot_animation(frames)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"환경을 더 이상 사용하지 않으면 환경을 종료하여 자원을 반납합니다:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"첫 번째 에이전트를 학습시키기 위해 간단한 Cart-Pole 환경을 사용하겠습니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 간단한 Cart-Pole 환경"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cart-Pole은 아주 간단한 환경으로 왼쪽이나 오른쪽으로 움직일 수 있는 카트와 카트 위에 수직으로 서 있는 막대로 구성되어 있습니다. 에이전트는 카트를 왼쪽이나 오른쪽으로 움직여서 막대가 넘어지지 않도록 유지시켜야 합니다."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n"
]
}
],
"source": [
"env = gym.make(\"CartPole-v0\")"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"obs = env.reset()"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ 0.02573366, -0.02697052, 0.03177656, 0.02587927])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"obs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"관측은 4개의 부동소수로 구성된 1D 넘파이 배열입니다. 각각 카트의 수평 위치, 속도, 막대의 각도(0=수직), 각속도를 나타냅니다. 이 환경을 렌더링하려면 먼저 몇 가지 이슈를 해결해야 합니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 렌더링 이슈 해결하기"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"일부 환경(Cart-Pole을 포함하여)은 `rgb_array` 모드를 설정하더라도 별도의 창을 띄우기 위해 디스플레이 접근이 필수적입니다. 일반적으로 이 창을 무시하면 됩니다. 주피터가 헤드리스(headless) 서버로 (즉 스크린이 없이) 실행중이면 예외가 발생합니다. 이를 피하는 한가지 방법은 Xvfb 같은 가짜 X 서버를 설치하는 것입니다. `xvfb-run` 명령을 사용해 주피터를 실행합니다:\n",
"\n",
" $ xvfb-run -s \"-screen 0 1400x900x24\" jupyter notebook\n",
" \n",
"주피터가 헤드리스 서버로 실행 중이지만 Xvfb를 설치하기 번거롭다면 Cart-Pole에 대해서는 다음 렌더링 함수를 사용할 수 있습니다:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"from PIL import Image, ImageDraw\n",
"\n",
"try:\n",
" from pyglet.gl import gl_info\n",
" openai_cart_pole_rendering = True # 문제없음, OpenAI 짐의 렌더링 함수를 사용합니다\n",
"except Exception:\n",
" openai_cart_pole_rendering = False # 가능한 X 서버가 없다면, 자체 렌더링 함수를 사용합니다\n",
"\n",
"def render_cart_pole(env, obs):\n",
" if openai_cart_pole_rendering:\n",
" # OpenAI 짐의 렌더링 함수를 사용합니다\n",
" return env.render(mode=\"rgb_array\")\n",
" else:\n",
" # Cart-Pole 환경을 위한 렌더링 (OpenAI 짐이 처리할 수 없는 경우)\n",
" img_w = 600\n",
" img_h = 400\n",
" cart_w = img_w // 12\n",
" cart_h = img_h // 15\n",
" pole_len = img_h // 3.5\n",
" pole_w = img_w // 80 + 1\n",
" x_width = 2\n",
" max_ang = 0.2\n",
" bg_col = (255, 255, 255)\n",
" cart_col = 0x000000 # 파랑 초록 빨강\n",
" pole_col = 0x669acc # 파랑 초록 빨강\n",
"\n",
" pos, vel, ang, ang_vel = obs\n",
" img = Image.new('RGB', (img_w, img_h), bg_col)\n",
" draw = ImageDraw.Draw(img)\n",
" cart_x = pos * img_w // x_width + img_w // x_width\n",
" cart_y = img_h * 95 // 100\n",
" top_pole_x = cart_x + pole_len * np.sin(ang)\n",
" top_pole_y = cart_y - cart_h // 2 - pole_len * np.cos(ang)\n",
" draw.line((0, cart_y, img_w, cart_y), fill=0)\n",
" draw.rectangle((cart_x - cart_w // 2, cart_y - cart_h // 2, cart_x + cart_w // 2, cart_y + cart_h // 2), fill=cart_col) # draw cart\n",
" draw.line((cart_x, cart_y - cart_h // 2, top_pole_x, top_pole_y), fill=pole_col, width=pole_w) # draw pole\n",
" return np.array(img)\n",
"\n",
"def plot_cart_pole(env, obs):\n",
" plt.close() # 이렇게 하지 않으면 nbagg 백엔드가 이전 그래프를 그릴 때가 있습니다\n",
" img = render_cart_pole(env, obs)\n",
" plt.imshow(img)\n",
" plt.axis(\"off\")\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"openai_cart_pole_rendering = True"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d0ZEbxxVAUTINpyGl4TSoMFQKg0rDachpOI11rWyo1ljJCyymL7p7Dr+BeT2n3wevyBW/vry8vHzxiwABAgQIECBAgAABAoHAVwESKBtBgAABAgQIECBAgMDvAgLEIhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAg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RIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIOnSt4sAAAn3SURBVECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgT+Dbq1TJ6U8AS1AAAAAElFTkSuQmCC\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_cart_pole(env, obs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"행동 공간을 확인해 보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Discrete(2)"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env.action_space"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"네 딱 두 개의 행동이 있네요. 왼쪽이나 오른쪽 방향으로 가속합니다. 막대가 넘어지기 전까지 카트를 왼쪽으로 밀어보죠:"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [],
"source": [
"obs = env.reset()\n",
"while True:\n",
" obs, reward, done, info = env.step(0)\n",
" if done:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d4ZEkRxWF0V03cAO5gRvIDIXMkNzADeEGbgxRIiYYrbS7nd1Vt/JmHv4yXfnqvIxQfPSM+Pz29vb2yX8IECBAgAABAgQIECAQEPgsQALKjiBAgAABAgQIECBA4HcBAeIiECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBAgAABAgQIEBAg7gABAgQIECBAgAABAjEBARKjdhABAgQIECBAgAABAgLEHSBAgAABAgQIECBAICYgQGLUDiJAgAABAgQIECBAQIC4AwQIECBAgAABAgQIxAQESIzaQQQIECBAgAABAgQICBB3gAABAgQIECBAgACBmIAAiVE7iAABAgQIECBAgAABAeIOECBAgAABAgQIECAQExAgMWoHESBAgAABAgQIECAgQNwBAgQIECBAgAABAgRiAgIkRu0gAgQIECBAgAABAgQEiDtAgAABAgQIECBAgEBMQIDEqB1EgAABAgQIECBAgIAAcQcIECBAgAABAgQIEIgJCJAYtYMIECBA4FGBf//64+8/+vd//vLoR/wcAQIECJQICJCSRRmTAAECOwm8B8hfvbMo2ekmeFcCBFYUECArbtU7ESBAoFzgWwHy5asJkvJlG58Age0EBMh2K/fCBAgQmF9AgMy/IxMSIEDgWQEB8qyczxEgQIDA6QIj4fF+uG9ATl+DBxIgQOBSAQFyKa+HEyBAgMCIgAAZ0fKzBAgQ6BQQIJ17MzUBAgSWFBgNEN9+LHkNvBQBAosLCJDFF+z1CBAg0CQgQJq2ZVYCBAg8JyBAnnPzKQIECBC4QECAXIDqkQQIEJhMQIBMthDjECBAYFeB0fg4nPwK1q63xXsTINAsIECat2d2AgQILCQwGiDiY6HlexUCBLYSECBbrdvLEiBAYF4BATLvbkxGgACBMwUEyJmankWAAAECTwsIkKfpfJAAAQJVAgKkal2GJUCAwLoCAmTd3XozAgQIfBQQIO4DAQIECEwhIECmWIMhCBAgcLmAALmc2AEECBAg8D2B0fg4nueP0L+n6r8nQIDAnAICZM69mIoAAQJbCYwGiPjY6np4WQIEFhMQIIst1OsQIECgUUCANG7NzAQIEHhOQIA85+ZTBAgQIHCigAA5EdOjCBAgMLmAAJl8QcYjQIDADgICZIcte0cCBAj8T0CAuAkECBAgcLuAALl9BQYgQIBATECAxKgdRIAAAQJ/JTAaH8cz/BG6u0SAAIFeAQHSuzuTEyBAYAmB0QARH0us3UsQILCxgADZePlenQABAjMICJAZtmAGAgQI5AQESM7aSQQIECDwFwICxLUgQIDAXgICZK99e1sCBAhMJTAaH8fwfgVrqhUahgABAsMCAmSYzAcIECBA4CwBAXKWpOcQIECgR0CA9OzKpAQIEFhOYCRAfPOx3Pq9EAECmwoIkE0X77UJECAwg4AAmWELZiBAgEBWQIBkvZ1GgAABAh8EBIjrQIAAgf0EBMh+O/fGBAgQmEZAgEyzCoMQIEAgJiBAYtQOIkCAAIGPAiPxcXzO34C4PwQIEFhDQICssUdvQYAAgTqBkQARH3XrNTABAgS+KiBAXA4CBAgQuEVAgNzC7lACBAjcLiBAbl+BAQgQILCngADZc+/emgABAgLEHSBAgACBWwQEyC3sDiVAgMDtAgLk9hUYgAABAnsJjITHu4y/AdnrjnhbAgTWFhAga+/X2xEgQGA6gdEAER/TrdBABAgQeElAgLzE58MECBAgMCogQEbF/DwBAgTWEhAga+3T2xAgQGB6AQEy/YoMSIAAgUsFBMilvB5OgAABAl8KCBB3ggABAnsLCJC99+/tCRAgEBcQIHFyBxIgQGAqAQEy1ToMQ4AAgbUFRuPj0PBH6GvfCW9HgMB+AgJkv517YwIECNwmMBog4uO2VTmYAAEClwkIkMtoPZgAAQIEvhQQIO4EAQIECAgQd4AAAQIEYgICJEbtIAIECEwrIECmXY3BCBAgsJbAaHwcb+9XsNa6A96GAAECh4AAcQ8IECBAICIwGiDiI7IWhxAgQCAuIEDi5A4kQIDAngICZM+9e2sCBAh8KSBA3AkCBAgQiAgIkAizQwgQIDC9gACZfkUGJECAwBoCAmSNPXoLAgQIvCogQF4V9HkCBAgQeEhAgDzE5IcIECCwvIAAWX7FXpAAAQL3C4zGxzGxP0K/f28mIECAwBUCAuQKVc8kQIAAgT8IjAaI+HCBCBAgsK6AAFl3t96MAAEC0wgIkGlWYRACBAjcLiBAbl+BAQgQILC+gABZf8fekAABAo8KCJBHpfwcAQIECDwtIECepvNBAgQILCcgQJZbqRciQIDAXAKj8XFM729A5tqhaQgQIHCmgAA5U9OzCBAgQOBPAiMBIjxcIAIECKwvIEDW37E3JECAwK0CAuRWfocTIEBgOgEBMt1KDESAAIG1BATIWvv0NgQIEHhVQIC8KujzBAgQIPBNAQHighAgQIDARwEB4j4QIECAwGUCI/FxDOFvQC5bhQcTIEBgGgEBMs0qDEKAAIH1BATIejv1RgQIEHhVQIC8KujzBAgQIPBVgZEA8e2Hi0SAAIE9BATIHnv2lgQIELhFQIDcwu5QAgQITC0gQKZej+EIECDQLSBAuvdnegIECFwhIECuUPVMAgQIEPhdQIC4CAQIECDwpYAAcScIECBA4BKBkfg4BvA3IJeswUMJECAwnYAAmW4lBiJAgMAaAiMBIj7W2Lm3IECAwCMCAuQRJT9DgAABAsMCAmSYzAcIECCwhYAA2WLNXpIAAQJ5AQGSN3ciAQIEGgQESMOWzEiAAIFCAQFSuDQjEyBAICAgQALIjiBAgMBOAiPh8e7ib0B2uiHelQCB3QUEyO43wPsTIEDgZIHRABEfJy/A4wgQIDC5gACZfEHGI0CAQJuAAGnbmHkJECCQFRAgWW+nESBAYHkBAbL8ir0gAQIEXhIQIC/x+TABAgQIfCkgQNwJAgQIEPiWgABxPwgQIEDgVAEBciqnhxEgQGA5AQGy3Eq9EAECBO4TGI2PY1J/hH7fvpxMgACBOwQEyB3qziRAgMCiAqMBIj4WvQheiwABAt8QECCuBwECBAicJiBATqP0IAIECCwrIECWXa0XI0CAQF5AgOTNnUiAAIE2AQHStjHzEiBAYFKB0fg4XsOvYE26TGMRIEDgQgEBciGuRxMgQGAnAQGy07a9KwECBJ4XECDP2/kkAQIECHwQGA0Q3364PgQIENhTQIDsuXdvTYAAgdMFBMjppB5IgACBJQUEyJJr9VIECBDICwiQvLkTCRAg0CggQBq3ZmYCBAhMKCBAJlyKkQgQIDChgACZcClGIkCAQJvAaHwc7+dvQNq2bF4CBAicIyBAznH0FAIECGwtMBIgwmPrq+LlCRAg8EmAuAQECBAg8LKAAHmZ0AMIECCwjYAA2WbVXpQAAQLXCQiQ62w9mQABAqsJCJDVNup9CBAgcIOAALkB3ZEECBAoFRAgpYszNgECBGYRGImPY2Z/AzLL5sxBgACBewQEyD3uTiVAgMAyAiMBIj6WWbsXIUCAwNMCAuRpOh8kQIAAgUNAgLgHBAgQIDAiIEBGtPwsAQIECPxJQIC4FAQIECAwIiBARrT8LAECBAgIEHeAAAECBF4SECAv8fkwAQIECPgGxB0gQIAAgREBATKi5WcJECBA4A8CI/FxfNAfobtABAgQICBA3AECBAgQeFpgJEDEx9PMPkiAAIGlBATIUuv0MgQIEMgKCJCst9MIECCwgoAAWWGL3oEAAQI3CQiQm+AdS4AAgWIBAVK8PKMTIEDgLoGR8Hif0a9g3bUt5xIgQGAuAQEy1z5MQ4AAgQqB0QARHxVrNSQBAgQiAgIkwuwQAgQIrCUgQNbap7chQIBAUkCAJLWdRYAAgUUEBMgii/QaBAgQuEFAgNyA7kgCBAi0CwiQ9g2anwABAvcJCJD77J1MgACBWgEBUrs6gxMgQOB2AQFy+woMQIAAgS6B0fg43s4foXft2LQECBC4UkCAXKnr2QQIEFhQYDRAxMeCl8ArESBA4AUBAfICno8SIEBgRwEBsuPWvTMBAgTOExAg51l6EgECBLYQECBbrNlLEiBA4DIBAXIZrQcTIEBgTQEBsuZevRUBAgRSAgIkJe0cAgQILCAwGh/HK/sbkAUW7xUIECBwooAAORHTowgQILC6gABZfcPejwABAtcLCJDrjZ1AgACBZQRGA8S3H8us3osQIEDgNAEBchqlBxEgQGB9AQGy/o69IQECBK4WECBXC3s+AQIEFhIQIAst06sQIEDgJgEBchO8YwkQINAmMBofx/v5Fay2LZuXAAEC1wsIkOuNnUCAAIElBATIEmv0EgQIELhdQIDcvgIDECBAoENgNEB8+9GxV1MSIEAgLSBA0uLOI0CAQKmAACldnLEJECAwmYAAmWwhxiFAgMCsAgJk1s2YiwABAl0CAqRrX6YlQIDAbQIC5DZ6BxMgQGApAQGy1Dq9DAECBK4RGI2PYwp/A3LNLjyVAAEC7QICpH2D5idAgEBAYCRAhEdgIY4gQIBAsYAAKV6e0QkQIJASECApaecQIEBgfQEBsv6OvSEBAgsLfP78OfJ2v/3yz4fP+eHHXx/+2Ud/8O3t7dEf9XMECBAgMLmAAJl8QcYjQIDAtwQEiPtBgAABAm0CAqRtY+YlQIDAB4FEgIx8+3GM5hsQV5QAAQIEvvk/nr35XtsNIUCAQK1AKkD+9Z+v/wrWP/72/1+5uiI+juX4R1XtFTU4AQIE/iTgGxCXggABAsUCiQD56affviv0HiEC5LtUfoAAAQLbCwiQ7a8AAAIEmgVmCZDD8IgQAdJ8m8xOgACBjIAAyTg7hQABApcICJBLWD2UAAECBC4UECAX4no0AQIErhYQIFcLez4BAgQInC0gQM4W9TwCBAgEBa4MkEf+9uPjq/oVrODiHUWAAIFiAQFSvDyjEyBA4MoAef/X737r34D1cQM///zDZQvxb8G6jNaDCRAgEBcQIHFyBxIgQOA8AQFynqUnESBAgEBGQIBknJ1CgACBSwRmCZArf/3qgPMNyCXXx0MJECBwi4AAuYXdoQQIEDhHIBEgx6SP/B8RXvWv4BUg59wVTyFAgMAsAgJklk2YgwABAk8ICJAn0HyEAAECBG4VECC38jucAAECrwlcGSBfm+z9j9M//vdXfvvhG5DX7ohPEyBAYDYBATLbRsxDgACBAYE7AmRgvNN+1N+AnEbpQQQIELhdQIDcvgIDECBA4HkBAfK8nU8SIECAwD0CAuSD+y7/IL/nqjmVAAECBAgQIEAgKTDrt8cCJHkLnEWAAIGTBXb5H05m/Yfoyev0OAIECGwhIEC2WLOXJEBgVQEBsupmvRcBAgTWFRAg6+7WmxEgsIGAANlgyV6RAAECiwkIkMUW6nUIENhLQIDstW9vS4AAgRUEBMgKW/QOBAhsKyBAtl29FydAgECtgACpXZ3BCRAg8OmTAHELCBAgQKBNQIC0bcy8BAgQ+CAgQFwHAgQIEGgTECBtGzMvAQIEBIg7QIAAAQLFAgKkeHlGJ0CAgG9A3AECBAgQaBMQIG0bMy8BAgR8Awn1AHoAAAxFSURBVOIOECBAgECxgAApXp7RCRAg4BsQd4AAAQIE2gQESNvGzEuAAAHfgLgDBAgQIFAsIECKl2d0AgQI+AbEHSBAgACBNgEB0rYx8xIgQMA3IO4AAQIECBQLCJDi5RmdAAECvgFxBwgQIECgTUCAtG3MvAQIEPANiDtAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAgKkeHlGJ0CAAAECBAgQINAmIEDaNmZeAgQIECBAgAABAsUCAqR4eUYnQIAAAQIECBAg0CYgQNo2Zl4CBAgQIECAAAECxQICpHh5RidAgAABAgQIECDQJiBA2jZmXgIECBAgQIAAAQLFAv8F0HqEnmZbSocAAAAASUVORK5CYII=\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.close() # 이렇게 하지 않으면 nbagg 백엔드가 이전 그래프를 그릴 때가 있습니다\n",
"img = render_cart_pole(env, obs)\n",
"plt.imshow(img)\n",
"plt.axis(\"off\")\n",
"save_fig(\"cart_pole_plot\")"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(400, 600, 3)"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"img.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"막대가 실제로 넘어지지 않더라도 너무 기울어지면 게임이 끝납니다. 환경을 다시 초기화하고 이번에는 오른쪽으로 밀어보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [],
"source": [
"obs = env.reset()\n",
"while True:\n",
" obs, reward, done, info = env.step(1)\n",
" if done:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,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gAABAgRqCAggNeakSgIECBAgQIAAAQItBASQFmPUBAECBAgQIECAAIEaAgJIjTmpkgABAgQIECBAgEALAQGkxRg1QYAAAQIECBAgQKCGgABSY06qJECAAAECBAgQINBCQABpMUZNECBAgAABAgQIEKghIIDUmJMqCRAgQIAAAQIECLQQEEBajFETBAgQIECAAAECBGoICCA15qRKAgQIECBAgAABAi0EBJAWY9QEAQIECBAgQIAAgRoCAkiNOamSAAECBAgQIECAQAsBAaTFGDVBgAABAgQIECBAoIaAAFJjTqokQIAAAQIECBAg0EJAAGkxRk0QIECAAAECBAgQqCEggNSYkyoJECBAgAABAgQItBAQQFqMURMECBAgQIAAAQIEaggIIDXmpEoCBAgQIECAAAECLQQEkBZj1AQBAgQIECBAgACBGgICSI05qZIAAQIECBAgQIBACwEBpMUYNUGAAAECBAgQIECghoAAUmNOqiRAgAABAgQIECDQQkAAaTFGTRAgQIAAAQIECBCoISCA1JiTKgkQIECAAAECBAi0EBBAWoxREwQIECBAgAABAgRqCAggNeakSgIECBAgQIAAAQItBASQFmPUBAECBAgQIECAAIEaAgJIjTmpkgABAgQIECBAgEALAQGkxRg1QYAAAQIECBAgQKCGgABSY06qJECAAAECBAgQINBCQABpMUZNECBAgAABAgQIEKghIIDUmJMqCRAgQIAAAQIECLQQEEBajFETBAgQIECAAAECBGoICCA15qRKAgQIECBAgAABAi0EBJAWY9QEAQIECBAgQIAAgRoCAkiNOamSAAECBAgQIECAQAsBAaTFGDVBgAABAgQIECBAoIaAAFJjTqokQIAAAQIECBAg0EJAAGkxRk0QIECAAAECBAgQqCEggNSYkyoJECBAgAABAgQItBAQQFqMURMECBAgQIAAAQIEaggIIDXmpEoCBAgQIECAAAECLQQEkBZj1AQBAgQIECBAgACBGgICSI05qZIAAQIECBAgQIBACwEBpMUYNUGAAAECBAgQIECghoAAUmNOqiRAgAABAgQIECDQQkAAaTFGTRAgQIAAAQIECBCoISCA1JiTKgkQIECAAAECBAi0EBBAWoxREwQIECBAgAABAgRqCAggNeakSgIECBAgQIAAAQItBASQFmPUBAECBAgQIECAAIEaAgJIjTmpkgABAgQIECBAgEALAQGkxRg1QYAAAQIECBAgQKCGgABSY06qJECAAAECBAgQINBCQABpMUZNECBAgAABAgQIEKghIIDUmJMqCRAgQIAAAQIECLQQEEBajFETBAgQIECAAAECBGoICCA15qRKAgQIECBAgAABAi0EBJAWY9QEAQIECBAgQIAAgRoCAkiNOamSAAECBAgQIECAQAsBAaTFGDVBgAABAgQIECBAoIaAAFJjTqokQIAAAQIECBAg0EJAAGkxRk0QIECAAAECBAgQqCEggNSYkyoJECBAgAABAgQItBAQQFqMURMECBAgQIAAAQIEaggIIDXmpEoCBAgQIECAAAECLQQEkBZj1AQBAgQIECBAgACBGgICSI05qZIAAQIECBAgQIBACwEBpMUYNUGAAAECBAgQIECghoAAUmNOqiRAgAABAgQIECDQQkAAaTFGTRAgQIAAAQIECBCoISCA1JiTKgkQIECAAAECBAi0EBBAWoxREwQIECBAgAABAgRqCAggNeakSgIECBAgQIAAAQItBASQFmPUBAECBAgQIECAAIEaAgJIjTmpkgABAgQIECBAgEALAQGkxRg1QYAAAQIECBAgQKCGgABSY06qJECAAAECBAgQINBCQABpMUZNECBAgAABAgQIEKghIIDUmJMqCRAgQIAAAQIECLQQEEBajFETBAgQIECAAAECBGoICCA15qRKAgQIECBAgAABAi0EBJAWY9QEAQIECBAgQIAAgRoCAkiNOamSAAECBAgQIECAQAsBAaTFGDVBgAABAgQIECBAoIaAAFJjTqokQIAAAQIECBAg0EJAAGkxRk0QIECAAAECBAgQqCEggNSYkyoJECBAgAABAgQItBAQQFqMURMECBAgQIAAAQIEaggIIDXmpEoCBAgQIECAAAECLQQEkBZj1AQBAgQIECBAgACBGgICSI05qZIAAQIECBAgQIBACwEBpMUYNUGAAAECBAgQIECghoAAUmNOqiRAgAABAgQIECDQQkAAaTFGTRAgQIAAAQIECBCoISCA1JiTKgkQIECAAAECBAi0EBBAWoxREwQIECBAgAABAgRqCAggNeakSgIECBAgQIAAAQItBASQFmPUBAECBAgQIECAAIEaAgJIjTmpkgABAgQIECBAgEALAQGkxRg1QYAAAQIECBAgQKCGgABSY06q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AgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQIAAAQIE9hEQQPaZhUoIECBAgAABAgQItBcQQNqPWIMECBAgQOA8gdfr9fpR1+/3+32eio4J7CEggOwxB1UQIECAAAECEwUEkImYLkVgsoAAMhnU5QgQIECAAIH1AgLI+hmogMB/ExBA3BsECBAgQIBAOwEBpN1INdRIQABpNEytECBAgACBpwRWv+Cv3v8p5+/us9rn7v1H1x+5pf8N0Or9R/2NPh/VP/KJ1482GDXgcwIECBAgQOA8gfQFJBVbvX9a/93rV/vcvf/o+iPf9P139f6j/kafj+of+cTrRxuMGvA5AQIECBAgcJ5A+gKSiq3eP63/7vWrfe7ef3T9kW/6/rt6/1F/o89H9Y984vWjDUYN+JwAAQIECBA4TyB9AUnFVu+f1n/3+tU+d+8/uv7IN33/Xb3/qL/R56P6Rz7x+tEGowZ8ToAAAQIECJwnMHoBGYmk7x+j/dPrj+pfvf+ovtWfr/a5e/+7rz+aX7r/aP1o//Rz/xF6Kmg9AQIECBA4UCB9gUkDwmj/9Pqjka7ef1Tf6s9X+9y9/93XH80v3X+0frR/+rkAkgpaT4AAAQIEDhRIX2DSgDDaP73+aKSr9x/Vt/rz1T5373/39UfzS/cfrR/tn34ugKSC1hMgQIAAgQMF0heYNCCM9k+vPxrp6v1H9a3+fLXP3fvfff3R/NL9R+tH+6ef/x/S9oavgXRH2AAAAABJRU5ErkJggg==\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_cart_pole(env, obs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"아까 말했던 것과 같은 상황인 것 같습니다. 어떻게 막대가 똑 바로 서있게 만들 수 있을까요? 이를 위한 *정책*을 만들어야 합니다. 이 정책은 에이전트가 각 스텝에서 행동을 선택하기 위해 사용할 전략입니다. 어떤 행동을 할지 결정하기 위해 지난 행동이나 관측을 사용할 수 있습니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 하드 코딩 정책"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"간단한 정책을 하드 코딩해 보겠습니다. 막대가 왼쪽으로 기울어지면 카트를 왼쪽으로 밀고 반대의 경우는 오른쪽으로 밉니다. 작동이 되는지 확인해 보죠:"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [],
"source": [
"frames = []\n",
"\n",
"n_max_steps = 1000\n",
"n_change_steps = 10\n",
"\n",
"obs = env.reset()\n",
"for step in range(n_max_steps):\n",
" img = render_cart_pole(env, obs)\n",
" frames.append(img)\n",
"\n",
" # hard-coded policy\n",
" position, velocity, angle, angular_velocity = obs\n",
" if angle < 0:\n",
" action = 0\n",
" else:\n",
" action = 1\n",
"\n",
" obs, reward, done, info = env.step(action)\n",
" if done:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d4bEcRxWAUZQGaeA0SMMOw+Uw7DRIw6RBGo8aFxKSLOvN7s580z19+OvduT2nbxV8SAsf3t7e3v7mXwQIECBAgAABAgQIEAgEPgiQQNkIAgQIECBAgAABAgT+EBAgFoEAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAA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6CEECBA4VkCAHOvpaQQIECAwjoAAGecunIQAAQKfBASIZSBAgACBuwoIkLverPciQGBqAQEy9fU5PAECBAh8R0CAWA8CBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCNFxOJ8AAArtSURBVBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgMB/AbhaSZ7tq3RMAAAAAElFTkSuQmCC\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"video = plot_animation(frames)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"아니네요, 불안정해서 몇 번 움직이고 막대가 너무 기울어져 게임이 끝났습니다. 더 똑똑한 정책이 필요합니다!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 신경망 정책"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"관측을 입력으로 받고 각 관측에 대해 선택할 행동을 출력하는 신경망을 만들어 보겠습니다. 행동을 선택하기 위해 네트워크는 먼저 각 행동에 대한 확률을 추정하고 그다음 추정된 확률을 기반으로 랜덤하게 행동을 선택합니다. Cart-Pole 환경의 경우에는 두 개의 행동(왼쪽과 오른쪽)이 있으므로 하나의 출력 뉴런만 있으면 됩니다. 행동 0(왼쪽)에 대한 확률 `p`를 출력할 것입니다. 행동 1(오른쪽)에 대한 확률은 `1 - p`가 됩니다."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING:tensorflow:From /home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/tensorflow/contrib/learn/python/learn/datasets/base.py:198: retry (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n",
"Instructions for updating:\n",
"Use the retry module or similar alternatives.\n"
]
}
],
"source": [
"import tensorflow as tf\n",
"\n",
"# 1. 네트워크 구조를 설정합니다\n",
"n_inputs = 4 # == env.observation_space.shape[0]\n",
"n_hidden = 4 # 간단한 작업이므로 너무 많은 뉴런이 필요하지 않습니다\n",
"n_outputs = 1 # 왼쪽으로 가속할 확률을 출력합니다\n",
"initializer = tf.contrib.layers.variance_scaling_initializer()\n",
"\n",
"# 2. 네트워크를 만듭니다\n",
"X = tf.placeholder(tf.float32, shape=[None, n_inputs])\n",
"hidden = tf.layers.dense(X, n_hidden, activation=tf.nn.elu,\n",
" kernel_initializer=initializer)\n",
"outputs = tf.layers.dense(hidden, n_outputs, activation=tf.nn.sigmoid,\n",
" kernel_initializer=initializer)\n",
"\n",
"# 3. 추정된 확률을 기반으로 랜덤하게 행동을 선택합니다\n",
"p_left_and_right = tf.concat(axis=1, values=[outputs, 1 - outputs])\n",
"action = tf.multinomial(tf.log(p_left_and_right), num_samples=1)\n",
"\n",
"init = tf.global_variables_initializer()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이 환경은 각 관측이 환경의 모든 상태를 포함하고 있기 때문에 지난 행동과 관측은 무시해도 괜찮습니다. 숨겨진 상태가 있다면 이 정보를 추측하기 위해 이전 행동과 상태를 고려해야 합니다. 예를 들어, 속도가 없고 카트의 위치만 있다면 현재 속도를 예측하기 위해 현재의 관측뿐만 아니라 이전 관측도 고려해야 합니다. 관측에 잡음이 있을 때도 같은 경우입니다. 현재 상태를 근사하게 추정하기 위해 과거 몇 개의 관측을 사용하는 것이 좋을 것입니다. 이 문제는 아주 간단해서 현재 관측에 잡음이 없고 환경의 모든 상태가 담겨 있습니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"정책 네트워크에서 만든 확률을 기반으로 가장 높은 확률을 가진 행동을 고르지 않고 왜 랜덤하게 행동을 선택하는지 궁금할 수 있습니다. 이런 방식이 에이전트가 새 행동을 *탐험*하는 것과 잘 동작하는 행동을 *이용*하는 것 사이에 균형을 맞추게 합니다. 만약 어떤 레스토랑에 처음 방문했다고 가정합시다. 모든 메뉴에 대한 선호도가 동일하므로 랜덤하게 하나를 고릅니다. 이 메뉴가 맛이 좋았다면 다음에 이를 주문할 가능성을 높일 것입니다. 하지만 100% 확률이 되어서는 안됩니다. 그렇지 않으면 다른 메뉴를 전혀 선택하지 않게 되고 더 좋을 수 있는 메뉴를 시도해 보지 못하게 됩니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"정책 신경망을 랜덤하게 초기화하고 게임 하나를 플레이해 보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [],
"source": [
"n_max_steps = 1000\n",
"frames = []\n",
"\n",
"with tf.Session() as sess:\n",
" init.run()\n",
" obs = env.reset()\n",
" for step in range(n_max_steps):\n",
" img = render_cart_pole(env, obs)\n",
" frames.append(img)\n",
" action_val = action.eval(feed_dict={X: obs.reshape(1, n_inputs)})\n",
" obs, reward, done, info = env.step(action_val[0][0])\n",
" if done:\n",
" break\n",
"\n",
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"랜덤하게 초기화한 정책 네트워크가 얼마나 잘 동작하는지 확인해 보겠습니다:"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d4bFkSXWF0W435IZwQ26AGQRmgBtyA7mBG60oiIbpoZl369WtfTJzL/5Sr07mOhkhfRoKff327du3L/5FgAABAgQIECBAgACBgMBXARJQNoIAAQIECBAgQIAAgb8LCBAPgQABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAECBAgQIECAAIGYgACJURtEgAABAgQIECBAgIAA8QYIECBAgAABAgQIEIgJCJAYtUEECBAgQIAAAQIECAgQb4AAAQIECBAgQIAAgZiAAIlRG0SAAAECBAgQIECAgADxBggQIECAAAECBAgQiAkIkBi1QQQIECBAgAABAgQICBBvgAABAgQIECBAgACBmIAAiVEbRIAAAQIECBAgQICAAPEGCBAgQIAAAQIECBCICQiQGLVBBAgQIECAAAECBAgIEG+AAAEC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\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"video = plot_animation(frames)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"음.. 별로 좋지 않네요. 신경망이 더 잘 학습되어야 합니다. 먼저 앞서 사용한 기본 정책을 학습할 수 있는지 확인해 보겠습니다. 막대가 왼쪽으로 기울어지면 왼쪽으로 움직이고 오른쪽으로 기울어지면 오른쪽으로 이동해야 합니다. 다음 코드는 같은 신경망이지만 타깃 확률 `y`와 훈련 연산(`cross_entropy`, `optimizer`, `training_op`)을 추가했습니다:"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"\n",
"reset_graph()\n",
"\n",
"n_inputs = 4\n",
"n_hidden = 4\n",
"n_outputs = 1\n",
"\n",
"learning_rate = 0.01\n",
"\n",
"initializer = tf.contrib.layers.variance_scaling_initializer()\n",
"\n",
"X = tf.placeholder(tf.float32, shape=[None, n_inputs])\n",
"y = tf.placeholder(tf.float32, shape=[None, n_outputs])\n",
"\n",
"hidden = tf.layers.dense(X, n_hidden, activation=tf.nn.elu, kernel_initializer=initializer)\n",
"logits = tf.layers.dense(hidden, n_outputs)\n",
"outputs = tf.nn.sigmoid(logits) # 행동 0(왼쪽)에 대한 확률\n",
"p_left_and_right = tf.concat(axis=1, values=[outputs, 1 - outputs])\n",
"action = tf.multinomial(tf.log(p_left_and_right), num_samples=1)\n",
"\n",
"cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits)\n",
"optimizer = tf.train.AdamOptimizer(learning_rate)\n",
"training_op = optimizer.minimize(cross_entropy)\n",
"\n",
"init = tf.global_variables_initializer()\n",
"saver = tf.train.Saver()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"동일한 네트워크를 동시에 10개의 다른 환경에서 플레이하고 1,000번 반복동안 훈련시키겠습니다. 완료되면 환경을 리셋합니다."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n"
]
}
],
"source": [
"n_environments = 10\n",
"n_iterations = 1000\n",
"\n",
"envs = [gym.make(\"CartPole-v0\") for _ in range(n_environments)]\n",
"observations = [env.reset() for env in envs]\n",
"\n",
"with tf.Session() as sess:\n",
" init.run()\n",
" for iteration in range(n_iterations):\n",
" target_probas = np.array([([1.] if obs[2] < 0 else [0.]) for obs in observations]) # angle<0 이면 proba(left)=1. 이 되어야 하고 그렇지 않으면 proba(left)=0. 이 되어야 합니다\n",
" action_val, _ = sess.run([action, training_op], feed_dict={X: np.array(observations), y: target_probas})\n",
" for env_index, env in enumerate(envs):\n",
" obs, reward, done, info = env.step(action_val[env_index][0])\n",
" observations[env_index] = obs if not done else env.reset()\n",
" saver.save(sess, \"./my_policy_net_basic.ckpt\")\n",
"\n",
"for env in envs:\n",
" env.close()"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [],
"source": [
"def render_policy_net(model_path, action, X, n_max_steps = 1000):\n",
" frames = []\n",
" env = gym.make(\"CartPole-v0\")\n",
" obs = env.reset()\n",
" with tf.Session() as sess:\n",
" saver.restore(sess, model_path)\n",
" for step in range(n_max_steps):\n",
" img = render_cart_pole(env, obs)\n",
" frames.append(img)\n",
" action_val = action.eval(feed_dict={X: obs.reshape(1, n_inputs)})\n",
" obs, reward, done, info = env.step(action_val[0][0])\n",
" if done:\n",
" break\n",
" env.close()\n",
" return frames "
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"INFO:tensorflow:Restoring parameters from ./my_policy_net_basic.ckpt\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d0ZlkR5WFUbUbuIHcwA3JDH0yQ3IDN4QbuNF8KWjoRlWqm5k394m4e80rlXki1omH+ae6hk+fP3/+/J3/IUCAAAECBAgQIECAQEDgkwAJKBtBgAABAgQIECBAgMDvAgLEQyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQIECAAAECBGICAiRGbRABAgQIECBAgAABAgLEGyBAgAABAgQIECBAICYgQGLUBhEgQIAAAQIECBAgIEC8AQIECBAgQIAAAQIEYgICJEZtEAECBAgQIECAAAECAsQbIECAAAECBAgQIEAgJiBAYtQGESBAgAABAgQIECAgQLwBAgQIECBAgAABAgRiAgIkRm0QAQIECBAgQIAAAQICxBsgQIAAAQIECBAgQCAmIEBi1AYRIECAAAECBAgQICBAvAECBAgQ2ELgH7/++N9z/vWHX7Y4s0MSIECAwB8FBIhXQYAAAQJbCHwdIG8dWJRssUaHJECAwHcCxCMgQIAAgS0EPgqQL5cQIlus0yEJECgWECDFy3d1AgQI7CQgQHbalrMSIEDgfQEB4nUQIECAwPICR+PjdhG/AVl+nQ5IgEC5gAApfwCuT4AAgR0EjgaI+Nhhm85IgEC7gABpfwHuT4AAgQ0EBMgGS3JEAgQIHBQQIAeh/BgBAgQIzAkIkDl7kwkQIHC2gAA5W9T3ESBAgMDpAgLkdFJfSIAAgTEBATJGbzABAgQIfCRwNDy+fI+/AflI1H9OgACBeQEBMr8DJyBAgACBdwTuCRDx4RkRIEBgDwEBsseenJIAAQKVAgKkcu0uTYDAxQUEyMUX7HoECBDYWUCA7Lw9ZydAgMDbAgLEyyBAgACBZQUEyLKrcTACBAg8LCBAHqbzQQIECBB4tYAAebWw7ydAgEBeQIDkzU0kQIAAgQMC98TH7ev8EfoBVD9CgACBBQQEyAJLcAQCBAgQ+KPAPQEiPrwgAgQI7CMgQPbZlZMSIECgSkCAVK3bZQkQKBIQIEXLdlUCBAjsJCBAdtqWsxIgQOC4gAA5buUnCRAgQCAoIECC2EYRIEAgKCBAgthGESBAgMAxgXvi4/aN/gbkmKufIkCAwAoCAmSFLTgDAQIECHwjcE+AiA+PhwABAnsJCJC99uW0BAgQqBAQIBVrdkkCBEoFBEjp4l2bAAECKwsIkJW342wECBB4TkCAPOfn0wQIECDwAgEB8gJUX0mAAIFFBATIIotwDAIECBD4t8A98XH7eX8D4uUQIEBgLwEBste+nJYAAQKXF7gnQMTH5Z+DCxIgcEEBAXLBpboSAQIEdhYQIDtvz9kJECDwsYAA+djITxAgQIBAUECABLGNIkCAwICAABlAN5IAAQIE3hcQIF4HAQIEri0gQK69X7cjQIDAVgL3xMftYv4GZKv1OiwBAgR+FxAgHgIBAgQILCNwT4CIj2XW5iAECBC4S0CA3MXlhwkQIEDglQIC5JW6vpsAAQJrCAiQNfbgFAQIECBw538HiN+AeDIECBDYU0CA7Lk3pyZAgMAlBfwG5JJrdSkCBAh8IyBAPAgCBAgQWELgnvi4HdhvQJZYm0MQIEDgbgEBcjeZDxAgQIDAKwTuCRDx8YoN+E4CBAhkBARIxtkUAgQIEPhAQIB4IgQIEOgQECAde3ZLAgQILC8gQJZfkQMSIEDgFAEBcgqjLyFAgACBZwUEyLOCPk+AAIE9BATIHntySgIECFxa4J74uEH4G5BLPweXI0Dg4gIC5OILdj0CBAjsIHBPgIiPHTbqjAQIEHhfQIB4HQQIECAwLiBAxlfgAAQIEIgJCJAYtUEECBAg8J6AAPE2CBAg0CMgQHp27aYECBBYVkCALLsaByNAgMDpAgLkdFJfSIAAAQL3CgiQe8X8PAECBPYVECD77s7JCRAgcAmBe+LjdmF/hH6JtbsEAQLFAgKkePmuToAAgRUE7gkQ8bHCxpyBAAECzwkIkOf8fJoAAQIEnhQQIE8C+jgBAgQ2ExAgmy3McQkQIHA1AQFytY26DwECBP5cQIB4IQQIECAwKiBARvkNJ0CAQFxAgMTJDSRAgACBLwL3xMftM/4GxNshQIDA/gICZP8dugEBAgS2FbgnQMTHtmt2cAIECHwjIEA8CAIECBAYExAgY/QGEyBAYExAgIzRG0yAAAECAsQbIECAQJ+AAOnbuRsTIEBgGQEBsswqHIQAAQIxAQESozaIAAECBL4WuCc+bp/zNyDeDwECBK4hIECusUe3IECAwHYC9wSI+NhuvQ5MgACBdwUEiMdBgAABAiMCAmSE3VACBAiMCwiQ8RU4AAECBDoFBEjn3t2aAAECAsQbIECAAIERAQEywm4oAQIExgUEyPgKHIAAAQJ9AvfEx03H34D0vRE3JkDgugIC5Lq7dTMCBAgsK3A0QITHsit0MAIECDwsIEAepvNBAgQIEHhUQIA8KudzBAgQ2F9AgOy/QzcgQIDAdgICZLuVOTABAgROExAgp1H6IgIECBA4KiBAjkr5OQIECFxPQIBcb6duRIAAgaUFjsbH7RL+BmTpVTocAQIEHhIQIA+x+RABAgQIPCpwNEDEx6PCPkeAAIG1BQTI2vtxOgIECFxOQIBcbqUuRIAAgbsEBMhdXH6YAAECBJ4VECDPCvo8AQIE9hYQIHvvz+kJECCwnYAA2W5lDkyAAIFTBQTIqZy+jAABAgQ+EhAgHwn5zwkQIHBtAQFy7f26HQECBJYSOBoft0P7I/SlVucwBAgQOE1AgJxG6YsIECBA4COBowEiPj6S9J8TIEBgXwEBsu/unJwAAQLbCQiQ7VbmwAQIEDhdQICcTuoLCRAgQOA9AQHibRAgQICAAPEGCBAgQCAmIEBi1AYRIEBgWQEBsuxqHIwAAQLXEjgaH7db+xuQa+3ebQgQIPC1gADxHggQIEAgInA0QMRHZB2GECBAYExAgIzRG0yAAIEuAQHStW+3JUCAwHsCAsTbIECAAIGIgACJMBtCgACB5QUEyPIrckACBAhcQ0CAXGOPbkGAAIFnBQTIs4I+T4AAAQIfChyNj9sX+RuQDzn9AAECBLYWECBbr8/hCRAgsIfA0QARH3vs0ykJECDwjIAAeUbPZwkQIEDgkIAAOcTkhwgQIFAhIEAq1uySBAgQmBUQILP+phMgQGAlAQGy0jachQABAhcVECAXXaxrESBA4AEBAfIAmo8QIECAwHGBo/Fx+0Z/A3Lc1U8SIEBgVwEBsuvmnJsAAQKbCBwNEPGxyUIdkwABAk8KCJAnAX2cAAECBP5cQIB4IQQIECDwtYAA8R4IECBA4KUCAuSlvL6cAAEC2wkIkO1W5sAECBDYS0CA7LUvpyVAgMCrBQTIq4V9PwECBIoFjsbHjcjfgBQ/FFcnQKBKQIBUrdtlCRAgkBU4GiDiI7sX0wgQIDApIEAm9c0mQIDAxQUEyMUX7HoECBB4QECAPIDmIwQIECBwTECAHHPyUwQIEGgSECBN23ZXAgQIBAWOxsftSP4JVnAxRhEgQGBYQIAML8B4AgQIXFXgaICIj6u+APciQIDA2wICxMsgQIAAgZcICJCXsPpSAgQIbC8gQLZfoQsQIEBgTQEBsuZenIoAAQLTAgJkegPmEyBA4KICAuSii3UtAgQIPCkgQJ4E9HECBAgQeFtAgHgZBAgQIPCWgADxLggQIEDgdIGj8XEb7I/QT+f3hQQIEFhaQIAsvR6HI0CAwJ4CRwNEfOy5X6cmQIDAMwIC5Bk9nyVAgACBNwUEiIdBgAABAu8JCBBvgwABAgROFxAgp5P6QgIECFxGQIBcZpUuQoAAgXUEBMg6u3ASAgQIrCYgQFbbiPMQIEBgc4Gj8XG7pr8B2XzZjk+AAIEHBATIA2g+QoAAAQLvCxwNEPHhFREgQKBTQIB07t2tCRAg8DIBAfIyWl9MgACBSwgIkEus0SUIECCwjoAAWWcXTkKAAIEVBQTIiltxJgIECGwsIEA2Xp6jEyBAICAgQALIRhAgQKBF4Gh83Dz8DUjLq3BPAgQIfCsgQLwIAgQIEDhN4GiAiI/TyH0RAQIEthMQINutzIEJECCwroAAWXc3TkaAAIFVBATIKptwDgIECAQFPn369JJpv/3yw6Hv/f7HXw/93NEf+vz589Ef9XMECBAgMCwgQIYXYDwBAgQmBATIhLqZBAgQIHATECDeAQECBAoFXhEgR3/7ceP2G5DCR+fKBAgQ+I+AAPEUCBAgUCjwygD5+z//+M+w/vaX//2Tq7Pj47Y+/wSr8BG7MgEC2woIkG1X5+AECBB4XOBVAfJWfHx9yluICJDH9+aTBAgQuIKAALnCFt2BAAECdwq8IkB++um3D08hQD4k8gMECBC4vIAAufyKXZAAAQJ/FBAgXgUBAgQITAkIkCl5cwkQIDAocHaA3P4A/aN/fvXluj///P3pN/c3IKeT+kICBAi8TECAvIzWFxMgQGBdgakAeUV83JQFyLpvzckIECDw/wICxJsgQIBAoYAAKVy6KxMgQGARAQGyyCIcgwABAkmBVwTI7fwf/TMsvwFJbtksAgQIrCkgQNbci1MRIEDgpQKvCpD3IuTLfw/IK/5f8N5m+idYL30uvpwAAQKnCgiQUzl9GQECBPYQODtA3rr1W//N6AJkj/fhlAQIEHilgAB5pa7vJkCAwKICiQBJXt1vQJLaZhEgQOA5AQHynJ9PEyBAYEsBAbLl2hyaAAEClxAQIIut8Wr/S8FivI5DgAABAgQIEHhawG9dnyMUIM/5nf5pAXI6qS8kQIAAAQIECJwqIECe4xQgz/n5NAECBLYUuNr/scP/MrDlM3RoAgRKBQRI6eJdmwCBbgEB0r1/tydAgMCkgACZ1DebAAECQwICZAjeWAIECBD4ToB4BAQIECgUECCFS3dlAgQILCIgQBZZhGMQIEAgKSBAktpmESBAgMDXAgLEeyBAgEChgAApXLorEyBAYBEBAbLIIhyDAAECSQEBktQ2iwABAgT8BsQbIECAQLmAACl/AK5PgACBQQG/ARnEN5oAAQJTAgJkSt5cAgQIEBAg3gABAgQKBQRI4dJdmQABAosICJBFFuEYBAgQSAoIkKS2WQQIECDwtYAA8R4IECBQKCBACpfuygQIEFhEQIAssgjHIECAQFJAgCS1zSJAgAABvwHxBggQIFAuIEDKH4DrEyBAYFDAb0AG8Y0mQIAAAQIECBAg0CYgQNo27r4ECBAgQIAAAQIEBgUEyCC+0QQIECBAgAABAgTaBARI28bdlwABAgQIECBAgMCggAAZxDeaABqj+2UAAAvESURBVAECBAgQIECAQJuAAGnbuPsSIECAAAECBAgQGBQQIIP4RhMgQIAAAQIECBBoExAgbRt3XwIECBAgQIAAAQKDAgJkEN9oAgQIECBAgAABAm0CAqRt4+5LgAABAgQIECBAYFBAgAziG02AAAECBAgQIECgTUCAtG3cfQkQIECAAAECBAgMCgiQQXyjCRAgQIAAAQIECLQJCJC2jbsvAQIECBAgQIAAgUEBATKIbzQBAgQIECBAgACBNgEB0rZx9yVAgAABAgQIECAwKCBABvGNJkCAAAECBAgQINAmIEDaNu6+BAgQIECAAAECBAYFBMggvtEECBAgQIAAAQIE2gQESNvG3ZcAAQIECBAgQIDAoIAAGcQ3mgABAgQIECBAgECbgABp27j7EiBAgAABAgQIEBgUECCD+EYTIECAAAECBAgQaBMQIG0bd18CBAgQIECAAAECgwICZBDfaAIECBAgQIAAAQJtAgKkbePuS4AAAQIECBAgQGBQQIAM4htNgAABAgQIECBAoE1AgLRt3H0JECBAgAABAgQIDAoIkEF8owkQIECAAAECBAi0CQiQto27LwECBAgQIECAAIFBAQEyiG80AQIECBAgQIAAgTYBAdK2cfclQIAAAQIECBAgMCggQAbxjSZAgAABAgQIECDQJiBA2jbuvgQIECBAgAABAgQGBQTIIL7RBAgQIECAAAECBNoEBEjbxt2XAAECBAgQIECAwKCAABnEN5oAAQIECBAgQIBAm4AAadu4+xIgQIAAAQIECBAYFBAgg/hGEyBAgAABAgQIEGgTECBtG3dfAgQIECBAgAABAoMCAmQQ32gCBAgQIECAAAECbQICpG3j7kuAAAECBAgQIEBgUECADOIbTYAAAQIECBAgQKBNQIC0bdx9CRAgQIAAAQIECAwKCJBBfKMJECBAgAABAgQItAkIkLaNuy8BAgQIECBAgACBQQEBMohvNAECBAgQIECAAIE2AQHStnH3JUCAAAECBAgQIDAoIEAG8Y0mQIAAAQIECBAg0CYgQNo27r4ECBAgQIAAAQIEBgUEyCC+0QQIECBAgAABAgTaBARI28bdlwABAgQIECBAgMCggAAZxDeaAAECBAgQIECAQJuAAGnbuPsSIECAAAECBAgQGBQQIIP4RhMgQIAAAQIECBBoExAgbRt3XwIECBAgQIAAAQKDAgJkEN9oAgQIECBAgAABAm0CAqRt4+5LgAABAgQIECBAYFBAgAziG02AAAECBAgQIECgTUCAtG3cfQkQIECAAAECBAgMCgiQQXyjCRAgQIAAAQIECLQJCJC2jbsvAQIECBAgQIAAgUEBATKIbzQBAgQIECBAgACBNgEB0rZx9yVAgAABAgQIECAwKCBABvGNJkCAAAECBAgQINAmIEDaNu6+BAgQIECAAAECBAYFBMggvtEECBAgQIAAAQIE2gQESNvG3ZcAAQIECBAgQIDAoIAAGcQ3mgABAgQIECBAgECbgABp27j7EiBAgAABAgQIEBgUECCD+EYTIECAAAECBAgQaBMQIG0bd18CBAgQIECAAAECgwICZBDfaAIECBAgQIAAAQJtAgKkbePuS4AAAQIECBAgQGBQQIAM4htNgAABAgQIECBAoE1AgLRt3H0JECBAgAABAgQIDAoIkEF8owkQIECAAAECBAi0CQiQto27LwECBAgQIECAAIFBAQEyiG80AQIECBAgQIAAgTYBAdK2cfclQIAAAQIECBAgMCggQAbxjSZAgAABAgQIECDQJiBA2jbuvgQIECBAgAABAgQGBQTIIL7RBAgQIECAAAECBNoEBEjbxt2XAAECBAgQIECAwKCAABnEN5oAAQIECBAgQIBAm4AAadu4+xIgQIAAAQIECBAYFBAgg/hGEyBAgAABAgQIEGgTECBtG3dfAgQIECBAgAABAoMCAmQQ32gCBAgQIECAAAECbQICpG3j7kuAAAECBAgQIEBgUECADOIbTYAAAQIECBAgQKBNQIC0bdx9CRAgQIAAAQIECAwKCJBBfKMJECBAgAABAgQItAkIkLaNuy8BAgQIECBAgACBQQEBMohvNAECBAgQIECAAIE2AQHStnH3JUCAAAECBAgQIDAoIEAG8Y0mQIAAAQIECBAg0CYgQNo27r4ECBAgQIAAAQIEBgUEyCC+0QQIECBAgAABAgTaBARI28bdlwABAgQIECBAgMCggAAZxDeaAAECBAgQIECAQJuAAGnbuPsSIECAAAECBAgQGBQQIIP4RhMgQIAAAQIECBBoExAgbRt3XwIECBAgQIAAAQKDAgJkEN9oAgQIECBAgAABAm0CAqRt4+5LgAABAgQIECBAYFBAgAziG02AAAECBAgQIECgTUCAtG3cfQkQIECAAAECBAgMCgiQQXyjCRAgQIAAAQIECLQJCJC2jbsvAQIECBAgQIAAgUEBATKIbzQBAgQIECBAgACBNgEB0rZx9yVAgAABAgQIECAwKCBABvGNJkCAAAECBAgQINAmIEDaNu6+BAgQIECAAAECBAYFBMggvtEECBAgQIAAAQIE2gQESNvG3ZcAAQIECBAgQIDAoIAAGcQ3mgABAgQIECBAgECbgABp27j7EiBAgAABAgQIEBgUECCD+EYTIECAAAECBAgQaBMQIG0bd18CBAgQIECAAAECgwICZBDfaAIECBAgQIAAAQJtAgKkbePuS4AAAQIECBAgQGBQQIAM4htNgAABAgQIECBAoE1AgLRt3H0JECBAgAABAgQIDAoIkEF8owkQIECAAAECBAi0CQiQto27LwECBAgQIECAAIFBAQEyiG80AQIECBAgQIAAgTYBAdK2cfclQIAAAQIECBAgMCggQAbxjSZAgAABAgQIECDQJiBA2jbuvgQIECBAgAABAgQGBQTIIL7RBAgQIECAAAECBNoEBEjbxt2XAAECBAgQIECAwKCAABnEN5oAAQIECBAgQIBAm4AAadu4+xIgQIAAAQIECBAYFBAgg/hGEyBAgAABAgQIEGgTECBtG3dfAgQIECBAgAABAoMCAmQQ32gCBAgQIECAAAECbQICpG3j7kuAAAECBAgQIEBgUECADOIbTYAAAQIECBAgQKBNQIC0bdx9CRAgQIAAAQIECAwKCJBBfKMJECBAgAABAgQItAkIkLaNuy8BAgQIECBAgACBQQEBMohvNAECBAgQIECAAIE2AQHStnH3JUCAAAECBAgQIDAoIEAG8Y0mQIAAAQIECBAg0CYgQNo27r4ECBAgQIAAAQIEBgUEyCC+0QQIECBAgAABAgTaBARI28bdlwABAgQIECBAgMCggAAZxDeaAAECBAgQIECAQJuAAGnbuPsSIECAAAECBAgQGBQQIIP4RhMgQIAAAQIECBBoExAgbRt3XwIECBAgQIAAAQKDAgJkEN9oAgQIECBAgAABAm0CAqRt4+5LgAABAgQIECBAYFBAgAziG02AAAECBAgQIECgTUCAtG3cfQkQIECAAAECBAgMCgiQQXyjCRAgQIAAAQIECLQJCJC2jbsvAQIECBAgQIAAgUEBATKIbzQBAgQIECBAgACBNgEB0rZx9yVAgAABAgQIECAwKCBABvGNJkCAAAECBAgQINAmIEDaNu6+BAgQIECAAAECBAYFBMggvtEECBAgQIAAAQIE2gQESNvG3ZcAAQIECBAgQIDAoIAAGcQ3mgABAgQIECBAgECbgABp27j7EiBAgAABAgQIEBgUECCD+EYTIECAAAECBAgQaBMQIG0bd18CBAgQIECAAAECgwICZBDfaAIECBAgQIAAAQJtAgKkbePuS4AAAQIECBAgQGBQQIAM4htNgAABAgQIECBAoE1AgLRt3H0JECBAgAABAgQIDAoIkEF8owkQIECAAAECBAi0CQiQto27LwECBAgQIECAAIFBAQEyiG80AQIECBAgQIAAgTYBAdK2cfclQIAAAQIECBAgMCggQAbxjSZAgAABAgQIECDQJiBA2jbuvgQIECBAgAABAgQGBf4FfCBJnjem4PkAAAAASUVORK5CYII=\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"frames = render_policy_net(\"./my_policy_net_basic.ckpt\", action, X)\n",
"video = plot_animation(frames)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"정책을 잘 학습한 것 같네요. 이제 스스로 더 나은 정책을 학습할 수 있는지 알아 보겠습니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 정책 그래디언트"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"신경망을 훈련하기 위해 타깃 확률 `y`를 정의할 필요가 있습니다. 행동이 좋다면 이 확률을 증가시켜야 하고 반대로 나쁘면 이를 감소시켜야 합니다. 하지만 행동이 좋은지 나쁜지 어떻게 알 수 있을까요? 대부분의 행동으로 인한 영향은 뒤늦게 나타나는 것이 문제입니다. 게임에서 이기거나 질 때 어떤 행동이 이런 결과에 영향을 미쳤는지 명확하지 않습니다. 마지막 행동일까요? 아니면 마지막 10개의 행동일까요? 아니면 50번 스텝 앞의 행동일까요? 이를 *신용 할당 문제*라고 합니다.\n",
"\n",
"*정책 그래디언트* 알고리즘은 먼저 여러번 게임을 플레이하고 성공한 게임에서의 행동을 조금 더 높게 실패한 게임에서는 조금 더 낮게 되도록 하여 이 문제를 해결합니다. 먼저 게임을 진행해 보고 다시 어떻게 한 것인지 살펴 보겠습니다."
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"\n",
"reset_graph()\n",
"\n",
"n_inputs = 4\n",
"n_hidden = 4\n",
"n_outputs = 1\n",
"\n",
"learning_rate = 0.01\n",
"\n",
"initializer = tf.contrib.layers.variance_scaling_initializer()\n",
"\n",
"X = tf.placeholder(tf.float32, shape=[None, n_inputs])\n",
"\n",
"hidden = tf.layers.dense(X, n_hidden, activation=tf.nn.elu, kernel_initializer=initializer)\n",
"logits = tf.layers.dense(hidden, n_outputs)\n",
"outputs = tf.nn.sigmoid(logits) # 행동 0(왼쪽)에 대한 확률\n",
"p_left_and_right = tf.concat(axis=1, values=[outputs, 1 - outputs])\n",
"action = tf.multinomial(tf.log(p_left_and_right), num_samples=1)\n",
"\n",
"y = 1. - tf.to_float(action)\n",
"cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits)\n",
"optimizer = tf.train.AdamOptimizer(learning_rate)\n",
"grads_and_vars = optimizer.compute_gradients(cross_entropy)\n",
"gradients = [grad for grad, variable in grads_and_vars]\n",
"gradient_placeholders = []\n",
"grads_and_vars_feed = []\n",
"for grad, variable in grads_and_vars:\n",
" gradient_placeholder = tf.placeholder(tf.float32, shape=grad.get_shape())\n",
" gradient_placeholders.append(gradient_placeholder)\n",
" grads_and_vars_feed.append((gradient_placeholder, variable))\n",
"training_op = optimizer.apply_gradients(grads_and_vars_feed)\n",
"\n",
"init = tf.global_variables_initializer()\n",
"saver = tf.train.Saver()"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [],
"source": [
"def discount_rewards(rewards, discount_rate):\n",
" discounted_rewards = np.zeros(len(rewards))\n",
" cumulative_rewards = 0\n",
" for step in reversed(range(len(rewards))):\n",
" cumulative_rewards = rewards[step] + cumulative_rewards * discount_rate\n",
" discounted_rewards[step] = cumulative_rewards\n",
" return discounted_rewards\n",
"\n",
"def discount_and_normalize_rewards(all_rewards, discount_rate):\n",
" all_discounted_rewards = [discount_rewards(rewards, discount_rate) for rewards in all_rewards]\n",
" flat_rewards = np.concatenate(all_discounted_rewards)\n",
" reward_mean = flat_rewards.mean()\n",
" reward_std = flat_rewards.std()\n",
" return [(discounted_rewards - reward_mean)/reward_std for discounted_rewards in all_discounted_rewards]"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([-22., -40., -50.])"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"discount_rewards([10, 0, -50], discount_rate=0.8)"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[array([-0.28435071, -0.86597718, -1.18910299]),\n",
" array([1.26665318, 1.0727777 ])]"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"discount_and_normalize_rewards([[10, 0, -50], [10, 20]], discount_rate=0.8)"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"반복: 249"
]
}
],
"source": [
"env = gym.make(\"CartPole-v0\")\n",
"\n",
"n_games_per_update = 10\n",
"n_max_steps = 1000\n",
"n_iterations = 250\n",
"save_iterations = 10\n",
"discount_rate = 0.95\n",
"\n",
"with tf.Session() as sess:\n",
" init.run()\n",
" for iteration in range(n_iterations):\n",
" print(\"\\r반복: {}\".format(iteration), end=\"\")\n",
" all_rewards = []\n",
" all_gradients = []\n",
" for game in range(n_games_per_update):\n",
" current_rewards = []\n",
" current_gradients = []\n",
" obs = env.reset()\n",
" for step in range(n_max_steps):\n",
" action_val, gradients_val = sess.run([action, gradients], feed_dict={X: obs.reshape(1, n_inputs)})\n",
" obs, reward, done, info = env.step(action_val[0][0])\n",
" current_rewards.append(reward)\n",
" current_gradients.append(gradients_val)\n",
" if done:\n",
" break\n",
" all_rewards.append(current_rewards)\n",
" all_gradients.append(current_gradients)\n",
"\n",
" all_rewards = discount_and_normalize_rewards(all_rewards, discount_rate=discount_rate)\n",
" feed_dict = {}\n",
" for var_index, gradient_placeholder in enumerate(gradient_placeholders):\n",
" mean_gradients = np.mean([reward * all_gradients[game_index][step][var_index]\n",
" for game_index, rewards in enumerate(all_rewards)\n",
" for step, reward in enumerate(rewards)], axis=0)\n",
" feed_dict[gradient_placeholder] = mean_gradients\n",
" sess.run(training_op, feed_dict=feed_dict)\n",
" if iteration % save_iterations == 0:\n",
" saver.save(sess, \"./my_policy_net_pg.ckpt\")"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mWARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.\u001b[0m\n",
"INFO:tensorflow:Restoring parameters from ./my_policy_net_pg.ckpt\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3d660kVxWA0TtpkIadBmmMw7AcxjgN0jBpkMZFF4RkrHnU8+uq2ovf3b3rrLMl+Kix59P7+/v7m/8QIECAAAECBAgQIEAgEPgkQAJlIwgQIECAAAECBAgQ+I+AALEIBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgQIECBAgAABAWIHCBAgQIAAAQIECBDIBARIRm0QAQIECBAgQIAAAQICxA4QIECAAAECBAgQIJAJCJCM2iACBAgQIECAAAECBASIHSBAgAABAgQIECBAIBMQIBm1QQQIECBAgAABAgQICBA7QIAAAQIECBAgQIBAJiBAMmqDCBAgQIAAAQIECBAQIHaAAAECBAgQIECAAIFMQIBk1AYRIECAAAECBAgQICBA7AABAgQIECBAgAABApmAAMmoDSJAgAABAgQIECBAQIDYAQIECBAgQIAAAQIEMgEBklEbRIAAAQIECBAgQICAALEDBAgQIECAAAECBAhkAgIkozaIAAECBAgQIECAAAEBYgcIECBAgAABAgQIEMgEBEhGbRABAgQIECBAgAABAgLEDhAgQIAAAQIECBAgkAkIkIzaIAIECBAgQIAAAQIEBIgdIECAAAECBAgQIEAgExAgGbVBBAgQIECAAAECBAgIEDtAgAABAgQIECBAgEAmIEAyaoMIECBAgAABAgQIEBAgdoAAAQIECBAgQIAAgUxAgGTUBhEgQIAAAQIECBAgIEDsAAECBAgQIECAAAECmYAAyagNIkCAAAECBAgQIEBAgNgBAgQIECBAgAABAgQyAQGSURtEgAABAgQIECBAgIAAsQMECBAgQIAAAQIECGQCAiSjNogAAQIECBAgQIAAAQFiBwgQIECAAAECBAgQyAQESEZtEAECBAgQIECAAAECAsQOECBAgAABAgQIECCQCQiQjNogAgQIECBAgAABAgQEiB0gQIAAAQIECBAgQCATECAZtUEECBAgQIAAAQIECAgQO0CAAAECBAgQIECAQCYgQDJqgwgQIECAAAECBAgQECB2gAABAgQIECBAgACBTECAZNQGESBAgAABAgQIECAgQOwAAQIECBAgQIAAAQKZgADJqA0iQIAAAQIECBAgQECA2AECBAgQIECAAAECBDIBAZJRG0SAAAECBAgQIECAgACxAwQIECBAgAABAgQIZAICJKM2iAABAgTuLPDP339Z/Pg/ff6y+LM+SIAAgWkCAmTajTsvAQIECGwWWBohAmQzsS8SIDBAQIAMuGRHJECAAIFjBATIMY5+hQCB2QICZPb9Oz0BAgQIrBAQICuwfJQAAQLfEBAgVoMAAQIECCwUECALoXyMAAEC3xEQINaDAAECBAgsFBAgC6F8jAABAgLEDhAgQIAAgf0CAmS/oV8gQICANyB2gAABAgQILBQQIAuhfIwAAQLegNgBAgQIECCwX0CA7Df0CwQIEPAGxA4QIECAAIGFAgJkIZSPESBAwBsQO0CAAAECBPYLCJD9hn6BAAEC3oDYAQIECBAgsFBgaYB8/Jy/DX0hqo8RIDBOQICMu3IHJkCAAIE9AksjRIDsUfZdAgSeLCBAnny7zkaAAAEChwsIkMNJ/SABAsMEBMiwC3dcAgQIENgnIED2+fk2AQIEBIgdIECAAAECKwQEyAosHyVAgMBXBASItSBAgAABAisEBMgKLB8lQICAALEDBAgQIEBgn4AA2efn2wQIEPAGxA4QIECAAIEVAgJkBZaPEiBAwBsQO0CAAAECBPYJCJB9fr5NgAABb0DsAAECBAgQWCEgQFZg+SgBAgS8AbEDBAgQIEBgn4AA2efn2wQIEPAGxA4QIECAAIEVAgJkBZaPEiBAwBsQO0CAAAECBPYJCJB9fr5NgAABb0DsAAECBAgQWCkgQlaC+TgBAgT+JCBArAMBAgQIEFgpIEBWgvk4AQIEBIgdIECAAAEC2wUEyHY73yRAgIA3IHaAAAECBAisFBAgK8F8nAABAt6A2AECBAgQILBdQIBst/NNAgQIeANiBwgQIECAwEoBAbISzMcJECDgDYgdIECAAAEC2wUEyHY73yRAgIA3IHaAAAECBAisFBAgK8F8nAABAt6A2AECBAgQILBdQIBst/NNAgQIeANiBwgQIECAwEoBAbISzMcJECDgDYgdIECAAAEC2wUEyHY73yRAgIA3IHaAAAECBAhsEBAhG9B8hQABAm9vbwLEGhAgQIAAgQ0CAmQDmq8QIEBAgNgBAgQIECCwTUCAbHPzLQIECHgDYgcIECBAgMAGAQGyAc1XCBAg4A2IHSBAgAABAtsEBMg2N98iQICANyB2gAABAgQIbBAQIBvQfIUAAQLegNgBAgQIECCwTUCAbHPzLQIECHgDYgcIECBAgMAGAQGyAc1XCBAg4A2IHSBAgAABAtsEBMg2N98iQICANyB2gAABAgQIbBAQIBvQfIUAAQLegNgBAgQIECCwTUCAbHPzLQIECHgDYgcIECBAgMAGAQGyAc1XCBAg4A2IHSBAgAABAtsFRMh2O98kQGCugDcgc+/eyQkQIEBgp4AA2Qno6wQIjBQQICOv3aEJECBA4AgBAXKEot8gQGCagACZduPOS4AAAQKHCQiQwyj9EAECgwQEyKDLdlQCBAgQOFZAgBzr6dcIEJghIEBm3LNTEiBAgMAJAgLkBFQ/SYDA4wUEyOOv2AEJECBA4CwBAXKWrN8lQODJAgLkybfrbAQIECBwqoAAOZXXjxMg8FABAfLQi3UsAgQIEDhfQICcb2wCAQLPExAgz7tTJyJAgACBSECARNDGECDwKAEB8qjrdBgCBAgQKAUESKltFgECTxEQIE+5SecgQIAAgVxgaYB8PNhPn7/kz2cgAQIEriggQK54K56JAAECBG4jsDRCBMhtrtSDEiBwsoAAORnYzxMgQIDAswUEyLPv1+kIEDheQIAcb+oXCRAgQGCQgAAZdNmOSoDAIQIC5BBGP0KAAAECUwUEyNSbd24CBLYKCJCtcr5HgAABAgTe3t4EiDUgQIDAOgEBss7LpwkQIECAwP8JCBALQYAAgXUCAmSdl08TIECAAAEBYgcIECCwQ0CA7MDzVQIECBAg4A2IHSBAgMA6AQGyzsunCRAgQICANyB2gAABAjsEBMgOPF8lQIAAAQLegNgBAgQIrBMQIOu8fJoAAQIECGx6A/LxJX8buuUhQIDA25sAsQUECBAgQGCngLcgOwF9nQCBUQICZNR1OywBAgQInCEgQM5Q9ZsECDxVQIA89WadiwABAgQyAQGSURtEgMADBATIAy7REQgQIEDgtQIC5LX+phMgcC8BAXKv+/K0BAgQIHBBAQFywUvxSAQIXFZAgFz2ajwYAQIECNxFQIDc5aY8JwECVxAQIFe4Bc9AgAABArcWECC3vj4PT4BALCBAYnDjCBAgQOB5AgLkeXfqRAQInCcgQM6z9csECBAgMERAgAy5aMckQOAQAQFyCKMfIUCAAIHJAgJk8u07OwECawUEyFoxnydAgAABAn8RECBWggABAssFBMhyK58kQIAAAQJfFVgaIB9f/unzF4oECBAYLSBARl+/wxMgQIDAUQJLI0SAHCXudwgQuKuAALnrzXluAgQIELiUgAC51HV4GAIELiwgQC58OR6NAAECBO4jIEDuc1eelACB1woIkNf6m06AAAECDxEQIA+5SMcgQOB0AQFyOrEBBAgQIDBBQIBMuGVnJEDgCAEBcoSi3yBAgACB8QICZPwKACBAYKGAAFkI5WMECBAgQOB7AgLEfhAgQGCZgABZ5uRTBAgQIEDguwICxIIQIEBgmYAAWebkUwQIECBAQIDYAQIECBwgIEAOQPQTBAgQIEDAGxA7QIAAgWUCAmSZk08RIECAAAFvQOwAAQIEDhAQIAcg+gkCBAgQIPAh4C2IPSBAgMCPBQTIj418ggABAgQILBIQIIuYfIgAgeECAmT4Ajg+AQIECBwnIECOs/RLBAg8V0CAPPdunYwAAQIEYgEBEoMbR4DALQUEyC2vzUMTIECAwBUFBMgVb8UzESBwNQEBcrUb8TwECBAgcFsBAXLbq/PgBAiEAgIkxDaKAAECBJ4tIECefb9OR4DAMQIC5BhHv0KAAAECBPxreO0AAQIEFggIkAVIPkKAAAECBJYIeAOyRMlnCBCYLiBApm+A8xMgQIDAYQIC5DBKP0SAwIMFBMiDL9fRCBAgQKAVECCtt2kECNxTQIDc8948NQECBAhcUECAXPBSPBIBApcTECCXuxIPRIAAAQJ3FhAhd749z06AQCEgQAplMwgQIEBgjIAAGXPVDkqAwEYBAbIRztcIECBAgMDXBASIvSBAgMD3BQSIDSFAgAABAgcKCJADMf0UAQKPFBAgj7xWhyJAgACBVwkIkFfJm0uAwF0EBMhdbspzEiBAgMAtBATILa7JQxIg8EIBAfJCfKMJECBA4HkCAuR5d+pEBAgcKyBAjvX0awQIECAwXECADF8AxydA4IcCAuSHRD5AgAABAgSWCwiQ5VY+SYDATAEBMvPenZoAAQIEThIQICfB+lkCBB4jIEAec5UOQoAAAQJXEBAgV7gFz0CAwJUFBMiVb8ezESBAgMDpAp8+fTp8xh9fPi/6zZ9/+X3R59Z86P39fc3HfZYAAQK5gADJyQ0kQIAAgSsJCJAr3YZnIUBggoAAmXDLzkiAAAEC3xQQIJaDAAECrYAAab1NI0CAAIGLCZwZIP/419f/KNbf//bfP3rlj2BdbBk8DgECiYAASZgNIUCAAIGrCpwVIN+Kj/85fESIALnqVnguAgTOFBAgZ+r6bQIECBC4vMCrAuQD5rfffj7cxz+EfjipHyRA4GABAXIwqJ8jQIAAgXsJnBEgv/76xyIEAbKIyYcIEHiYgAB52IU6DgECBAisExAg67x8mgABAnsFBMheQd8nQIAAgVsLCJBbX5+HJ0DghgIC5IaX5pEJECBA4DiBMwLk4y8i/NE/hP5xAn8E67h79EsECNxHQIDc5648KQECBAicIPCqAPFvwTrhMv0kAQK3EBAgt7gmD0mAAAECZwmcFSAfz/ujvwfk4zNH/6t4/VuwztoUv0uAwFECAuQoSb9DgAABArcUOCNAPiA+/hjWn/9zdGh8C1uA3HINPTSBUQICZNR1OywBAgQI/FXgrAB5lbQAeZW8uQQILBUQIEulfI4AAQIEHikgQB55rQ5FgMCFBQTIxS7naf9FeDFej0OAAAECBAgQ2C3gTeM+QgGyz+/wbwuQw0n9IAECBAgQIEDgUAEBso9TgOzz820CBAgQuLnA0/6PH//D6OYL6fEJDBAQIAMu2REJECBA4NsCAsR2ECBAoBUQIK23aQQIECBwMQEBcrEL8TgECDxeQIA8/oodkAABAgS+JyBA7AcBAgRaAQHSeptGgAABAhcTECAXuxCPQ4DA4wUEyOOv2AEJECBAwBsQO0CAAIHrCAiQ69yFJyFAgACBFwh4A/ICdCMJEBgtIEBGX7/DEyBAgIAAsQMECBBoBQRI620aAQIECFxMQIBc7EI8DgECjxcQII+/YgckQIAAge8JCBD7QYAAgVZAgLTephEgQIDAxQQEyMUuxOMQIPB4AQHy+Ct2QAIECBDwBsQOECBA4DoCAuQ6d+FJCBAgQOAFAt6AvADdSAIERgsIkNHX7/AECBAgIEDsAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAL+VeI4AAAqwSURBVAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFRAgrbdpBAgQIECAAAECBEYLCJDR1+/wBAgQIECAAAECBFoBAdJ6m0aAAAECBAgQIEBgtIAAGX39Dk+AAAECBAgQIECgFfg3sJlJnjwJG+kAAAAASUVORK5CYII=\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"frames = render_policy_net(\"./my_policy_net_pg.ckpt\", action, X, n_max_steps=1000)\n",
"video = plot_animation(frames)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 마르코프 연쇄"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"상태: 0 0 3 \n",
"상태: 0 1 2 1 2 1 2 1 2 1 3 \n",
"상태: 0 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 3 \n",
"상태: 0 3 \n",
"상태: 0 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 3 \n",
"상태: 0 1 3 \n",
"상태: 0 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 ...\n",
"상태: 0 0 3 \n",
"상태: 0 0 0 1 2 1 2 1 3 \n",
"상태: 0 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 3 \n"
]
}
],
"source": [
"transition_probabilities = [\n",
" [0.7, 0.2, 0.0, 0.1], # s0에서 s0, s1, s2, s3으로\n",
" [0.0, 0.0, 0.9, 0.1], # s1에서 ...\n",
" [0.0, 1.0, 0.0, 0.0], # s2에서 ...\n",
" [0.0, 0.0, 0.0, 1.0], # s3에서 ...\n",
" ]\n",
"\n",
"n_max_steps = 50\n",
"\n",
"def print_sequence(start_state=0):\n",
" current_state = start_state\n",
" print(\"상태:\", end=\" \")\n",
" for step in range(n_max_steps):\n",
" print(current_state, end=\" \")\n",
" if current_state == 3:\n",
" break\n",
" current_state = np.random.choice(range(4), p=transition_probabilities[current_state])\n",
" else:\n",
" print(\"...\", end=\"\")\n",
" print()\n",
"\n",
"for _ in range(10):\n",
" print_sequence()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 마르코프 결정 과정"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"policy_fire\n",
"상태 (+보상): 0 (10) 0 (10) 0 1 (-50) 2 2 2 (40) 0 (10) 0 (10) 0 (10) ... 전체 보상 = 210\n",
"상태 (+보상): 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 1 (-50) 2 2 (40) 0 (10) ... 전체 보상 = 70\n",
"상태 (+보상): 0 (10) 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) ... 전체 보상 = 70\n",
"상태 (+보상): 0 1 (-50) 2 1 (-50) 2 (40) 0 (10) 0 1 (-50) 2 (40) 0 ... 전체 보상 = -10\n",
"상태 (+보상): 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 1 (-50) 2 (40) 0 (10) 0 (10) ... 전체 보상 = 290\n",
"요약: 평균=121.1, 표준 편차=129.333766, 최소=-330, 최대=470\n",
"\n",
"policy_random\n",
"상태 (+보상): 0 1 (-50) 2 1 (-50) 2 (40) 0 1 (-50) 2 2 (40) 0 ... 전체 보상 = -60\n",
"상태 (+보상): 0 (10) 0 0 0 0 0 (10) 0 0 0 (10) 0 ... 전체 보상 = -30\n",
"상태 (+보상): 0 1 1 (-50) 2 (40) 0 0 1 1 1 1 ... 전체 보상 = 10\n",
"상태 (+보상): 0 (10) 0 (10) 0 0 0 0 1 (-50) 2 (40) 0 0 ... 전체 보상 = 0\n",
"상태 (+보상): 0 0 (10) 0 1 (-50) 2 (40) 0 0 0 0 (10) 0 (10) ... 전체 보상 = 40\n",
"요약: 평균=-22.1, 표준 편차=88.152740, 최소=-380, 최대=200\n",
"\n",
"policy_safe\n",
"상태 (+보상): 0 1 1 1 1 1 1 1 1 1 ... 전체 보상 = 0\n",
"상태 (+보상): 0 1 1 1 1 1 1 1 1 1 ... 전체 보상 = 0\n",
"상태 (+보상): 0 (10) 0 (10) 0 (10) 0 1 1 1 1 1 1 ... 전체 보상 = 30\n",
"상태 (+보상): 0 (10) 0 1 1 1 1 1 1 1 1 ... 전체 보상 = 10\n",
"상태 (+보상): 0 1 1 1 1 1 1 1 1 1 ... 전체 보상 = 0\n",
"요약: 평균=22.3, 표준 편차=26.244312, 최소=0, 최대=170\n",
"\n"
]
}
],
"source": [
"transition_probabilities = [\n",
" [[0.7, 0.3, 0.0], [1.0, 0.0, 0.0], [0.8, 0.2, 0.0]], # s0에서, 행동 a0이 선택되면 0.7의 확률로 상태 s0로 가고 0.3의 확률로 상태 s1로 가는 식입니다.\n",
" [[0.0, 1.0, 0.0], None, [0.0, 0.0, 1.0]],\n",
" [None, [0.8, 0.1, 0.1], None],\n",
" ]\n",
"\n",
"rewards = [\n",
" [[+10, 0, 0], [0, 0, 0], [0, 0, 0]],\n",
" [[0, 0, 0], [0, 0, 0], [0, 0, -50]],\n",
" [[0, 0, 0], [+40, 0, 0], [0, 0, 0]],\n",
" ]\n",
"\n",
"possible_actions = [[0, 1, 2], [0, 2], [1]]\n",
"\n",
"def policy_fire(state):\n",
" return [0, 2, 1][state]\n",
"\n",
"def policy_random(state):\n",
" return np.random.choice(possible_actions[state])\n",
"\n",
"def policy_safe(state):\n",
" return [0, 0, 1][state]\n",
"\n",
"class MDPEnvironment(object):\n",
" def __init__(self, start_state=0):\n",
" self.start_state=start_state\n",
" self.reset()\n",
" def reset(self):\n",
" self.total_rewards = 0\n",
" self.state = self.start_state\n",
" def step(self, action):\n",
" next_state = np.random.choice(range(3), p=transition_probabilities[self.state][action])\n",
" reward = rewards[self.state][action][next_state]\n",
" self.state = next_state\n",
" self.total_rewards += reward\n",
" return self.state, reward\n",
"\n",
"def run_episode(policy, n_steps, start_state=0, display=True):\n",
" env = MDPEnvironment()\n",
" if display:\n",
" print(\"상태 (+보상):\", end=\" \")\n",
" for step in range(n_steps):\n",
" if display:\n",
" if step == 10:\n",
" print(\"...\", end=\" \")\n",
" elif step < 10:\n",
" print(env.state, end=\" \")\n",
" action = policy(env.state)\n",
" state, reward = env.step(action)\n",
" if display and step < 10:\n",
" if reward:\n",
" print(\"({})\".format(reward), end=\" \")\n",
" if display:\n",
" print(\"전체 보상 =\", env.total_rewards)\n",
" return env.total_rewards\n",
"\n",
"for policy in (policy_fire, policy_random, policy_safe):\n",
" all_totals = []\n",
" print(policy.__name__)\n",
" for episode in range(1000):\n",
" all_totals.append(run_episode(policy, n_steps=100, display=(episode<5)))\n",
" print(\"요약: 평균={:.1f}, 표준 편차={:1f}, 최소={}, 최대={}\".format(np.mean(all_totals), np.std(all_totals), np.min(all_totals), np.max(all_totals)))\n",
" print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Q-러닝"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Q-러닝은 에이전트가 플레이하는 것(가령, 랜덤하게)을 보고 점진적으로 Q-가치 추정을 향상시킵니다. 정확한 (또는 충분히 이에 가까운) Q-가치가 추정되면 최적의 정책은 가장 높은 Q-가치(즉, 그리디 정책)를 가진 행동을 선택하는 것이 됩니다."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [],
"source": [
"n_states = 3\n",
"n_actions = 3\n",
"n_steps = 20000\n",
"alpha = 0.01\n",
"gamma = 0.99\n",
"exploration_policy = policy_random\n",
"q_values = np.full((n_states, n_actions), -np.inf)\n",
"for state, actions in enumerate(possible_actions):\n",
" q_values[state][actions]=0\n",
"\n",
"env = MDPEnvironment()\n",
"for step in range(n_steps):\n",
" action = exploration_policy(env.state)\n",
" state = env.state\n",
" next_state, reward = env.step(action)\n",
" next_value = np.max(q_values[next_state]) # 그리디한 정책\n",
" q_values[state, action] = (1-alpha)*q_values[state, action] + alpha*(reward + gamma * next_value)"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {},
"outputs": [],
"source": [
"def optimal_policy(state):\n",
" return np.argmax(q_values[state])"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[39.13508139, 38.88079412, 35.23025716],\n",
" [18.9117071 , -inf, 20.54567816],\n",
" [ -inf, 72.53192111, -inf]])"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"q_values"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"상태 (+보상): 0 (10) 0 (10) 0 1 (-50) 2 (40) 0 (10) 0 1 (-50) 2 (40) 0 (10) ... 전체 보상 = 230\n",
"상태 (+보상): 0 (10) 0 (10) 0 (10) 0 1 (-50) 2 2 1 (-50) 2 (40) 0 (10) ... 전체 보상 = 90\n",
"상태 (+보상): 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) ... 전체 보상 = 170\n",
"상태 (+보상): 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) 0 (10) ... 전체 보상 = 220\n",
"상태 (+보상): 0 1 (-50) 2 (40) 0 (10) 0 1 (-50) 2 (40) 0 (10) 0 (10) 0 (10) ... 전체 보상 = -50\n",
"요약: 평균=125.6, 표준 편차=127.363464, 최소=-290, 최대=500\n",
"\n"
]
}
],
"source": [
"all_totals = []\n",
"for episode in range(1000):\n",
" all_totals.append(run_episode(optimal_policy, n_steps=100, display=(episode<5)))\n",
"print(\"요약: 평균={:.1f}, 표준 편차={:1f}, 최소={}, 최대={}\".format(np.mean(all_totals), np.std(all_totals), np.min(all_totals), np.max(all_totals)))\n",
"print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# DQN 알고리즘으로 미스팩맨 게임 학습하기"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 미스팩맨 환경 만들기"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(210, 160, 3)"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env = gym.make(\"MsPacman-v0\")\n",
"obs = env.reset()\n",
"obs.shape"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Discrete(9)"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env.action_space"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 전처리"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이미지 전처리는 선택 사항이지만 훈련 속도를 크게 높여 줍니다."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {},
"outputs": [],
"source": [
"mspacman_color = 210 + 164 + 74\n",
"\n",
"def preprocess_observation(obs):\n",
" img = obs[1:176:2, ::2] # 자르고 크기를 줄입니다.\n",
" img = img.sum(axis=2) # 흑백 스케일로 변환합니다.\n",
" img[img==mspacman_color] = 0 # 대비를 높입니다.\n",
" img = (img // 3 - 128).astype(np.int8) # -128~127 사이로 정규화합니다.\n",
" return img.reshape(88, 80, 1)\n",
"\n",
"img = preprocess_observation(obs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"노트 `preprocess_observation()` 함수가 책에 있는 것과 조금 다릅니다. 64비트 부동소수를 -1.0~1.0 사이로 나타내지 않고 부호있는 바이트(-128~127 사이)로 표현합니다. 이렇게 하는 이유는 재생 메모리가 약 8배나 적게 소모되기 때문입니다(52GB에서 6.5GB로). 정밀도를 감소시켜도 눈에 띄이게 훈련에 미치는 영향은 없습니다."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+gAAAJYCAYAAADxHswlAAAgAElEQVR4XuydB9g0SVW2ew0ohl8B07qLWcGIYg5gToiKrgEVDKgoBsQIRsyKcUUFxcwCirqCiooIiqAgiDliVhZRwZww4Ptfz3zMx3zzzrxzTteprlPVd18X1++/b1X1qfucnq/urp6eq87Ozs4mDghAAAIQgAAEIAABCEAAAhCAAASaErgKQW/Kn5NDAAIQgAAEIAABCEAAAhCAAAQ2BBB0CgECEIAABCAAAQhAAAIQgAAEIJCAAIKeIAmEAAEIQAACEIAABCAAAQhAAAIQQNCpAQhAAAIQgAAEIAABCEAAAhCAQAICCHqCJBACBCAAAQhAAAIQgAAEIAABCEAAQacGIAABCEAAAhCAAAQgAAEIQAACCQgg6AmSQAgQgAAEIAABCEAAAhCAAAQgAAEEnRqAAAQgAAEIQAACEIAABCAAAQgkIICgJ0gCIUAAAhCAAAQgAAEIQAACEIAABBB0agACEIAABCAAAQhAAAIQgAAEIJCAAIKeIAmEAAEIQAACEIAABCAAAQhAAAIQQNCpAQhAAAIQgAAEIAABCEAAAhCAQAICCHqCJBACBCAAAQhAAAIQgAAEIAABCEAAQacGIAABCEAAAhCAAAQgAAEIQAACCQgg6AmSQAixBH7oh35ouv7666c/+IM/mG5+85tPb/mWbznd//73n97mbd5mc6LnP//5m//+4Ac/ePrkT/7kyyf/nM/5nOkbv/Ebp7Ozs4MBffd3f/f0iZ/4idNzn/vc6ZVe6ZWKgv7SL/3STYz/9E//dHCcxz/+8dN7vud7nvvbH//xH0+v93qv5z73x37sx06v+qqvOj3gAQ9w96VDewLf+Z3fOf3gD/7g9IQnPGF68Rd/8fYBEQEEIAABCEAAAhCAQBUCCHoVrAzaisC3fdu3Tfe+972nz//8z5/ufOc7T//xH/8xfcd3fMf0kz/5k9OTn/zk6a3f+q2rCrqE+xa3uMXR6T/wgQ+cPv3TP306Juj/8z//M/393//9Ju6/+qu/OjfOa73Wa21uLki2rcdDH/rQ6Yu+6Is2Nyxe9mVf9nI33aj48A//8OknfuInpl/4hV+Y3uVd3uXgkL/yK78yfdmXfdn0tKc9bVJ8b/AGbzB98zd/83THO97xcvtHPepR05d/+ZdvzvFqr/Zq0z3vec/pfve73/RiL/Zi1jAvbPcbv/Ebm5ssyuH//u//bm5SKM8f93Efd67fb/7mb07v/d7vveH0F3/xFwfHfchDHjJ90zd90+bvYvpZn/VZm5iPHcqXGGyPl3qpl5pe8zVfc7ruuuumL/zCL7yCq9r813/916QbOrpZ9Lu/+7ub//+11147vfEbv/HmppDi07E/7lVXXTW9+qu/+nSHO9xhw/P1X//1N+100+jt3/7tN/124wiByyAQgAAEIAABCEAAAmkIIOhpUkEgEQQkNxLNRzziEZeHe8ELXrARurd927fdCNOpHfRTcVy0g/5///d/0x/90R+dG0JS+aZv+qbT93//908f8zEfc1TQf+mXfmkjZxcdehJAsmw5JPqv+7qvu5E97f5vj6c+9akbIf3nf/7n6VnPetZRQf/xH//xjcRLhD/iIz5iI9yKUTw/5EM+ZDPcYx7zmOkDPuADps/4jM+YPvIjP3L6tV/7tekzP/MzNzcivu7rvu5cmD/3cz938OkAMZKAv+u7vusVfZ74xCdO7/M+7zPd7W5328Tyci/3ctMP//APb55A0M2Hu9/97pfbf+3Xfu30VV/1VdMrvuIrbnaaDwn6gx70oE1sX/EVX7GJ43GPe9z0JV/yJdO3fuu3Tp/yKZ9yEOtWpCX/Gldcn/SkJ21k+Xa3u93m/97ejLjpppumO93pTptz3+te99pIteL5y7/8y03c+t+//Mu/bKR+f1wx+JM/+ZPNzQjd7JHcb2/46KkK3XTSUxS3vvWtLemnDQQgAAEIQAACEIBAZwQQ9M4SRrgXE7jlLW85fdiHfdhm13z3eJM3eZONqEo4LxJ0PfYuwTx0/OiP/uj0xV/8xbMecf/rv/7r6Zprrpl++qd/enrf933fo4IuQdt97F07qhJCSZvk8d/+7d+mn/mZn9ns3loOCbL+JwnXjvL20BMG2gW+613vOr3hG77hQUF/3vOet2H2NV/zNUfFVeOpv3ahFdf2kChLev/sz/5ss3O8PSSvt73tbTe731/91V99+b/rxsZHfdRHbURXAvoyL/Myl/+mHXxx0A2W3eP2t7/9dKtb3WqS8G+Pd3iHd9jkSDcwdDNkX9D/8z//c5MHib5yvT20q/0jP/Ijk+Lb5bT9+1ak9QTBS7zES1zu973f+73Tx3/8x2/qSjcp9HfdQPmHf/iH6ed//uc3/PaPxz72sdO7vdu7TTe72c0uC/r+uOLwzu/8ztOjH/3o6QM/8AMvD6E6fsd3fMdJj7xzQAACEIAABCAAAQiMRwBBHy+nq57R537u5056zF2PML//+7//JCHTzqgkU49y679dJOgSe0nwocP7HXTtlN7mNrfZ7LBKGN/u7d5u+r3f+73pjd7ojY4K+v55n/70p28eJ5c86rv02kV9+Zd/eXOOTwmdBPa1X/u1Dwq6dodvvPHG6bd+67c2gnzo+P3f//3NY9v6frRkf3tsb0h8+7d/+zm51/eotROsR+C1U6zHt7VDr5147ZYrZsvxwR/8wZunFbTLvH9IqA8Jum6QvN/7vd+kJwiUj+3xlKc8ZSO+P/VTP7XZ/T40nnjsi/Tf/u3fbh7pV31pPtsakWCfehJC5zgm/tsnKfT/Kq7tofPo5oee4pDgc0AAAhCAAAQgAAEIjEUAQR8rn6ufjXagJVKScj2+rUMCqseZtUOro+Yj7rsJ0GPNn/ZpnzZ95Vd+5fQ93/M90yd90idN//7v/77Zud7/7vH2sWXt3ut73L/6q7+6kUW13z7Wrce5tdOsx721m6xH+S+SWY2p74vrRsGHfuiHHqyNiwT9Ld7iLTbirB3jb/mWb5kk3XpMXzvf2++r66sE4vrMZz5zc67dQ+Kq3d9Du73abb/LXe6yyZUe/dZXD7TjrHNaDn2nW7v2ehx+9+sM277HBF2x6/v4unGjPGyPbU3o73q6wCroelT9FV7hFTZz1FcG3uM93mP6x3/8x6NPYZwaVzcAlH89Gq8XEWpnfvd4xjOesXmPwvZJDAsr2kAAAhCAAAQgAAEI9EMAQe8nV0TqJPCc5zxneumXfunN49J6bFyPHf/N3/zNZidaUrX/FnftSuoFbdvju77ruzYvEpMw7R56cZflTdq7gq5xJW7bN7BLIL/hG75hs2usQ8KtXeo3f/M334ijJEwirh3/rUhK3iRmEll9V/sLvuALLn8P/BAaPZIvMT8kz9v2xwRdNzrETY9i/+u//uvmBsdLvuRLbh5b126zbiTo5oDmoKcWJKr7O/uSbT3erhf0HTp+7Md+bBO/cqSX1O0/wn5RurVb/fVf//WbJxPe6q3e6qBQH9pB1w0T7fbv5nnbWd/11vfcdXPnlEhv/66nMrST/4d/+Ieb3EqqxXz38fmL5rG9UbOtJ92A0RMF+hrEwx/+8HMvHFRetHOunfT73ve+ziuC5hCAAAQgAAEIQAAC2Qkg6NkzRHwmAtqR3X1ZmERHMqOdUv2/OvRyMX3/+Bd/8Rc3jyXvC/r+ifQosXZTj/3s2qnAJOjaNZfASnIl6M9+9rM3Mqvd7d2fWdP3vfW/OYe+033o0KPxeju5bk7ohsSh45ig60bG1VdfvXlbvL5Hvv1OuHi+xmu8xkbcH/nIR27EXdL+3//93xuB3z30fXD102P5h47P+7zPm/TCNvXV2+13f/LuIg76zrluXujt6Xr53aHj2A66vi+u74ArD/uHXjCox9v1mPr+sf8ounKox9gl/Hrh3Gd/9mdvuoiB/m/VzvbQTRV9P333+NM//dPNewT2XxKnnXzdUNFOvl5Ep68DvM7rvM4VfXUTQE9V6OsDHBCAAAQgAAEIQAACYxFA0MfK52pnI/mV2GwPvVFbL1X7f//v/20egZYsa6dWx/4j7se+X30Kpr4/LcHaHhI/7XzuH4pFcWiH9lVe5VU2303WG7x3BX3/kfdT5979+7EbCNr9lyyWCLoEX78Nv3voN9W1i6+fgdML6LSTq0fxd1/spvZ6CkDz1aP6+4de5KZxdWNFsqw32+u9AYd+Nm2375//+Z9vdto1tnbmj/2M2zFBV03oyYK/+7u/OxfTK7/yK29eMHhIfPd3uvXLAJqv3hGg7+BvD720TmPs7qDr5owe49ehpwZUN5qHHtE/9h10Pc2hrwy8+7u/+ybe3UOCru/7610LHBCAAAQgAAEIQAACYxFA0MfKJ7PZIaDvSWvXXI+R63vH+vmt3WO7g67Hk+ccEiX9b3toJ1iP0euQOOqxZb0RfF9c9fdjv4M+J45jfUoecddc9Gj99rvVu+fQ4+V6tF1PJnzf933fdI973GMjoNpZ3z0koMqBHjXfPbQ7rJ1vCbZ+5kyHxtHPwP3AD/zA5XcF7M9LN2G0K69HvPXytItelndM0CXHOr++w74r9xJuzVd1snvTZRvD/k63dvF182Kfj968rrH15vlDx/YlcqcEXX31Mjs9wr/7ZAWPuEdeIYwFAQhAAAIQgAAE8hFA0PPlhIiCCOwK+u4j5JJPvVl9/xF37TRrd12Pv5ce+lk0SZ/e4n7oOCboikGPl586tCOvR7IvOvSGc51/7kvi9P3xT/iETzgnrHopnL4zrpfGaQdZO9r7Ly3TI+B6YkCPeusR/+2hPvru+g033LAR0N1Dj7vru9WKe/+nziS9knk9bi9p1VcVLjqOCbpY6PFwvX1ePw+3PfQmeL0A7xir/Z1ufYVCNaSdeOV6e7Ng+9TCb//2b2/G2z88gq73D4iv3hS/PbYviTv2tvlTdcPfIQABCEAAAhCAAARyE0DQc+eH6GYQ0O9Qf/RHf/Tmp6gketrx3T2OvcVd34HW96UlXKWHfkZMP9emnw07dGh3W4936+3uu4d+5k1v8D51SDL15vNTh37STY/UH/vd7Ive4q63kus70HpJ3vYnvbSLre9O6+Vu+g1wieqtb33rzaPYesv89tDj6uKpvvs3KTSG5P3QcehveoRfNwUkpXo53pu92ZudmvbmpsKhl8RpfN3Y0E2D3e+v64V7+sqBbiDo6xD7x6FH0fVkhiRa9aUbCzr0OLvi0xhid8tb3vKKoayCvn2h4Du90ztd8SZ3nUf/U23vvoX+JBAaQAACEIAABCAAAQh0QQBB7yJNBOkhIDm6z33uc/BRZY0jqdQLvvQd391d6CUF3TOf/bb6/W49Pm4RdO1g63H0Zz3rWed2pTXuRYKux9b1Vvnb3/72G5baxdb3zfV49q//+q9vYtChx9L13XE9Hq6Xof3O7/zOhr++i6034ZceeixdMq3d6e0j8btjHvqpuWOCrn76aTc95v6ABzxgc/NCLw3c/ia7Xjx36Dj2XfE73vGOm5/E09ckdONCh+avl9jpxsK9733vzWP5euJBNyt0Y0Dfud9+JWD/0XnVpl4gp69j6J0KemP+7k685vr2b//2IVxL80J/CEAAAhCAAAQgAIF4Agh6PFNGbExAgq6fy9Ibti869h8Tl6BrV/tnf/ZnL+ynF5/t74zud9AOuh5/3t1VPjSofqPduxPqEXTt4ust4BI+fcd7/7hI0NVWj2p/zud8zuY71RJOfW1AP2+2/+Z4Sbr+u95OLz56kZze7r7/Zvc5paFzSqKPHYdekneRoKu9XlCnpxX0ojt9d161opsKx45jgv7Upz51I+B6aZt+vm176KsKeou+foZNT2ToaxUSeP1uu94kr6c8dBx6OaDqV99l/8qv/MorfudeT3foawES9+3NkTk86QMBCEAAAhCAAAQgkJcAgp43N0Q2k4AE55//+Z9P9t5/TFyCfuxR8N3B9Ijx/mPzhwT90NvL99v9xm/8xmaX2nN4BF3j6jF67UDre9d6ezxHfwR0U0E75/p5O+3+c0AAAhCAAAQgAAEIjEkAQR8zr8wKAlcQ0Hfy9bvmeqyboz8C+k7/wx72sM3P273ES7xEfxMgYghAAAIQgAAEIAABEwEE3YSJRhCAAAQgAAEIQAACEIAABCAAgboEEPS6fBkdAhCAAAQgAAEIQAACEIAABCBgIoCgmzDRCAIQgAAEIAABCIxDQO+2uOqqqzYT+rZv+7bp0z/906f/+Z//cX+NRi/YfPSjH735VRDvoZ/s/Ju/+Zvpl37pl7xdi2LePZleRKp312gOhw79QoletLp7XHPNNdNNN93kjvk//uM/prd+67fe/LrK+77v+7r70+FKAnqJrX62VrWn/1u/bjMnL6/0Sq+0+WlYvZy11rG93nQuvZhWL4nVC1/1kln94s72+LM/+7Pp8z//8zcvLNb1qJ9b1dcTd99XpFr9oA/6oM2v6mgMvZRYf1ef3V9+qTUXxq1PAEGvz5gzQAACEIAABCAAgcUJ6Kcdv/Vbv/WK80oUtPD/7u/+7uke97jHhYK+/ysar/AKrzDpVyp2j31Bf/7zn3/wZz3V52M+5mOm7//+77/c/ZCgb39d5Bgs/dqKfsry1E2FrcQcG0e/UiKZOSbo//7v/z7967/+6/S85z1v87/dQ+8CkRi93Mu93EburYd46yW2N95446aLcqH3izz4wQ/e/EKHBP4N3/ANp8/4jM+Y9O6Y7Q0UxaJfH5GMSuBe/MVffNILY7/4i794I3ARx+/93u9tXij79Kc/ffrrv/7rzS+yXHfddZN+fvSVX/mVL5/iH//xHzcy/OM//uOTcq1fMtHPoB76ydPduPRzr5rHqeM2t7nN5Xk/9rGPPXcj4wM/8AMv30w5JejbX21Rrfzt3/7t5hdUFOtbvuVbXhGGVdCVG4nw7q+27M/np3/6pzc/Obt76CdU3+u93mvSfC4S9O3P277VW73V5mdt9Ss/qg3lXb+mo5+91bEv6Ppv3/7t37651vXy4Zvf/OanMPP35AQQ9OQJIjwIQAACEIAABCAwh8Czn/3s6bnPfe5GeCR1Esvv+q7v2kiKfhbz9V7v9TbDHpNd/RSlpHF7aAz12Yqj/vu+oEuKJJv7x53udKfpjne8o1nQFdO7v/u7nxvn1re+9eYXSU4Jun5m9NBu6lOe8pTNz13qRoB+/vKYoGs3VQJ80fF5n/d55pev/tqv/dpGEPWLKpJQHdo51W6pfgb1oz7qozZcJX/6GdAv+IIvuLxzr912/dzo/e9//83PcP793//9Rtj13ySE7/3e731FmHqhqNopX/vHz/3cz03v+Z7vecV//qM/+qNJUqifZZWQi4vi1fn0k7S/8zu/s5G+F7zgBZsbApLd66+/frrFLW6xkXpJof6n3Bw73uM93mN6whOecLKM//d///dy3BJ6if320E2LV3/1VzcLunap9csnD3rQgzZirSchFIPm9vqv//qXx7UIuur6Vre61eZnfB/4wAcenYdu6ki0dSifuuY+8iM/cnrd133d6Yd/+IcvFHTVwC//8i9Pf/iHfzi99Eu/9GYMnVc3YyTrT3rSkzb/7ZCgKzc6x6d8yqdMqkuOvgkg6I787f6D5OhGUwhAAAIQWCEBLaw4IJCJgBbx2gW89tprN6Ky/UlSSbsW9dtH3CXYb/zGb3wudO0ESmb0SO32sD7iftvb3nYjGtYd9Ec96lHTXe5yl6P4Tgn6sY6PeMQjNjIs+XuZl3mZo4L+n//5n1fs+G5vcjz+8Y/ffB1AP30pmZY4WQ7doNAYuz/Bqjy8xVu8xfSTP/mTVwyhx5ef9rSnbXay/+RP/mQjk3os/rM/+7Mvt/uv//qvjUjr5zc1p+2hGyq6iaL//tCHPnR6sRd7sct/U44lqxJAnXt76DF+3SzY7pxv/7t2yZUDPTqtHWA9Qv4RH/ERm93ct33bt900k5BK7PU0hHZ75x4Sad0U2b0htD/W/s2Ui3bQVaO6YaAbCNqN1vHf//3fm51+3YzYZWYRdN2A0A62nirQjrbn0Dl1Y0M3Xi7aQb/d7W43vcEbvMH0Iz/yI1cMr3nqiZft0yuHBF0dvv7rv36TR90g0NMdHP0SQNAduUPQHbBoCgEIQGDlBBD0lRdAwulrB1G7ws94xjOm13iN17ji0WWFuxV0iYx2VbeH1j83u9nNJknmG73RG20eb94etQRdQrK/M6xzakdX/7MKuh7JlhRrB/fVXu3VNjve2n3e3mQ49R30LReNIUnWDue97nWvjehaD+04a+dXj7NLcLeHHiMX092bFvrbJ3zCJ2x2SfVovXbcdbPk+77v+6aP/diPveKUEnFJ3faR+e0fteOteUmuxVH5+5Iv+ZJN/I95zGM28r57SNz1eLRYbXdu9Xe9G+AOd7jD9BM/8RPT+7//+08f9mEfNumrARL83eOe97znpiY0z7mHJPThD3/49JznPOfoEB5B17z1ZILecfCqr/qql8cUBz1BoptT2ycMLIKuvOlGhW5I6EaFRN1y6IkAyfI3f/M3b+rmIkHXo/H6CoPyt+scug7E9jd/8zc3pzwm6GKnOlN8H/7hH24JjzZJCSDojsQ8+z73cbSmKQQgAAEIrJnANddfv+bpM/dkBLTbq8dz9aiydkAlfVuhkvzpkeqLXhKnx4K18/hjP/ZjmxdUbY9Dgr4vcGrrfcT9GD49gq2dVqugS2q0S61HuyXpd7/73Tcv19q+mG73e/bbl7/9wz/8w/Trv/7r0x/8wR9MT37ykzdiJrHSzrsESkKsHWh9F15MtLt89dVXH824nlD4pE/6pI0sSsq3h+agXXE9Lr59xF07u/e+9703Mq2deh0f+qEfOunRfOVp+4i7dpwl9trR15MJ+4fypa8I6PFq3YzR4+qSaMW8f4iHdsQl47qBoZ15zV+CK7nUo/Qv+ZIvudnd3d991ljaGZZ8agf+Ig4XXRL6fr5i/q3f+q3LzbSbrq9ZbI/tI+5f8RVfsXkCRDcOlI9DL4n7zM/8zM0TBPo6wO6h60BM9GSCHgnXcUrQv/d7v3fztYhHPvKR05/+6Z9uHpV/3OMet/n+/f4heVZOJOZ6SkO72aohnVeP2evJC71j4NBL4n71V391esd3fMdNreh61E0xsdUTDnrK4s53vvPmdMcEXX/Tbr2ubcXK0S8BBN2ROwTdAYumEIAABFZOAEFfeQEkmb6e5Piar/mazc75/e53v0nyqUd09T/Jno5TsqtH29VWLwWTRGzbb6coodt9i/uxJw4tL4lTvNuXiUmu9DI2SYoEVoekRf87FfM2tn1B13eatXOqnUYdEnTJlKRfj6tLoLSDKbnVo+X6/2tnU/K6nZd2mvUIvr4TrKcRtLsuBscOvbVbNzYksPuH5F/CqLzo0EvnJOm7b3kXf+36K3/6v3XoBXe6UbD7Xer9sZ/61Kdudsv1OLykbv/lZbvtdbNGcej769tDIilh3z7GL1mXPCqW3WP7KLxY7L+AzXoZ6OVv+lqBxHd7PPGJT5ze9V3f9Yoh1E43mpQv8dTXFA4Jup5C+IVf+IWNUO8eGl870qoLPX2g45ig64aVZFw3BPSuAJ1T9SlZ1/WjpygUy+4hEdeLDFWjt7zlLTd5349BN0uOvcVdNx50g0ZPTujQjSDdrNndEb9I0HUDSnnQzSWOfgkg6I7cIegOWDSFAAQgsHICCPrKCyDB9LVTrp1ELdj1/WBJiwRDO6Pajf3UT/3UjXzccMMNF/7Mmr7D+3Vf93WbnWK9lEyP+Wo3WIfkXzus+4Ku/y6hvOg49TNresRbbxA/9Hi3V9C126qXtG3fzK6dTe1mf9mXfdkVP7O2/2I8axolitqpPnRIKjWuvse8e2iXWjva2onVDrRuAEj69Bi93vi9ld3v+Z7v2eRHoi/Jlpjqu9V6HF3CKRE8dOjN3nrCQYKt/srJoUM3XpQLPb6ucbVTLjnWLr0ePZdM6tCNDeV0/2fnttKrneK5b5XXzrFenqfd6u2xFXRx2/2Zse3fL/oO+md91mdtnjDY3vjY9tnuoEua9d15HfuCrp179dWNCNW5vtutl69tD90k0a62bl7oqQxdY5L+/RtTukEiHj/wAz9wWeRP/cza9hzKsW4c6Xv0++PqutbNArHafSJDfcVEN7T0kkSOfgkg6I7cWQX92huPP+bkOB1NLyBw03XHv6O0241cUEajEKDm82TSmgsEPU/O1hiJduIkD9oJ1vdf938fWY//Ssz16Ky+r3vsd9C1c6j+khHtwmsnT49sb49Dj7hLKOYIuqRCsrg9JOh6qZ1EUW8e10uy9GZ2CZQk6VjMkkjFu3/ojdr6qTi9jVvfS9Z3pzX33d9B3/9pOWvt6AkDCeWhQ1KtnftdQdeNEn2fXwK2u2us/sqZvk+snz7TfCWSkmQ93r89JJ4SWn0XWkK2f2hekn7lWN+911cM9F3zrWzvtleuJOZ6cmD7Zn/9XX31WLlEU4/R6yaEbhLoZs3uoScI9Pi1fqJNv/M+59Cj8ZLg3Tfnlwi6bmrohpQ4av7bQ+Orni/6Drp2zvW1AtWIaum1X/u1D05JoqwbPPpqhHjvHrohI2a6saCbWtvDIuiq8+0NsItY6hF9ffVg90DQ51Rfvj4IuiMnCLoDVuWm1gUygl45EQy/GAFqfjHUJ09kzQWCfhIlDSoT0A7t9reTLzrVod1oybJ22LXjqMd0JWDahdb3z/U/7d7pZW0WQddOoKRaj6xLOPX93+3veEtEtt8H1+PCelHY/iGx1rkk19ox1Peg9Vb4Y4Ku2Lc7iHqLuSRG373ffQHa9hyWl8SVpunQI+56CZ8EWxK8/5i0dm8/7uM+bvOiPj2mrcfKJfi7b15XTHppnL5br5/T2z30Mjr1lzTqawU69Ci9dvL1eLVuuuwe2sGXfOu707uHRFWPtUv6tGuux/h182B3l1vtt/Hqu+z6SoL3UK5e/uVf/hyLXUHX96r/5axL5gMAACAASURBVF/+ZSOu+t65WKhW9Kj5oUfc9ci+bn5IoLfz1aP+27e47/6W+anvoHvnoxsVekGfBF9Pl6h2t4dF0Pe/QnLs/Id484i7N1s52yPojrwg6A5YlZtaF8gIeuVEMPxiBKj5xVCfPJE1Fwj6SZQ0WIiAdvP0fWmJtX5DfP/YF3T9XrMeEZZUS/a0+7o99Dftqup7uNtHqPWdWMt30CXakjztHOvRev1vV9B3d9Al1nqkWlJ96KfMrI+4a9daorT9zvn+3I8Juh4xlhCeOrQ7qx34i46HPOQh0yd/8idf8ZI47UpLjA+9cXv7GLbaSND1wrD9t5HrfBJ3PT69+9SBvset76XrbeV6QmD30A0WCbt25nd56EaBdun13endQ08pSNCVb+3Sqw50k0U/Abd7SOA1R+387v6s2yl2279rJ1rf9d/fgT/0HXTJ7nZ+quljgq6xxUA/O7b7O+i6CaGX0ekx/u0RJei6AaDr5ju/8zs3T6/o59J042H3sAj6KW56E7/eqn9I0HUDQteXrlOOfgkg6I7cIegOWJWbWhfICHrlRDD8YgSo+cVQnzyRNRcI+kmUNFiIgCRGj+we2+HU93ElsnpcWo+n663ikiNJ8P6urUKWoOmRee26nnqL+/Yn2iQqerR89/u0F30HXTvAOrck/tCht1Tr0WzJ1vbnsg612+4AH3q0W+31aLduWuz+xrj++13velfTm7D13X7J90XHoZ9Z04vm9DSAbprsy5TyIJnWT8Hp6QI9Iq+bIbvfg9aL9CRiEj4x2D009i1ucYuDIR36m86nr0ToxWK7b2HX1x907gc+8IGbpxUk6Xqq4pnPfOblR+H1k3wSfAn27m+Lb0+upyX0BMVFh+pTj55L0LdCu/0+v24uSfp1o0bftd/9fe+LvoOu8+lrBHocX+zEUo/f6yfW9h/D3xV0cdXTCt5DT2nokXN9HUP1pBe9HXpZokXQ5+6gb39mTXnY/Tk/71xo354Agu7IAYLugFW5qXWBjKBXTgTDL0aAml8M9ckTWXOBoJ9ESYOFCJwS9JIwrL+DfugcFwm6HmPX96G1w1xynBL0uWNr11qPzVsEXefQy+B0I0Hf194e25fv6bHk7W+c63FxPfau74vre/QveMELNm+Q1w0UfR9aL+qTbOrmhN6orzeob39+a+5c9FUIvaRNTzfoRoa+c63dez0erpsqOrfEWXPWT9ZpHnpxmr5yoBj15nfFoq8d7B96wmDO76NvfxbvojmdEnQrj11B18v5dt+gbx1j+xN9p9p7BP3Um9j3v4OunKgu9ALE/Z37U3Hx91wEEHRHPhB0B6zKTa0LZAS9ciIYfjEC1PxiqE+eyJoLBP0kShosRGAr6Hq8VyJx7NDfvAv7moKuR9P3f9JrP3bttGon/9ghQZf86nvgxw4J50U/V3aon1fQ9dSBHmnXz2dpx1mHdnj1PXFJvn47Xv9/vRRPL3fTC862O7Da1dVbu/XItHaUdWNAv1uuR9/1iH7EoZ8dk5DrsXE92i+x1mPU+m96k/720KP/EmOJrBgoDomhbqgsfdQQ9NpziBR0PYGxfYO/buRI2PW1h/13DNSeE+PHE0DQHUwRdAesyk2tC2QEvXIiGH4xAtT8YqhPnsiaCwT9JEoaLERgK+inTqfvPuvRbs9RU9D3H90+FJe+o32RpErQt7+rfmxe2gnW96c9h1fQNbZ2yvW9fv1+OUc5gdEF/RQh3RgRAx16jF9fTdGNlkPvmTg1Fn/PRQBBd+QDQXfAqtzUukBG0CsnguEXI0DNL4b65ImsuUDQT6KkAQRWRUA3CvT9Zz0VMOcx6lXBYrJmAvrKg37OTS/5u93tbmfuR8O8BBB0R24QdAesyk2tC2QEvXIiGH4xAtT8YqhPnsiaCwT9JEoaQAACEIAABCCwRwBBd5QEgu6AVbmpdYGMoFdOBMMvRoCaXwz1yRNZc4Ggn0RJAwhAAAIQgAAEEPT5NYCgz2cX3dO6QEbQo8kzXisC1Hwr8ufPa80Fgp4nZ0QCAQhAAAIQ6IUAO+iOTCHoDliVm1oXyAh65UQw/GIEqPnFUJ88kTUXCPpJlDSAAAQgAAEIQGCPAILuKIloQbcu8qyS2WK8zLEptcR3uMCja8VyGUWfk/EOU89c85ljq/F5gaBbPhloAwEIQAACEIDALgEE3VEPCPp5WGtbcDNfxwWz1xSh7k+oo6V1bdcPgj7/84KeEIAABCAAgbUSQNAdmUfQEfS1CYZ1vpbLCEFH0K31NEqtIOiWTwbaQAACEIAABCDADvrMGkDQEfS1CYZ1vpZLahTpsjJhvnxeIOiWTwbaQAACEIAABCCAoM+sAQSdBTdyNvPimaYJYWUHfW3XD4I+//OCnhCAAAQgAIG1EuARd0fmowXdcWqa7hGIlj0AQyA7AWo+T4asuUDQ8+SMSCAAAQhAAAK9EEDQHZlC0B2wKje1LpCtO3aVw2V4CBQToOaLEYYNYM0Fgh6GnIEgAAEIQAACqyGAoDtSjaA7YFVual0gI+iVE8HwixGg5hdDffJE1lwg6CdRXtjg7ne/e9kA9A4jcMMNN5jHIm9mVDRcGQGuoz4T7slb1AwRdAdJBN0Bq3JT6wIZQa+cCIZfjAA1vxjqkyey5gJBP4kSQS9DtFhvzwIVQV8sLZyoMwJcR50l7IXhevIWNUME3UESQXfAqtzUukBG0CsnguEXI0DNL4b65ImsuUDQT6JE0MsQLdbbs0BF0BdLCyfqjADXUWcJQ9D7SBiCnidP1gUygp4nZ0RSRoCaL+MX2duaCwS9jDqiV8YvsjdiEUmTsdZKgOuoz8x78hY1Q3bQHSQRdAesyk2tC2QEvXIiGH4xAtT8YqhPnsiaCwT9JEp20MsQLdbbs0DlxspiaeFEnRHgOuosYeyg95EwBD1PnqwLZAQ9T86IpIwANV/GL7K3NRcIehl1RK+MX2RvxCKSJmOtlQDXUZ+Z9+QtaobsoDtIIugOWJWbWhfICHrlRDD8YgSo+cVQnzyRNRcI+kmU7KCXIVqst2eByo2VxdLCiTojwHXUWcLYQe8jYQh6njxZF8gIep6cEUkZAWq+jF9kb2suEPQy6oheGb/I3ohFJE3GWisBrqM+M+/JW9QM2UF3kETQHbAqN7UukBH0yolg+MUIUPOLoT55ImsuEPSTKNlBL0O0WG/PApUbK4ulhRN1RoDrqLOEsYPeR8IQ9Dx5si6QEfQ8OSOSMgLUfBm/yN7WXCDoZdQRvTJ+kb0Ri0iajLVWAlxHfWbek7eoGbKD7iCJoDtgVW5qXSAj6JUTwfCLEaDmF0N98kTWXCDoJ1Gyg16GaLHengUqN1YWSwsn6owA11FnCWMHvY+EZRd066IxknYrAbbONXt8kblgrGUIZK+p7PFFZin7XBH0smx7RK/WwpdxL+UQDn1y8F6B5LnPPJO3PHnzXnPH2rOD7iCJoJ+HlX2BnD0+R/nRNAmB7DWVPb7INGafK4Jelm0E/RI/OMBh90ry1IP3CkT08oieJ8/kLU/evNccgh5ALFrQo3eBreMFoLg8RPYFcvb4InPBWMsQyF5T2eOLzFL2uSLoZdlmgYqYzhVThGXetQe3PKLH51+fn3/zrrzzvdhBd5BE0NlBt5ZLi5sl1thoV0YguxRmj6+M/pW9s88VQS/LNgvUPheo5C1P3rxXIIKOoO/WDPXgrwfvNccOegAxBB1Bt5YRgm4l1V+77FKYPb7IjGefK4Jelm1EL4/osVD3L9Qz1K/3CiTPfeaZvOXJm/eaQ9ADiCHoCLq1jBB0K6n+2mWXwuzxRWY8+1wR9LJsewSn7Ez0PkWglgCcOi9/h8BIBLiO+symJ29RM+QRdwdJBB1Bt5YLgm4l1V+77FKYPb7IjGefK4Jelm0EvYxfZG/PApW8RZJnrJEIcB31mU1P3qJmiKA7SCLoCLq1XBB0K6n+2mWXwuzxRWY8+1wR9LJsI3pl/CJ7exao5C2SPGONRIDrqM9sevIWNUME3UESQUfQreWCoFtJ9dcuuxRmjy8y49nniqCXZRvRK+MX2duzQCVvkeQZayQCXEd9ZtOTt6gZIugOkgg6gm4tFwTdSqq/dtmlMHt8kRnPPlcEvSzbiF4Zv8jengUqeYskz1gjEeA66jObnrxFzRBBd5BE0BF0a7kg6FZS/bXLLoXZ44vMePa5Iuhl2Ub0yvhF9vYsUMlbJHnGGokA11Gf2fTkLWqGCLqDJIKOoFvLBUG3kuqvXXYpzB5fZMazzxVBL8s2olfGL7K3Z4FK3iLJM9ZIBLiO+symJ29RM0TQHSQR9PmCbhVW64J7lPEc5UfTJATWVqPR841MozW2yHNqLOvnD4JeRh7RK+MX2duzQCVvkeQZayQCXEd9ZtOTt6gZIugOkgg6gm4VAusC3lF+NE1CILoG1jZeZBqt7CLPiaBH0zw+HqK3HOtTZ/IsUMnbKZr8fa0EuI76zLwnb1EzRNAdJBF0BN0qBAi648LqrGl0DaxtvMh0W9lFnhNBj6aJoC9HdP6ZPAtUBH0+Z3qOTYDrqM/8evIWNUME3UESQUfQrUKAoDsurM6aRtfA2saLTLeVXeQ5EfRomgj6ckTnn8mzQEXQ53Om59gEuI76zK8nb1EzRNAdJBF0BN0qBAi648LqrGl0DaxtvMh0W9lFnhNBj6aJoC9HdP6ZPAtUBH0+Z3qOTYDrqM/8evIWNUME3UESQZ8v6A7MpqZWAc6+gDdNlkapCGSvqezxRSYz+1x5SVxZthG9Mn6RvT0LVPIWSZ6xRiLAddRnNj15i5ohgu4giaAj6NZysd5AsI5HuzwEskth9vgiM5l9rgh6WbYRvTJ+kb09C1TyFkmesUYiwHXUZzY9eYuaIYLuIImgI+jWckHQraT6a5ddCrPHF5nx7HNF0MuyjeiV8Yvs7VmgkrdI8ow1EgGuoz6z6clb1AwRdAfJaEF3nNrUtIUUZl8gZ4/PlFgapSKQvaayxxeZzOxzRdDLso3olfGL7O1ZoJK3SPKMNRIBrqM+s+nJW9QMEXQHSQSdHXRrubS4WWKNjXZlBLJLYfb4yuhf2Tv7XBH0smwjemX8Int7FqjkLZI8Y41EgOuoz2x68hY1QwTdQRJBR9Ct5YKgW0n11y67FGaPLzLj2eeKoJdl2yN6ngVUrXHLZnu8tyfeWjHU4psh3loxMG4uAlxH/nx4rnv/6LYeveXNNqvTrRD004wut0DQEXRruSDoVlL9tcsuhdnji8x49rki6GXZ9izMPAvJWuOWzRZBj+TnqYfI8zJWXgKe677WLDx12Vu8tZitlQOC7qgoBB1Bt5YLgm4l1V+77FKYPb7IjGefK4Jelm3PwqzWwtczbtlsEfRIfhnyFjkfxion4Pk8KT/b4RE8ddlbvLWYrZUDgu6oKAQdQbeWC4JuJdVfu+xSmD2+yIxnnyuCXpZtz8Ks1sLXM27ZbBH0SH4Z8hY5H8YqJ+D5PCk/G4IexbC3vEXNG0F3kETQEXRruSDoVlL9tcsuhdnji8x49rki6GXZ9izMPEJWa9yy2SLokfw89RB5XsbKS8Bz3deahacue4u3FrO1ckDQHRWFoCPo1nJB0K2k+muXXQqzxxeZ8exzRdDLsu1ZmNVa+HrGLZstgh7JL0PeIufDWOUEPJ8n5WdjBz2KYW95i5o3gu4giaAj6NZyQdCtpPprl10Ks8cXmfHsc0XQy7LtWZh5hKzWuGWzRdAj+XnqIfK8jJWXgOe6rzULT132Fm8tZmvlgKA7Kiq7oDumQlMIQAACEJhJwHoDDkGfCfiF3TwLs1oLX8+4ZbNF0CP5Zchb5HwYq5yA5/Ok/GyHR/DUZW/x1mK2Vg4IuqOiEHQHLJpCAAIQGJQAgr5MYj0Ls1oLX8+4tah4ONSKwcOht3hrMWPcXAR6q8ve4q2V7bVyQNAdFYWgO2DRFAIQgMCgBBD0ZRLrWZjVEkjPuLWoeDjUisHDobd4azFj3FwEeqvL3uKtle21ckDQHRWFoDtg0RQCEIDAoAQQ9GUS61mY1RJIz7i1qHg41IrBw6G3eGsxY9xcBHqry97irZXttXJA0B0VhaA7YNEUAhCAwKAEEPRlEutZmNUSSM+4taj0xqG3eGvljXFzEeitLnuLt1a2PRxqxdDi3wEE3ZFNBN0Bi6YQgAAEBiWAoC+TWM/CzLOAqjVuLSq14mXcWhlj3IwEqPdLWanFoVbOPfHWisHz70tUDAi6gySC7oBFUwhAAAKDEkDQl0msZ2HmWUDVGrcWlVrxMm6tjDFuRgLUO4I+ty49/77MPcd+PwTdQRJBd8CiKQQgAIFBCSDoyyS2twV1LSq9cegt3lp5Y9xcBHqry97irZVtD4daMSDotcgGjYugB4FkGAhAAAIdE0DQl0meZ2HmWUDVGrcWlVrxMm6tjDFuRgLUOzvoc+vS8+/L3HOwg15ADkEvgEdXCEAAAoMQQNCXSWRvC+paVDwcasXgWaD2Fm8tZoybi0BvddlbvLWyvVYOPOLuqCgE3QGLphCAAAQGJYCgL5NYz8KslkB6xq1FxcOhVgweDr3FW4sZ4+Yi0Ftd9hZvrWyvlQOC7qgoBN0Bi6YQgAAEBiWAoC+TWM/CrJZAesatRcXDoVYMHg69xVuLGePmItBbXfYWb61sr5UDgu6oKATdAYumEIAABAYlgKAvk1jPwqyWQHrGrUXFw6FWDB4OvcVbixnj5iLQW132Fm+tbK+VA4LuqKhoQbcu8q698WpTlC3GyxyboBHf4dKJrhVLgUafk/EOU89c85ljq/F5cc3111suDdocIeBZmNUSSM+4tRLp4VArBg+H3uKtxYxxcxHorS57i7dWttfKAUF3VBSCfh7W2hbczNdxwew1Raj7E+poaV3b9YOgz/+8UE/PwqyWQHrGLZvt8d4eDrVi8HDoLd5azBg3F4He6rK3eGtle60cEHRHRSHoCPraBMM6X8tlhKAj6NZ6GqVWEHTLJ8M4Ylo223E4rHVBXSv/jBtDoLe67C3emCydH2WtHBB0R0Uh6Aj62gTDOl/LZTSKdFmZMF8+LxB0yyfDOGJaNttxOKx1QV0r/4wbQ6C3uuwt3pgsIehbAgi6o6IQdBbcyJnjgtlrirCyg7626wdBn/95oZ4sUC/x641Db/GWVSm9eyHQW132Fm+tOlgrBwTdUVHRgu44NU0hAAEIQCAJAevNJgS9LGFrXZjtU+uNQ2/xllUpvXsh0Ftd9hZvrTpYKwcE3VFRCLoDFk0hAAEIDEoAQV8msWtdmCHo5fXleald+dkYoQcCvX2e9BZvrRpYKwcE3VFRCLoDFk0hAAEIDEoAQV8msWtdmCHo5fWFoJczHG2E3j5Peou3Vr2slQOC7qgoBN0Bi6YQgAAEBiWAoC+T2LUuzBD08vpC0MsZjjZCb58nvcVbq17WygFBd1QUgu6ARVMIQAACgxJA0JdJ7FoXZgh6eX0h6OUMRxuht8+T3uKtVS9r5YCgOyoKQXfAoikEIACBQQkg6Mskdq0LMwS9vL4Q9HKGo43Q2+dJb/HWqpe1ckDQHRWFoDtg0RQCEIDAoAQQ9GUSu9aFGYJeXl8IejnD0Ubo7fOkt3hr1ctaOSDojopC0B2waAoBCEBgUAII+jKJXevCDEEvry8EvZzhaCP09nnSW7y16mWtHBB0R0Uh6A5YNIUABCAwKAEEfZnEZliYLTNTzgIBCEAAAlkJtLjhh6A7qgFBd8CiKQQgAIFBCSDoyyQWQV+GM2eBAAQgAIHjBBD05NWBoCdPEOFBAAIQWIAAgr4A5GmaEPRlOHMWCEAAAhBA0LutAQS929QROAQgAIEwAgh6GMoLB0LQl+HMWSAAAQhAAEHvtgasgh49wWtvvDp6SNN4lkVo5thMk3Q2Wtt8nXhSNl9bzjLPN3NsNYr3muuvrzHsasZE0FeTaiYKAQhAIC0BHnFPm5pLgSHo5xO0tgX32uab/JI0hbe2nGWeb+bYTMXkbISgO4HtNUfQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUUXQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUUXQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUUXQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUUXQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUUXQy/jRGwIQgAAEygkg6OUMq46AoCPoaxOMqhfUQoOvLWeZ55s5thrliKCXUfUIumcBxbiX8gKH8Tl4r0Cuo0vE4ACH3WvHUw/ea+5Y+6vOzs7OogYbfRwEHUFfm2CMcE2vLWeZ55s5thq1jqCXUUUgxxdIz8KXevDXg/cKJB+I6Vwx5fr0Xm0Xt0fQHTwRdAR9bYLhuDzSNl1bzjLPN3NsNQoYQS+jyoLPL2QIFoJVctVRP9QPgn7+CvJcFyXX325fBN1BEkFH0NcmGI7LI23TteUs83wzx1ajgBH0MqoIOoK+W0HUg78evFegR0TIhz8f8B3/Boj3mjvWHkF3kETQEfS1CYbj8kjbdG05yzzfzLHVKGAEvYwqAoAAIOjnryHPdeG9AhHI8QXSUz/Ug78evNccgh5ADEFH0NcmGAGXTfMh1pazzPPNHFuNQkXQy6h6FpJlZ6I3BCAAAQhA4DABz42KKIbsoDtIZhf0m657jmk21kWyZbzIsRR89HgmII5G0fFFj+eYymqaRjOOHi86EdHxRY4XOVYPnxcIell1I+hl/OgNAQhAAALlBBD0coZVR0DQz+Nd24J7bfOtekEtNPjacpZ5vpljq1GOCHoZVQS9jB+9IQABCECgnACCXs6w6ggIOoK+NsGoekEtNPjacpZ5vpljq1GOCHoZVQS9jB+9IQABCECgnACCXs6w6ggIOoK+NsGoekEtNPjacpZ5vpljq1GOCHoZVQS9jB+9IQABCECgnACCXs6w6ggIOoK+NsGoekEtNPjacpZ5vpljq1GOCHoZVQS9jB+9IQABCECgnACCXs6w6gjZBT168pEviWsRW/Q5NZ5VMKLPbclF9DlHGW9tOcs838yx1ah3BL2MKoJexo/eEIAABCBQTgBBL2dYdQQEff4OenRiWgnr2gQjOm8txltbzjLPN3NsNWoTQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQS+jiqCX8aM3BCAAAQiUE0DQyxlWHQFBR9DXJhhVL6iFBl9bzjLPN3NsNcoRQa9BtXzMDOLvWfD1Fm95hg6PAIdaZHONS54v5aM3Dr3Fm6vqz0dz1dnZ2Vn2ILPEd9WtH5AllEXiuOm655w8T6sF98nAaAABCCxKgM+L87jPnnXfRXPAyWwEeltI9havLQv+VnDwM+uxB3lG0OfWrefG59xzLNUPQXeQRtDPw0LQHQVEUwgMTABBR9B7Ke/eBKC3eGvVARxqkc01LnlG0OdWJII+l1zn/RB0BL3zEiZ8CFQjgKAj6NWKK3jg3gSgt3iD03V5ODjUIptrXPKMoM+tSAR9LrnO+yHoCHrnJRwS/q+8yRNPjvN2v/suJ9vQYCwCCDqC3ktF9yYAvcVbqw7gUItsrnHJM4I+tyIR9LnkOu+HoCPonZdwSPgIegjG4QZB0BH0Xoq6NwHoLd5adQCHWmRzjUueEfS5FYmgzyXXeT8EHUHvvIRnh2+R8mODs5s+G3tXHRF0BL2Xgu1NAHqLt1YdwKEW2VzjkmcEfW5FIuhzyXXeD0FH0Dsv4dnhI+iz0a2mI4KOoPdS7L0JQG/x1qoDONQim2tc8oygz61IBH0uuc77IegIeucl7A5/K+aHdsEtf9s9ITvpbvxddUDQEfReCrY3Aegt3lp1AIdaZHONS54R9LkViaDPJdd5PwQdQe+8hN3hWyT8InlH0N3Iu+2AoCPovRRvbwLQW7y16gAOtcjmGpc8I+hzKxJBn0uu834IOoLeeQm7w0fQ3chW2wFBR9B7Kf7eBKC3eGvVARxqkc01LnlG0OdWJII+l1zn/RB0BL3zEnaHf+i759sd87l/cwdBhy4IIOgIeheFOk2TRwA8Cz7GrVsBvfGtS2Pc0cmzX9D5nLrEzMMh+xV01dnZ2Vn2ILPEh6Aj6Flqcak45kr4Rf2Wip3zLEsAQUfQl624+WfrTQB6i3d+Zi7uCYdaZHONS54R9N2KrFUPuar+fDQIuiND0YJuWdAqvGtvvNoUZYvxMseWnV0P8d30zGduai/iJXHX3uY2pjqmpg5janF9R9foKLk1FfI0TWfPuq+1Ke0WJFBrwce4dZPYG9+6NMYdnTwj6Aj6NCHojs84BP08rFEW3CPIT7RMaTwEnZqPvMYjx6pR79b4rP9sIOhWUsu2BQ8QYQAAIABJREFU600Aeou3VjbhUItsrnHJM4KOoCPork8lBB1ZsS7gRxF+BJ2aj6z5yLEQdNc/XzTeIdCbAPQWb61ig0MtsrnGJc8IOoKOoLs+lRB0ZGUUwbDeQNgKuutCOdKYR9wPg1lTTY0yV+v1wA66ldSy7TwCUCsyz8uMeou3FjM41CKba1zy7Bf0Whnkc6oW2dPj8oj7aUaXWyDoCPoogoGgn69lK5O11UDkfCPHYgfd8Y8XTa8g0JsA9BZvrXKDQy2yucYlzwj63Ir03FCYe46l+iHoDtLRgu44dZOmFmGxLribTICTFhM49Db2uYMeetHc3LHol48Anxfnc8IOer46VUS9CUBv8dbKOhxqkc01LnlG0OdWJII+l1zn/RD08wlE0Dsv6hPhI+hj5zdydgg6gh5ZTzXH6k0Aeou3Vu7gUItsrnHJM4I+tyIR9LnkOu+HoCPonZewO3wE3Y1stR0QdAS9l+LvTQB6i7dWHcChFtlc45JnBH1uRSLoc8l13g9BR9A7L2F3+Ai6G9lqOyDoCHovxd+bAPQWb606gEMtsrnGJc8I+tyKRNDnkuu8H4KOoHdewu7wEXQ3stV2QNAR9F6KvzcB6C3eWnUAh1pkc41LnhH0uRWJoM8l13k/BB1B77yE3eEj6G5kq+2AoCPovRR/bwLQW7y16gAOtcjmGpc8I+hzKxJBn0uu834IOoLeeQm7w0fQ3chW2wFBR9B7Kf7eBKC3eGvVARxqkc01LnlG0OdWJII+l1zn/RB0BL3zEnaHj6C7ka22A4KOoPdS/L0JQG/x1qoDONQim2tc8oygz61IBH0uuc77IegIeuclPDv8XVHf/p759r/t/r659b/NDoSOaQkg6Ah62uLcC6w3Aegt3lp1AIdaZHONS54R9LkViaDPJdd5PwQdQe+8hGeHj6DPRreajgg6gt5LsfcmAL3FW6sO4FCLbK5xyTOCPrciEfS55Drvh6Aj6J2XMOFDoBoBBB1Br1ZcwQP3JgC9xRucrsvDwaEW2VzjkmcEfW5FIuhzyXXeD0FH0DsvYcKHQDUCCDqCXq24ggfuTQB6izc4XQh6LaBJx6XeEfS5pYmgzyXXeT8EHUHvvIQJHwLVCCDoCHq14goeuDcB6C3e4HQh6LWAJh2XekfQ55Ymgj6XXOf9nn2f+zSZwbU3Xt3kvJYFd5PAGp6UXDSEP/PU5GwmuArd1paLa66/vgJFhiwl0JsA9BZvaX6O9YdDLbK5xiXPCPrcikTQ55LrvB+C3nkCA8Jfm2AEIGs+BDlrnoLLAawtFwh6ntrbjaQ3Aegt3lpZh0MtsrnGJc8I+tyKRNDnkuu8H4LeeQIDwl+bYAQgaz4EOWueAgQ9TwqaR5Jh8d0cwjRNnoUkzDJkLH8M97znPc1BPuQhDzG3peF6CfA55c+9h9lFo191dnZ25j/9Onsg6OvM++6skb3+aoCc5cnZ2nLBDvr52kM2LzHxLOJgluczLHMkCHrm7PQZG59T/rx5mCHofr4HeyDoQSA7HmZtgtFxqi6HTs7yZHFtuUDQEfRjV59nEYeg5/kMyxwJgp45O33GxueUP28eZgi6ny+CPk0TL4k7XwZrE4ygS6fpMOSsKf4rTr62XCDoCDqCnufzZ/RIEPTRM7z8/DyyyY3ES/nxMEPQg2qaHfQgkB0PszbB6DhV7KAnTN7arh8EHUFH0BN+EA0aEoI+aGIbTssjmwg6gt6sVBH0ZujTnHhtgpEGfEEg5KwAXnDXteUCQUfQEfTgDxGGm46JuOfFbxFjkIrxCSDo/hx7mLGD7ud7sAeCHgSy42HWJhgdp4od9ITJW9v1g6Aj6Ah6wg+izkOKkOuIMTrHSPgGAh7ZZAedHXRDSdVpgqDX4drTqGsTjJ5ycyxWcpYni2vLBYKOoCPoeT5/RokkQq4jxhiFJ/M4TgBB91eHhxk76H6+7KDzkriDNbA2wQi6dJoOQ86a4r/i5GvLBYKOoCPoeT5/RokkQq4jxhiFJ/NA0CNrAEGPpGkcix10I6iBm61NMEZIJTnLk8W15QJBR9AR9DyfP6NEEiHXEWOMwpN5IOiRNYCgR9I0jpVd0K0/i2ZdJFvHM+Ibolk0u1bjjZAMa322YmyNb4RcWOewtlwg6Ag6gm79dKCdlUCEXEeMYY2Xdv0S8Mgm30G/lGcPs4sq46qzs7Ozfktn2cgR9GV5ZzxbdsGwxpeRrTcmqwBbmbQazzvvntuvLRcI+nKC7lkUeRaSjOtfdML3ErNaHJ785Ccf/Gcg4i3ud7jDHcz/xNSaH+PWrR/4+vmaLwoE3YMqri2CHsey15GyC4Y1vl7578bdSqitjK3xjZAL6xyi2UWPZ52HtR2CjqAfqxXEH/HfrQ1PPSDoLyLn4YaY+sUUvtZ/7efV5EWjs4PuYI+gO2AN2jRaCFqNN0J6rALcirE1vhFyYZ3D2nKBoCPoCPrFnw4IgP9GBYI+T4YQdAR999PIUw/WNY7aeT7TEHQP2QvaIuhBIDseJrtgWOPrOAWXQ7cKsJVJq/FGyIV1DmvLBYKOoCPoCLrl89GzqEfQEfT9mvLUj0dMGddy9V7ZxsMMQffzPdgju6AHTdMtQNHnzTyeVTCi5xAtj9HxtRgvOxNrfC3YtTpn9usnmguCjqAj6Ai65XPFs6hH0BF0BP3wVeW5jjw3KizX8LaNJwYE3UP2grYIehDIjofJLhit4muRUqsAt2Jija8Fu1bnXFsuEPTlBL1VTc89r2cRV2shOTd2+rUlcOwN7BFReV40F3E+xshNgM8pf348zBB0P9+DPRD0IJAdD5NdMFrF1yKlVgFuxcQaXwt2rc65tlwg6Aj6sWvNs4hD0Ft9YuU8L4KeMy8jRsXnlD+rHmYIup8vgj5NE4JxvgyyC0ar+IIuMdcw1vpsxcQan2vSnTdeWy4QdAQdQe/8Qyth+Ah6wqQMGpJHNrmReKkIPMwQ9KALhx30IJAdD5NdMFrF1yKlVgFuxcQaXwt2rc65tlwg6Ag6gt7q02bc8yLo4+Y228w8somgI+jN6hdBb4Y+zYmzC0ar+FokyCrArZhY42vBrtU515YLBB1BR9BbfdqMe14EfdzcZpsZgu7PiIcZO+h+vgd7IOhBIDseJrtgtIqvRUqtAtyKiTW+FuxanXNtuUDQEXQEvdWnzbjnRdDHzW22mXlkkx10dtCb1S+C3gx9mhNnF4xW8bVIkFWAWzGxxteCXatzri0XCDqCjqC3+rQZ97wI+ri5zTYzBN2fEQ8zdtD9fNlB5yVxB2sgu2C0ii/oEnMNYxXgVkys8bkm3XnjteUCQUfQEfTOP7QSho+gJ0zKoCF5ZJMddHbQm10G7KA3Q5/mxNkFo1V8LRJkFeBWTKzxtWDX6pxrywWCjqAj6K0+bcY9L4I+bm6zzQxB92fEw4wddD9fdtDZQWcHPei6qTWMVYDXJoW1eEeMu7ZcIOgIOoIe8cnBGLsEEHTqYSkCHtlkB50d9KXq8tx52EFvhj7NibMLRqv4WiQIQW9BveycrerTWitlszvfG0FH0BH06KuK8RB0amApAgi6n7SHGTvofr7soLODzg560HVTaxirdK1NCmvxjhh3bblA0BF0BD3ik4MxIACBFgQ8sskOOjvoLWp0c86rbv2A0HNnF4zQyTJYEQFq5Tw+mBSV1Ko6t6qVs2fdd1WcLZNlEedfxMHMUlm0gQAEogkg6H6iHmYXjX7V2dnZmf/06+yBoK8z7xlm3UowMsz9WAwwyZydXLG1qhUE/XwdIJsIeq5PB6KBAASOEfDIJp/t/s92BD3o2kPQg0AyjJtAK8FwB7pgB5gsCLvzU7WqFQQdQWfh2/mHB+FDYMUEEHR/8j3MEHQ/34M9EPQgkAzjJtBKMNyBLtgBJgvC7vxUrWoFQUfQEfTOPzwIHwIrJuCRTXbQLxWKhxmCHnRxIehBIBnGTaCVYLgDXbADTBaE3fmpWtUKgo6gI+idf3gQPgRWTMAjmwg6gt7sUkHQm6Ff/YlbCUZm8DDJnJ1csbWqFQQ9Vx1so8mwkPQsfHNSXD4q8rY88xZnJM8tqJefk7yVM9wdgZfEOXgi6A5YNA0l0EowQicRPBhMgoEOPFyrWkHQcxYVC8mceTkVFXk7RWiMv5PnPvNI3mLzhqA7eCLoDlg0DSXQSjBCJxE8GEyCgQ48XKtaQdBzFhULyZx5ORUVeTtFaIy/k+c+80jeYvOGoDt4IugOWDQNJdBKMEInETwYTIKBDjxcq1pB0HMWFQvJnHk5FRV5O0VojL+T5z7zSN5i84agO3gi6A5YNA0l0EowQicRPBhMgoEOPFyrWkHQcxYVC8mceTkVFXk7RWiMv5PnPvNI3mLzhqA7eCLoDlg0DSXQSjBCJxE8GEyCgQ48XKtaQdBzFhULyZx5ORUVeTtFaIy/k+c+80jeYvOGoDt4IugOWDQNJdBKMEInETwYTIKBDjxcq1pB0HMWFQvJnHk5FRV5O0VojL+T5z7zSN5i84agO3hmF/ToRahlvGtvvNpE0DKWBmK8wzij+ZmSlrxRNBPGO5zwyGsycqyWnxfWSwNBt5Jath0LyWV5R52NvEWRzD0Oec6dn2PRkbfYvCHoDp4I+nlYoyy41yZnjrJP23RtORthvqN8XlgvCgTdSmrZdiwkl+UddTbyFkUy9zjkOXd+EPRl8oOgOzgj6Aj62gTDcXks3nQEYW25C9yC39quHwR98Y8F0wkRABOmdI3IW7qUVAmIPFfBWn1Q8haLGEF38ETQEfS1CYbj8li8aQvBXJtQR893bdcPgr74x4LphCwkTZjSNSJv6VJSJSDyXAVr9UHJWyxiBN3BE0FH0NcmGI7LY/GmCPph5JlrNHNsnpsR1mJH0K2klm3nWUjecMMN5uBqjWsOYPCGtfhmGHfw1E0ZrqNaeR49d9b51eJba1zrvFq1Q9Ad5LMLumMqNO2MQLSMlkz/MR/9Qye73/mhdz3ZprRBJialc6F/XQKtagVBr5vXuaPXWvDVGnfuPEfrV4tvhnFHy9X+fBD00TM8TRmuI0+dZc8Igu7IEILugEXTUAKtBOPQJBD00NQy2AIEWl0/CPoCyZ1xChaSM6Al6NJb3jzxJsBbNQSPOHm4ZRi3KriOBidvsclC0B08EXQHLJqGEmglGNtJWKT82IRr7aa3ZhKaYAarSqBVrSDoVdM6e3AWkrPRNe3YW9488TYFu8DJM4i0Jx+eeBfA18UpavGtNW52qAi6I0MIugMWTUMJtBIMBD00jQzWiECr6wdBb5TwE6etteCrNW5OistHVYtvhnGXp7nsGT3CmyEfnniXJZn3bOQtNjcIuoMngu6ARdNQAq0EA0EPTSODNSLQ6vpB0BslPFDQa80AAfCT9QiAf3RbD0/eMsRrm1X9Vr1x88Rbn14fZ8hQ7yPlDUF31D2C7oBF01ACrQRj+2j7ocfULX/bhRD9qHsrJqGJZbBFCLSqFQR9kfS6T8JC0o0sRYfe8pYh3hSJm6Zqb3GvNb+RRK8Wo/1xM9T7SHlD0B2Vi6A7YNE0lEArwbBI+EXyjqCHlgGDzSTQ6vpB0GcmrHI3FpKVAVcavre8ZYi3Uircw3rEKQM3T7xuGIN2IG+xiUXQHTwRdAcsmoYSaCUYh14OtxXyuX+LAtOKSVT8jLMcgVa1gqAvl2PPmVhIemjladtb3jLEmyV7HuHNwM0TbxbGreMgb7EZQNAdPBF0ByyahhJoJRhzJfyiflFgWjGJip9xliPQqlYQ9OVy7DkTC0kPrTxte8tbhnizZM8jvBm4eeLNwrh1HOQtNgMIuoMngu6ARdNQAq0EA0EPTSODNSLQ6vpB0Bsl/MRpWUjmzMupqHrLW4Z4TzFd6u8e4c3AzRPvUgyzn4e8xWYIQXfwRNAdsGgaSqCVYPAd9NA0MlgjAq2uHwS9UcIR9JzgC6PqTQAyxFuIPKy7R3gzcPPEGwap84HIW2wCEXQHTwTdAYumoQRaCQaCHppGBmtEoNX1g6A3SjiCnhN8YVS9CUCGeAuRh3X3CG8Gbp54wyB1PhB5i00ggu7g+ez73MfROq7ptTdeHTeYYyTrotYxZPdNs+ciOr5Dj7jPTeLafmaN6+d8pUTXp7UWW+Ximuuvt4ZIuwUJsJBcEHbgqXrLW4Z4A/EXDeUR3gzcPPEWgRmoM3mLTSaC7uCJoDtgDdo0u2BEx4egzy/kVlI4P+L6PaPr0xpxq1wg6NYMLduOheSyvKPO1lveMsQbxb50HI/wZuDmibeUzSj9yVtsJhF0B08E3QFr0KbZBSM6PgR9fiG3ksL5EdfvGV2f1ohb5QJBt2Zo2XYsJJflHXW23vKWId4o9qXjeIQ3AzdPvKVsRulP3mIziaA7eCLoDliDNs0uGNHxIejzC7mVFM6PuH7P6Pq0RtwqFwi6NUPLtmMhuSzvqLP1lrcM8UaxLx3HI7wZuHniLWUzSn/yFptJBN3BE0F3wBq0aXbBiI4PQZ9fyK2kcH7E9XtG16c14la5QNCtGVq2HQvJZXlHna23vGWIN4p96Tge4c3AzRNvKZtR+pO32Ewi6A6eCLoD1qBNswtGdHwI+vxCbiWF8yOu3zO6Pq0Rt8oFgm7N0LLtWEguyzvqbL3lLUO8UexLx/EIbwZunnhL2YzSn7zFZhJBd/BE0B2wBm2aXTCi40PQ5xdyKymcH3H9ntH1aY24VS4QdGuGlm3HQnJZ3lFn6y1vGeKNYl86jkd4M3DzxFvKZpT+5C02kwi6gyeC7oA1aNPsghEdH4I+v5BbSeH8iOv3jK5Pa8StcoGgWzO0bDsWksvyjjpbb3nLEG8U+9JxPMKbgZsn3lI2o/Qnb7GZRNAdPBF0B6xBm2YXjOj4EPT5hdxKCudHXL9ndH1aI26VCwTdmqFl27GQXJZ31Nl6y1uGeKPYl47jEd4M3DzxlrIZpT95i80kgu7giaA7YA3aNLtgRMeHoM8v5FZSOD/i+j2j69MacatcIOjnM1RrEedZUNeKwVqPtOuXAHU2L3dwm8eNXj4CGerME8NFs0PQHblH0B2wBm2aXTBqxbcr6nd+6F032d3+t+3/3/PfIsrDKl21mJyagzW+U+OM9Pe15QJBR9BHun6ZyzR5Ft/cCHpRxcCNq2cJAhnqzBMDgh5UFQh6EMiOh8kuGLXiQ9D9RYugn2dWqz5PZadVLhB0BP1UbfL3vgh4Ft8IOoLeV3X3H22G69MTA4IeVHMIehDIjofJLhi14kPQ/UXbSgr9kS7Xo1Z9nppBq1wg6Aj6qdrk730R8Cy+EXQEva/q7j/aDNenJwYEPajmsgu6dRFqXSRbxwvC28Uw0exajdcF7BNBWuuzFWNrfCPkwjqHteUCQUfQrdcG7fog4Fl8I+gIeh9VPU6UGa5PTwwIelDtIehBIDseJrtgWOPrOAWXQ7cKsJVJq/FGyIV1DmvLBYKOoFuvDdr1QcCz+EbQEfQ+qnqcKDNcn54YEPSg2kPQg0B2PEx2wbDG13EKEPSOk2etz1FuliDoCHrHlyuhHyDgWXwj6Ag6F9GyBDJcn54YEPSg+kDQg0B2PEx2wbDG13EKEPSOk2etTwS94ySfCL2WtHgWRbViGDdrzGxLgDqbVwtwm8eNXj4CGerMEwOC7svv0dYIehDIjofJLhjW+DpOAYLecfKs9Ymgd5xkBH3c5DEzfmZtZg14pIUbaDMh0y3F9empdQQ9qGizC3rQNN0CFH3ezONZBSN6DlZhiT7vCOORszxZXFsueMT9fO3VWnx7FkW1YshzpRFJLQLU2TyycJvHjV4+AhnqzBMDgu7L79HWCHoQyI6HWZtgdJyqy6GTszxZXFsuEHQEPc/VRyQRBDyLb24EvYg43CKqjzFOEchQZ54YEPRTGTX+HUE3ghq42doEY4RUkrM8WVxbLhD0/gXds9jyCBnjXqoNOOT5fK4ZCXmm3nfrq7d68Fwbnrkh6B6yF7RF0INAdjzM2gSj41Sxg54weWu7fhB0BP3YZehZxCH+lyiOzCHhx3VoSNQ7go6g+y6pq87Ozs58XdbbGkFfb+63M1+bYIyQcXKWJ4trywWCjqAj6Bd//iBueT6fa0ZCnhF0BN13hSHoDl4IugPWoE3XJhgjpJGc5cni2nKBoCPoCDqCnucTuF0kCDqCjqD7rj8E3cELQXfAGrTp2gRjhDSSszxZXFsuEHQEHUFH0PN8AreLBEFH0BF03/WHoDt4IegOWIM2XZtgjJBGcpYni2vLBYLev6DnuXqIJAOBWqIZMbd73ete5mEe/OAHm9tGNMzMLWJ+jJGDQIY688RwETUE3VFTCLoD1qBN1yYYI6SRnOXJ4tpygaAj6HmuPiKJIOBZfHteahcRG4IeQZExeiaQ4fr0xICgB1Ubgh4EsuNh1iYYHafqcujkLE8W15YLBB1Bz3P1EUkEAc/iG0F/EfHM3CLqgjFyEMhQZ54YEPSgukHQg0B2PMzaBKPjVCHoCZO3tusHQUfQE16GhFRAwLP4RtAR9IJSo+sMAhmuT08MCPqMJB/qgqAHgex4mLUJRsepQtATJm9t1w+CjqAnvAwJqYCAZ/GNoCPoBaVG1xkEMlyfnhgQ9BlJPtTlqls/IGikPoa56brnnAy01YL7ZGA0gAAEFiXA58V53GfPuu+iOejhZLWkxbMoqhVDD/yJsYxA5jrjO+hluaV3/wQyXJ+eGBD0oJpD0M+DRNCDiothINA5AQQdQbeUcC059iyKasVgmT9t+iaQoc6OibjnzewRY3gymYGbJ17a9kkgQ515YkDQg+oMQUfQg0qJYSAwHAEEHUG3FHUtOfYsimrFYJk/bfomkKHOIuQ6YgxPJjNw88RL2z4JZKgzTwwIelCdIegIelApdT3Mb3/tk47G/2b3u+Pmb7tttv+t60kT/EkCCDqCfrJIpmmqJceeRVGtGCzzp03fBDLUWYRcR4zhyWQGbp54adsngQx15okBQQ+qMwQdQQ8qpa6HQdC7Tl+14BF0BN1SXLXk2LMoqhWDZf606ZtAhjqLkOuIMTyZzMDNEy9t+ySQoc48MSDoQXWGoCPoQaXU3TAXSfmhyezumm/7spPeXdpdASPoCLqlYGrJsWdRVCsGy/xp0zeBDHUWIdcRY3gymYGbJ17a9kkgQ515YkDQg+oMQUfQg0qpu2EQ9O5StnjACDqCbim6WnLsWRTVisEyf9r0TSBDnUXIdcQYnkxm4OaJl7Z9EshQZ54YEPSgOkPQEfSgUupuGAS9u5QtHjCCjqBbiq6WHHsWRbVisMyfNn0TyFBnEXIdMYYnkxm4eeKlbZ8EMtSZJwYEPajOEHQEPaiUuhvG8r1zTepQOx5t7y7dswJG0BF0S+HUkmPPoqhWDJb506ZvAhnqLEKuI8bwZDIDN0+8tO2TQIY688SAoAfVGYKOoAeVUnfDIOjdpWzxgBF0BN1SdLXk2LMoqhWDZf606ZtAhjqLkOuIMTyZzMDNEy9t+ySQoc48MSDoQXWGoCPoQaU0xDDWx97ZQR8i3ScngaAj6CeLhJ9ZsyCiTWICnsV3rRtBEXIdMYYnTRm4eeKlbZ8EMtSZJwYEPajOEHQEPaiUhhgGQR8ijWGTQNARdEsx1ZIWz6KoVgyW+dOmbwIZ6ixCriPG8GQyAzdPvLTtk0CGOvPEgKAH1RmCjqAHldIQwyDoQ6QxbBIIOoIeVkyVB8og6FGLuMqoUg3fW95qxXtMriOS9eAHPzhimHNjeOq9FjfPxDzxesYduS15i83uVWdnZ2exQ447WrSgWxa0onntjVeboLYYL3Ns2dmNFN+2QC96SVyL+hyJ8Qj8Rvm8MH0gT9N09qz7WpvSbkECLCQXhB14qt7yViteBD2wqI4MhaD7Gdeqd08kI+UNQXdkHkE/D2uUBfcI8tNSRhH089cGNTXu54X1nw0E3Upq2XYsJJflHXW23vJWK14EPaqijo8zkujVp3XpDLXq3RP/SHlD0B2ZR9DHXXAjU4cvBOsNGAQdQbdcQ9Z6soyV4YbUqX8+EPRThNr8nYVkG+6lZ+0tb7XiRdBLK+l0/5FE7/RsY1rUqndPdCPlDUF3ZB5BR9BHEYxoAULQEXRLTY1y/Vj/2UDQraSWbcdCclneUWfrLW+14kXQoyqKHfRIkrXq3RMjgu6hNVDbaEHPjiZywZ19rsTnJ8BL4vzMRu7B58X57CLoOSuehWTOvJyKqre81YoXQT9VKeV/H0n0ymnYRqhV77azX2o1Ut7YQXdkHkE/D8u6I+bATNNOCCDonSRqoTARdAR9oVIrPg0LyWKETQboLW+14kXQ65ffSKJXn9alM9Sqd0/8I+UNQXdkHkFH0B3lMnxTBH34FLsmiKAj6K6CadiYhWRD+AWn7i1vteJF0AuKyNh1JNEzTrm4Wa169wQ2Ut4QdEfmEXQE3VEuQzW1yvihSb/Z/e44FAsmc5gAgo6g93JtsJDsJVNXxtlb3mrFi6DXr9+RRK8+rUtnqFXvnvhHyhuC7sg8go6gO8plqKYI+lDprDIZBB1Br1JYFQZlIVkB6gJD9pa3WvEi6PWLbSTRq08LQa/BGEF3UEXQEXRHuQzV1Cvo7JoPlX7TZBB0BN1UKAka1RInz9QQAA+tPgWgVp0h6P7a8fbg+vQSYwfdT+ziHgi6gyiCjqA7ymWopgj6UOmsMhkEHUGvUlgVBvWIk2ehXmvcCgi6HLIW3wzjdpkQR9AZrqNaeXZgGLppLb61xs2eDATdkSEEHUF3lMtQTRH0odJZZTIIOoJepbAqDFprwVdr3AoIuhyyFt8M43aZEEfQCLoDVqdNM1xHnjrLjhlBd2QIQUfQHeUybNOLZJ1H24dN+8mJIegI+skiSdKAhWSSRDjD6C1vnnidKLpr7hEnD7cM43aXjEoBk7dYsAi6gyeCjqA7ymXYpgj6sKktmhiCjqAXFdCCnVlILgg78FS95c0TbyCmlENlEGlPPjzxpgTeIKiS7LsHAAAgAElEQVRafGuN2wCR65QIugMXgo6gO8qFphBYFQEEHUHvpeBrLfhqjdsL19px1uKbYdza7FqP7xHeDPnwxNuabZbzk7fYTCDoDp4IOoLuKBeaQmBVBBB0BL2Xgmch2Uumroyzt7x54u0zI/aoPcLr4ZZhXDuFsVuSt9j8IugOngg6gu4oF5pCYFUEEHQEvZeC9ywka83JIxa1YuhtXPLWW8bmxUue53Fr3Yu8xWYAQXfwfPZ97uNoHdf02huvjhvMMVLmBbclNk01M7se4nOUS1hTcnYY5Qg1nz23YUX8woGuuf766CEZL4AAC8kAiA2GIG8NoDc4JXluAD3glOQtAOLOEAi6gyeCfh5W9gU38ZXJnuPyCGtKzspylplf5tjCCnhnIAS9BtXyMVlIljNsMQJ5a0F9+XOS5+WZR5yRvEVQfNEYCLqDJ4KOoFvLZYTdTutco9tllzjim38DITu76FpG0KOJxozHQjKG49KjkLelibc5H3luw730rOStlOCV/RF0B08EHUG3lguCbiVFTVlJjVBTCLo127SrSYCFZE269cYmb/XYZhqZPGfKhj0W8mZnZWmJoFsovbANgo5MWctlBJmyzjW6XXaJI77DGbfUfHZ20bXMDno00ZjxWEjGcFx6FPK2NPE25yPPbbiXnpW8lRK8sj+C7uCJoCPo1nKxyIrGWpuwWPhlZ0J8CLqljtUGQbeSWrYdC8lleUedjbxFkcw9DnnOnZ9j0ZG32Lwh6A6eCDqCbi0XBN1KipqykhqhprLf3LDmwtoOQbeSWrYdC8lleUedjbxFkcw9DnnOnR8EfZn8IOgOzgg6MmUtlxFkyjrX6HbZJY74DmfcUvPZ2UXXMoIeTTRmPAQghuPSo5C3pYm3OR95bsO99KzkrZTglf0RdAdPBB1Bt5aLRVY01tqExcIvOxPiQ9Atdaw2CLqV1LLtWEguyzvqbOQtimTucchz7vwci468xeYNQXfwRNARdGu5IOhWUtSUldQINZX95oY1F9Z2CLqV1LLtWEguyzvqbOQtimTucchz7vwg6MvkB0F3cEbQkSlruYwgU9a5RrfLLnHEdzjjlprPzi66lhH0aKIx4yEAMRyXHoW8LU28zfnIcxvupWclb6UEr+yPoDt4IugIurVcLLKisdYmLBZ+2ZkQH4JuqWO1QdCtpJZtx0JyWd5RZyNvUSRzj0Oec+fnWHTkLTZvCLqDZ3ZBj5ZCy3hWWbGM5RFWxjtcuNH5cFweYU2j58B484U6+prMnouwIn7hQAh6NNGY8VhIxnBcehTytjTxNucjz224l56VvJUSvLI/gu7giaCfh5V9wU18ZXLmuDzCmpKzspxl5pc5trAC3hkIQa9BtXxMFpLlDFuMQN5aUF/+nOR5eeYRZyRvERRfNAaC7uCJoCPo2QUjOj7H5RHWNHoOjJdH+LPnIqyIXzgQgh5NNGY8FpIxHJcehbwtTbzN+chzG+6lZyVvpQSv7I+gO3gi6Ah6dsGIjs9xeYQ1jZ4D4yHo1q/EhBUxgn4UZYZFXHSe54x3ww03mLvBzIxq1Q0f9rCHFc//bne7W/EYDDAOAT6n/Ln0MLtodATdwR5BR9DXJnuOyyOs6doYr2m+2ecaVsQIOoJ+opg8izgEPfrKHHM8BH3MvLacFZ9TfvoeZgi6n+/BHtkFPWial4ex7DpZF9wtYtM5ie8weUtuo3NmHY+cleUsM7/MsVnr09OOR9zP00I2LzHxLOJg5rnq1tsWQV9v7mvNnM8pP1kPMwTdzxdBn6bJInHZF9zEVyZ7QZeOaxhyVpazzPwyx+YqUmNjBB1BP1YqnkUcgm684FbeDEFfeQFUmD6fU36oHmYIup8vgo6gF1WN5eaGTrA2YbFAzc6E+ObfQMjOzlKfnjYIOoKOoHuuGNqWEEDQS+jR9xABj2xyI/ESQQ8zBD3ouuMR9/Mgsy+4iW++TAVdNu5hyFlZzjLzyxybu1ANHRB0BB1BN1woNAkhgKCHYGSQHQIe2UTQEfRmFw+CjqBbi48ddCspaspKaoSaQtCt2R63HYs4/yIOZuNeD5EzOyTox97KfkzmeYt7ZEb6HwtB9+fQw4wddD/fgz0QdGTKWkojyJR1rtHtsksc8R3OuKXms7OLrmV20NlBZwc9+qpivGMEEHRqI5qARza5kei/+YqgB1Usgo6gW0vJIisaa23CYuGXnQnxIeiWOlYbBB1BR9CtVwvtSgkg6KUE6b9PAEH314SHGYLu53uwB4KOoFtLCUG3kqKmrKRGqKnsNzesubC2Q9ARdATderXQrpQAgl5KkP4IenkNIOjlDN0jIOjIlLVoRpAp61yj22WXOOI7nHFLzWdnF13LCDqCjqBHX1WMd4wAgk5tRBPwyCaPuF+i72F2Ub6uOjs7O4tO6KjjIegIurW2LbKisdYmLBZ+2ZkQH4JuqWO1QdCXE3TPosizkGRc/6ITvpeYZeDgkXm19ShBhvlxfXJ97tZ4rXqw/puPoHtIBbZF0BF0azkh6FZS1JSV1Ag1lf3mhjUX1nYIOoJ+rFZqLSQZF2GxfD4de4s7gk79LCG8vX1OWa6pbRvP3C4alx10B3UEHZmylssIMmWda3S77BJHfIczbqn57OyiaxlBR9AR9IuvKs9ilh3bSyw9HI7RR9CpS8u/d1yfFkpXtvEwQ9D9fA/2QNARdGspWWRFY61NWCz8sjMhPgTdUsdqg6Aj6Ag6ImT5vPAs6hH0FxGtxY1xLzGGg+XqRdD9lIJ7WAW91QI+eLqphxtFgKmVPGVGTeXJRfZIrLWCoCPoCDqCbvk8qyVCx87NDjp12bIua9V7rXEtrLZtPDFcNC6PuDuoI+gOWJWbWhfIrQQ4e3yV09Pl8Nlzlj2+LpM+M2hrLhB0BB1BR4QsHzOeRT076C8iWosb415iDAfL1XtlGw8zBN3P92APBD0IZMAw1gUygh4AeyVDUFMrSXTANK21gqAvJ+gBaV10CM8iLkLIFp0cJ0tD4NhO+bEA73a3u6WJnUDaE+Bzyp8DDzME3c8XQQ9iVmsY6wIZQa+VgfHGpabGy2mtGVlrBUFH0I/VoGcRh6DXupLHHxdBHz/HNWfI55SfrocZgu7ni6AHMas1jHWBjKDXysB441JT4+W01oystYKgI+gIeq2rkHEtBBB0CyXa8DkVVwMIehxL80g84m5GVb2hdYGMoFdPxTAnoKaGSWX1iVhrBUFH0Fn4Vr8cOcEFBBB0yqOEgEc2edLnEmkPs4tyw0viHJWLoDtgVW5qXSAj6JUTMdDw1NRAyaw8FWutIOgIOoJe+WJk+AsJIOgUSAkBj2wi6Ah6Sa0V9UXQi/CFdrYukBH0UOxDD0ZNDZ3e0MlZawVBR9AR9NBLj8EuIOCRcV4GRylZCCDoFkpXtvEwu2h0dtAd7BF0B6zKTa0LZAS9ciIGGp6aGiiZladirRUEHUFH0CtfjAx/mQCCTjFEE/DIJjvol+h7mCHoQRWLoAeBDBjGukBG0ANgr2QIamoliQ6YprVWEHQEHUEPuOAYwkQAQTdhopGDgEc2EXQE3VFasU0R9FieJaNZF8gIegnldfWlptaV75LZWmsFQUfQEfSSK42+HgIIuocWbS0EEHQLpSvbeJhdNDqPuDvYI+gOWJWbWhfICHrlRAw0PDU1UDIrT8VaKwg6go6gV74YGf4yAQSdYogm4JFNdtAv0fcwQ9CDKjZa0K2LPKtkthgvc2xKO/EdLv7oWrFcYtHnZLzD1DPXfObYanxeIOgIOoJu+deBNhEEEPQIioyxS8Ajmwg6gt7s6kHQz6Nf24Kb+c6//BDq/oQ6WlrXdv0g6Ag6gj7/3wx6QgACbQkg6H7+HmbsoPv5HuyBoCPoaxMM63wtlxiCjqBb62mUWkHQEXQE3fKvA20gAIGMBDyyyQ46O+jNahhBR9DXJhjW+VouylGky8qE+fJ5gaAj6Ai65V8H2kAAAhkJIOj+rHiYsYPu58sO+jRNFsFAVg4Xl4Wdeo7Cz3KJrY0J80XQEXQEHUG3/OtAGwhAICMBj2yyg84OerMajt5BbzaRAU4cLT/RSLLHFz3fEcbLnrPs8Y1QA9Y5WHOBoCPoCLr1qqIdBCCQjQCC7s+Ihxk76H6+i+ygB4W1ymGsC2TrDnU0xOzxRc93hPGy5yx7fCPUgHUO1lwg6Faiy7bLsNPjWcQRr78+Rubrp1GnB3Xp5zpyXfZWD/7sLduD30F38GYH3QGrclPrAhlBr5yIgYanpgZKZuWpWGsFQa+ciJnD97aQJF5/okcWIT+NOj2oSz/Xkeuyt3rwZ2/ZHgi6gzeC7oBVual1gYygV07EQMNTUwMls/JUrLWCoFdOxMzhe1tIEq8/0SOLkJ9GnR7UpZ/ryHXZWz34s7dsDwTdwRtBd8Cq3NS6QEbQKydioOGpqYGSWXkq1lpB0CsnYubwvS0kidef6JFFyE+jTg/q0s915LrsrR782Vu2B4Lu4I2gO2BVbmpdICPolRMx0PDU1EDJrDwVa60g6JUTMXP43haSxOtP9Mgi5KdRpwd16ec6cl32Vg/+7C3bA0F38EbQHbAqN7UukBH0yokYaHhqaqBkVp6KtVYQ9MqJmDl8bwtJ4vUnemQR8tOo04O69HMduS57qwd/9pbtgaA7eCPoDliVm1oXyAh65UQMNDw1NVAyK0/FWisIeuVEzBy+t4Uk8foTPbII+WnU6UFd+rmOXJe91YM/e8v2QNAdvBF0B6zKTa0LZAS9ciIGGp6aGiiZladirRUEvXIiZg7f20KSeP2JHlmE/DTq9KAu/VxHrsve6sGfvWV7IOgO3gi6A1blptYFMoJeOREDDU9NDZTMylOx1gqCXjkRM4fvbSFJvP5EjyxCfhp1elCXfq4j12Vv9eDP3rI9EHQHbwTdAatyU+sCGUGvnIiBhqemBkpm5alYawVBr5yImcP3tpAkXn+iRxYhP406PahLP9eR67K3evBnb9keCLqDN4LugFW5qXWBjKBXTsRAw1NTAyWz8lSstYKgV07EzOF7W0gSrz/RI4uQn0adHtSln+vIddlbPfizt2wPBN3BO7ugWxeNjimfbIoAn0SUskGLWrGCaFVT1vhatbPmrBU/a3yR/LLPFUGPzHbcWJ6FZK0FNeNeyicc/BziroSykbiOLvGDQ58cyqq/fm8E3cEYQT8PK/sCuVV8jrJq0rSFTFknSs4Ok7LmrBU/a3zWOrC0yz5XBN2SxeXbsKDuc0FN3pa/Vi46I/ngOtqtj97qIdfVdD4aBN2RIQQdQXeUS+qmLWTKCqSVdFnja9XOmrNW/KzxRfLLPlcEPTLbcWP1tpAkXkQorvrjRqIuqUsEPe562h8JQXewRdARdEe5pG7aQqasQFpJlzW+Vu2sOWvFzxpfJL/sc0XQI7MdNxZigVggFuXXE9cR1xHXUfl1dGwEBN3BFkFH0B3lkrppC5myAmklXdb4WrWz5qwVP2t8kfyyzxVBj8x23FiIBWKBWJRfT1xHXEdcR+XXEYIewBBBR9ADyijFEC1kyjrxVtJlja9VO2vOWvGzxhfJL/tcEfTIbMeNhVggFohF+fXEdcR1xHVUfh0h6AEMEXQEPaCMUgzRQqasE28lXdb4WrWz5qwVP2t8kfyyzxVBj8x23FgesYg765Uj1Xp7OfFeIjAy31o59o7LdeQlNnZd9lYP/uwt24NH3B28EXQE3VEuqZu2kCkrkFbSZY2vVTtrzlrxs8YXyS/7XBH0yGzHjdXbQpJ4/blH0P3MvD2oSy8xBN1PzNfDc937Rl6+NYLuYI6gI+iOckndtIVMWYG0ki5rfK3aWXPWip81vkh+2eeKoEdmO24sxMLP0rPwhW9dvv7R6/Qgz36uXEd+Zp4eHr6ecVu0RdAd1BF0BN1RLqmbtpApK5BW0mWNr1U7a85a8bPGF8kv+1wR9Mhsx42FWPhZeha+8K3L1z96nR7k2c+V68jPzNPDw9czbou2CLqDOoKOoDvKJXXTFjJlBdJKuqzxtWpnzVkrftb4IvllnyuCHpntuLEQCz9Lz8IXvnX5+kev04M8+7lyHfmZeXp4+HrGbdEWQXdQR9ARdEe5pG7aQqasQFpJlzW+Vu2sOWvFzxpfJL/sc0XQI7MdNxZi4WfpWfjCty5f/+h1epBnP1euIz8zTw8PX8+4Ldoi6A7qCDqC7iiX1E1byJQVSCvpssbXqp01Z634WeOL5Jd9rgh6ZLbjxkIs/Cw9C1/41uXrH71OD/Ls58p15Gfm6eHh6xm3RVsE3UE9WtCtC1rrItQ6nmPKJ5tGx8Z4h5FbcxvN72QBVGgQPQfGG7emsucWQa/wAREwJGLhh+hZ+MK3Ll//6HV6kGc/V64jPzNPDw9fz7gt2iLoDuoI+nlY2RfIxFcmZ47LI6wpOSvLWXZ+YYUyTVP2uSLokdmOGwux8LP0LHzhW5evf/Q6PciznyvXkZ+Zp4eHr2fcFm0RdAd1BB1Bzy4E0fE5Lo+wptFzYLxcwh9WKAh6JMpVjYVY+NPtWfjCty5f/+h1epBnP1euIz8zTw8PX8+4Ldoi6A7qCDqCvjbZc1weYU3Xxnht8w0rFAQ9EuWqxkIs/On2LHzhW5evf/Q6PciznyvXkZ+Zp4eHr2fcFm0RdAd1BB1BR6YcF8zMpmtjvLb5ziyLg92ys+MR98hsx42FWPhZeha+8K3L1z96nR7k2c+V68jPzNPDw9czbou2CLqDerSgO05tamp9kZhpMGMj6wLZOJy5mXWureIzT6RRQyu/FuGRs8PUrTlrxc8aX2RNZZ8rgh6Z7bixEAs/S8/CF751+fpHr9ODPPu5ch35mXl6ePh6xm3RFkF3UEfQz8PKvkBuFZ+jrJo0bSFT1omSMwQ9e61Yrx8E3ZrJZdshFn7enoUvfOvy9Y9epwd59nPlOvIz8/Tw8PWM26Itgu6gjqAj6I5ySd3UKhgtJoGgI+jWumtVK9brB0G3ZnLZdoiFn7dn4Qvfunz9o9fpQZ79XLmO/Mw8PTx8PeO2aIugO6gj6Ai6o1xSN7UKRotJtJKuFnP1nNOas1b8rPF55nyqbfa5IuinMtjm74iFn7tn4Qvfunz9o9fpQZ79XLmO/Mw8PTx8PeO2aIugO6gj6Ai6o1xSN20hU1YgraTLGl+rdtacteJnjS+SX/a5Iuhl2c4gAGUzGKe3Z+GbIW+9xTtOpSw7k97y3Fu8y2Yz79k8eYuaBYLuIImgI+iOckndtIVMWYG0ki5rfK3aWXPWip81vkh+2eeKoJdlO4Polc1gnN6eBWqGvPUW7ziVsuxMestzb/Eum828Z/PkLWoWCLqDJIKOoDvKJXXTFjJlBdJKuqzxtWpnzVkrftb4IvllnyuCXpbtDKJXNoNxensWqBny1lu841TKsjPpLc+9xbtsNvOezZO3qFkg6A6SCDqC7iiX1E1byJQVSCvpssbXqp01Z634WeOL5Jd9rgh6WbYziF7ZDMbp7VmgZshbb/GOUynLzqS3PPcW77LZzHs2T96iZoGgO0gi6Ai6o1xSN20hU1YgraTLGl+rdtacteJnjS+SX/a5Iuhl2c4gemUzGKe3Z4GaIW+9xTtOpSw7k97y3Fu8y2Yz79k8eYuaBYLuIImgI+iOckndtIVMWYG0ki5rfK3aWXPWip81vkh+2eeKoJdlO4Polc1gnN6eBWqGvPUW7ziVsuxMestzb/Eum828Z/PkLWoWCLqDJIKOoDvKJXXTFjJlBdJKuqzxtWpnzVkrftb4IvllnyuCXpbtDKJXNoNxensWqBny1lu841TKsjPpLc+9xbtsNvOezZO3qFkg6A6SCDqC7iiX1E1byJQVSCvpssbXqp01Z634WeOL5Jd9rgh6WbYziF7ZDMbp7VmgZshbb/GOUynLzqS3PPcW77LZzHs2T96iZoGgO0gi6P0JuiO9TZpmF4wWUGBSRn1N/LLPFUEvq+UMolc2g3F6exaoGfLWW7zjVMqyM+ktz73Fu2w2857Nk7eoWSDoDpIIOoLuKBdT0+yCYZpEcCOYlAFdE7/sc0XQy2o5g+iVzWCc3p4Faoa89RbvOJWy7Ex6y3Nv8S6bzbxn8+QtahYIuoMkgo6gO8rF1DS7YJgmEdwIJmVA18Qv+1wR9LJaziB6ZTMYp7dngZohb73FO06lLDuT3vLcW7zLZjPv2Tx5i5oFgu4gGS3o1u9sWheh1vEcUz7Z1BrbyYGcDVrM1RmiqTn8uOljKhRHozXVVPa5IuiOwj3Q1CN6ngUU416CDYfxOXivQK6jS8TgAIfda8dTD95r7lh7BN1BEkFHphzlYmqaXTBMkwhuBJMyoGvil32uCHpZLSOQ4wukZ+FLPfjrwXsFkg/EdK6Ycn16r7aL2yPoDp4IOoLuKBdT0+yCYZpEcCOYlAFdE7/sc0XQy2qZBZ9fyBAsBKvkqqN+qB8E/fwV5LkuSq6/3b4IuoMkgo6gO8rF1DS7YJgmEdwIJmVA18Qv+1wR9LJaRtAR9N0Koh789eC9Aj0iQj78+YDv+DdAvNfcsfYIuoMkgo6gO8rF1DS7YJgmEdwIJmVA18Qv+1wR9LJaRgAQAAT9/DXkuS68VyACOb5AeuqHevDXg/eaQ9ADiCHoCHpAGV0xRHbBiJ6vZTyYWCgdb7MmftnniqCX1TILSQQdQUfQL/oUQSD9Asnnat3P1bJ/9V7Umx10B0kEHUF3lIupaXbBME0iuBFMyoCuiV/2uSLoZbXsWUiWnYnepwjUEqFT5537997inTvPtffrLc+9xbv2+trO35O3KGYIuoMkgo6gO8rF1DS7YJgmEdwIJmVA18Qv+1wR9LJaRtDL+EX29ixQM+Stt3gjc7WmsXrLc2/xrqmWLpqrJ29RzBB0B0kEHUF3lIupaXbBME0iuBFMyoCuiV/2uSLoZbWcQfTKZjBOb88CNUPeeot3nEpZdia95bm3eJfNZt6zefIWNQsE3UESQUfQHeViappdMEyTCG4EkzKga+KXfa4IelktZxC9shmM09uzQM2Qt97iHadSlp1Jb3nuLd5ls5n3bJ68Rc0CQXeQRNDnC/pN1z3HRNq64LaOZzppw0bR840erwWa6DlEj9eCieec0fONHs8zl1NtrbGdGsf7d+vnD4LuJXtl+wyiVzaDcXp7FqgZ8tZbvONUyrIz6S3PvcW7bDbzns2Tt6hZIOgOkgg6gu4oF1NTq2BYhSB6PNMkghtFzyF6vODphg8XPd/o8SInbI0t8pway3o9Iuhl5DOIXtkMxuntWaBmyFtv8Y5TKcvOpLc89xbvstnMezZP3qJmgaA7SCLoCLqjXExNrYJhFYLo8UyTCG4UPYfo8YKnGz5c9Hyjx4ucsDW2yHMi6NE0j4+XQfSWm23uM3kWqBny1lu8ubOfN7re8txbvHkzv2xknrxFRYagO0gi6Ai6o1xMTa2CgaCfx7kmJqZiemGjNdWUda4efpa21tpjB91CE0Evo7RMb88CFUFfJiecZZqoS6pgCQKeOouKB0F3kETQEXRHuZiaWgXDKgTR45kmEdwoeg7R4wVPN3y46PlGjxc5YWtskefUWNbrEUEvI59B9MpmME5vzwI1Q956i3ecSll2Jr3lubd4l81m3rN58hY1CwTdQRJBny/oDsymptYFsmmwho2yC0YLNDApo74mftnniqCX1XIG0SubwTi9PQvUDHnrLd5xKmXZmfSW597iXTabec/myVvULBB0B0kEHUF3lIupaXbBME0iuBFMyoCuiV/2uSLoZbWcQfTKZjBOb88CNUPeeot3nEpZdia95bm3eJfNZt6zefIWNQsE3UEyWtAdpzY1bbGrnH2BbALXsBH8uOkTXX5rqqnsc0XQy6o7g+iVzWCc3p4Faoa89RbvOJWy7Ex6y3Nv8S6bzbxn8+QtahYIuoMkgo5MOcrF1DS7YJgmEdwIJmVA18Qv+1wR9LJaziB6ZTMYp7dngZohb73FO06lLDuT3vLcW7zLZjPv2Tx5i5oFgu4giaAj6I5yMTXNLhimSQQ3gkkZ0DXxyz5XBL2sljOIXtkMxuntWaBmyFtv8Y5TKcvOpLc89xbvstnMezZP3qJmgaA7SCLoCLqjXExNswuGaRLBjWBSBnRN/LLPFUEvq+UMolc2g3F6exaoGfLWW7zjVMqyM+ktz73Fu2w2857Nk7eoWSDoDpIIOoLuKBdT0+yCYZpEcCOYlAFdE7/sc0XQy2o5g+iVzWCc3p4Faoa89RbvOJWy7Ex6y3Nv8S6bzbxn8+QtahYIuoMkgo6gO8rF1DS7YJgmEdwIJmVA18Qv+1wR9LJa7k30ymZ7vHdvHDLEWysXtcZtIQClcyHPfoKePGfg64nXT8PWY60cEHRbfWxaIegIuqNcTE2zC4ZpEsGNYFIGdE38ss8VQS+r5bUuzPap9cYhQ7xllbd87wwi5J01efYSmyZPnjPw9cTrp2HrsVYOCLqtPhD0I5yyL5Ad6W3SFH7c9IkuvDXVVPa5Iuhl1b3WhRmCXlY3PfbOIEJebhmuT2/Mrdt78pyBryfeWmzXygFBd1QUO+jIlKNcTE2zC4ZpEsGNYFIGdE38ss8VQS+r5bUuzBD0srrpsXcGEfJyy3B9emNu3d6T5wx8PfHWYrtWDgi6o6IQdATdUS6mptkFwzSJ4EYwKQO6Jn7Z54qgl9XyWhdmCHpZ3fTYO4MIeblluD69Mbdu78lzBr6eeGuxXSsHBN1RUdkF3TEVmkIAAhCAwEwCN133HFNPBN2E6WijtS7MEPSyuumxdwYR8nLLcH16Y27d3pPnDHw98dZiu1YOCLqjohB0ByyaQgACEBiUAIK+TGLXujBD0Jepr0xnySBCXh4Zrk9vzK3be/Kcga8n3lps18oBQXdUFILugEVTCEAAAoMSQNCXSexaF2YI+jL1leksGUTIyyPD9emNuXV7T54z8PXEW4vtWjkg6I6KQtAdsGgKAQhAYFACCPoyiaEozCoAABtkSURBVF3rwgxBX6a+Mp0lgwh5eWS4Pr0xt27vyXMGvp54a7FdKwcE3VFRCLoDFk0hAAEIDEoAQV8msWtdmCHoy9RXprNkECEvjwzXpzfm1u09ec7A1xNvLbZr5YCgOyoKQXfAoikEIACBQQkg6Mskdq0LMwR9mfrKdJYMIuTlkeH69Mbcur0nzxn4euKtxXatHBB0R0Uh6A5YNIUABCAwKAEEfZnEehZmnoVkrXFrUakVL+NeylhvHGrVmXfc3rgRb91699aPtX1vebPO61Q7BP0UoZ2/I+gOWDSFAAQgMCgBBH2ZxK51YbZPtzcOxNunCHmvavJcN8+98fXWj7X9Wjkg6NYKmaYJQXfAoikEIACBQQkg6Mskdq0LMwT9cH31Vg+14l3m6jt9llrzY9w+xf90xcxr0Vs9zJvl+V4IuoMkgu6ARVMIQAACgxJA0JdJ7FoXZgg6gr7MFVZ2lt6uT+KtK/5l1XS8d295i+KAoDtIIugOWDSFAAQgMCgBBH2ZxK51YYagI+jLXGFlZ+nt+iReBH234mvVQ9lV9aLeCLqDJILugEVTCEAAAoMSQNCXSWytBVStcWtRqRUv49YVllp8a9WZd9xa82Nc6nIJka5VZ97r6Fh7BN1BMlrQrYu8a2+82hRli/EyxyZoxHe4dKJrxVKg0edkvMPUM9d85thqfF5cc/31lkuDNkcIeBZQtSB63g5fK4beOGSIt1Yuao2boc68cyPPXmLT5MlzBr6eeP00bD3WygFBt9XHphWCfh7W2hbczNdxwew1Raj7E+poaV3b9YOgz/+8UM+1Lsz2qfXGIUO8ZZW3fO8MIuSdNXn2EkPQ/cTW++8Agu6oFgQdQV+bYFjna7mMEHQE3VpPo9QKgm75ZDjeJoMAZBCn3jhkiLes8pbvnaHOvLMmz15iCLqfGII+h9nq+iDoCPraBMM6X8uHwSjSZWXCfPm8QNAtnwwI+ilKGUTII5AZ4j3FNNvfPXyzxE6e/Znw5DkDX0+8fhq2HmvlwA66rT42rRB0FtzImeOC2WuKsLKDvrbrB0Gf/3mhnmtdmO1T641DhnjLKm/53hlEyDtr8uwlxg66n9h6/x1A0B3VEi3ojlPTFAIQgAAEkhCw3mxC0MsSlkEAMohTbxwyxFtWecv3zlBn3lmTZy8xBN1PDEGfw2x1fRD01aWcCUMAAhA4RwBBX6YoMghABnHqjUOGeJep0LizZKgz72zIs5cYgu4nhqDPYba6Pgj66lLOhCEAAQgg6I1qIIMAZBCn3jhkiLdRyc4+bYY68wZPnr3EEHQ/MQR9DrPV9UHQV5dyJgwBCEAAQW9UAxkEIIM49cYhQ7yNSnb2aTPUmTd48uwlhqD7iSHoc5itrg+CvrqUM2EIQAACCHqjGsggABnEqTcOGeJtVLKzT5uhzrzBk2cvMQTdTwxBn8NsdX0Q9NWlnAlDAAIQQNAb1UAGAcggTr1xyBBvo5KdfdoMdeYNnjx7iSHofmII+hxmq+uDoK8u5UwYAhCAAILeqAYyCEAGceqNQ4Z4G5Xs7NNmqDNv8OTZSwxB9xND0OcwW10fBH11KWfCEIAABBD0RjWQQQAyiFNvHDLE26hkZ582Q515gyfPXmIIup8Ygj6H2er6IOirSzkThgAEIICgN6qBDAKQQZx645Ah3kYlO/u0GerMGzx59hJD0P3EEPQ5zFbXB0FfXcqZMAQgAAEEvVENZBCADOLUG4cM8TYq2dmnzVBn3uDJs5cYgu4nhqDPYba6Pgj66lLOhCEAAQgg6I1qIIMAZBCn3jhkiLdRyc4+bYY68wZPnr3EEHQ/MQR9DrPV9UHQV5dyJgwBCEAAQW9UAx4B8AhOrXFrYfLEWysGD99aMdQaF77zyMJtHjdrL/heIrVWDlednZ2dWYtl7e0Q9LVXAPOHAAQgME03XfccE4Zrrr/e1I5Ghwl4FmYegaw1bq08euKtFYOHb60Yao0L33lk4TaPm7UXfBF0BN14tVgF3TgczSAAAQhAYGACCHpZcj0LVI9A1hq3bLbHe3virRWDh2+tGGqNC995ZOE2j5u1F3wRdATdeLUg6EZQNIMABCAAgQlBLysCzwLVI5C1xi2bLYJei9+pcT31cGqsuX/31O/cc0T3g1s00SvHgy+CjqAbrzEE3QiKZhCAAAQggKAX1oBngeoRnFrjFk73aHdPvLVi8PCtFUOtceE7jyzc5nGz9oIvgo6gG68WBN0IimYQgAAEIICgF9aAZ4HqEcha4xZOF0GvBfDEuJ56qBWip35rxeAdF25eYr728EXQEXTjNYOgG0HRDAIQgAAEEPTCGvAsUD2CU2vcwun+//buJseW4ggD6OsNsBgkDxkieRksgaV4CUhswhJDhpZYzNvAs/CjzYW+3ZWREVF/eWaWb2ZU5smoW/XRTSOgdwEK6C2ykfuoZQGfYv/Zsq41dNXlK6AL6IN318vLy+BIwwgQIEBgdQH/kZRcB0ReUAX0nPXW7IjvVq2zfR7ps661X9GXW1c3rB1M/666ap/5z6wF7i8BPYBlKAECBBYXENBzDRB5MYsEnK66ud2+Pzuy3q41RHy71tBVl++cLLc5t9FZfNf+BxUC+uid8unTp6MD+i+//OP/q/3++//8738//n/PtvI6LrDN6aGva3m85kfr23Nt05sykcCCAv/+9ts3u/7nb78tKJHbsoCe84u8oEYCZFfd3G4F9C6/rbqRftiqNft5pH9nr1E9j1u16F/r8RXQ/Yr74D0moH8MJaAPNpJhBE4uIKDXHJCAnnOMvKBGAk5X3dxuBfQuv626kX7YqjX7eaR/Z69RPY9btaiA/kx01T7zE/TA/SWgC+iBdjGUwGUFBPSaoxPQc46RF7NIwOmqm9ttTUC/swPfLoG5ul33UVfduV0eN6vLoatul1RkvV1riHyvVq1BQA9IHhXQoz+Zfralrl8nn/m1+z3XFzheQwksL/AazJ/9OrvQHm8PAT1u9jgj8mIWeYHqqpvbrYDe5bdVt6sfuupu7Wevz7v211V3L5eq63Q5dNWt2vff60TW27WGyPOlag0CekBSQH+LJaAHGshQAicXENBrD0hAz3lGXswiL1BddXO7FdC7/LbqdvVDV92t/ez1edf+uuru5VJ1nS6HrrpV+xbQvwoI6IGOOiqgvy7x2R9cO9MfZPtofWf6Y3aBIzeUAIE/BB5/gv76E/Zn/x+wPwUE9Fw3dL1IdtXN7VZA7/LbqtvVD111t/az1+dd++uqu5dL1XW6HLrqVu1bQBfQw70koH9MJqCHW8oEApcRENDjRyWgx80eZ3S9SHbVze1WQO/y26rb1Q9ddbf2s9fnXfvrqruXS9V1uhy66lbtW0AX0MO9JKAL6OGmMYHATQQE9PhBCuhxMwH9rVnXC3VX3dyp7z+7y6Gr7v5Cz6/Ytb+uumdxG11Hl0NX3dF9RcdF1hutPTo+8q9QjdbcGudX3LeEHj4/Y0B/tvyuPwi3RbX1a+yv849a39b6fU6AwPsCW7/O/tG/v76qq4CeO/nIi1nkBaqrbm6378+OrLdrDRHfrjV01eU7J8ttzm10Ft+vUqs6COijd8rv/8L+y0tgdP3Qswfgs6+v/kRUJLCOgIAeP2sBPW72OCPyYhYJkF11c7sV0Lv8tupG+mGr1uznkf6dvUb1PG7Von+tx1dA/9LbYvepfsaAfqafRm/9Ebv7dIKdEFhHwF92nz9rAX3eLvqTk0jAibz4Rurmdiugd/lt1Y30w1at2c/P0GfRtXOLisXG8xXQBfTBe0ZA/xhKQB9sJMMIXEhAQJ8/LAF93k5A/9POi3quj7Zm890Sev45tzm30Vl8BXQBffBuEdAF9MFWMYzAbQQE9PmjFNDn7QR0AT3XPeOzBaFxq8eR3ObcRmfxFdAF9MG7RUAX0AdbxTAClxYY/ffNP9rk638r/dIQycUL6DlAL6hrv6Dmumd8tj4btxLQ56xmZunLtb///JG4wF0joAvogXYxlMBlBQT0mqMT0HOOXlDXfkHNdc/4bH02biWgz1nNzNKXa3//CeiBu+aMAf3Z8o/6w3H+inugmQwlcGKBx4D+uszHn4g/+/zv2/ET9E+fBPRck3tBXfsFNdc947P12biVgD5nNTNLX679/SegB+4aAf1jLAE90EyGEjixgIBeczgCes7RC+raL6i57hmfrc/GrQT0OauZWfpy7e8/AT1w1wjoAnqgXQwlcFkBAb3m6AT0nKMX1LVfUHPdMz5bn41bCehzVjOz9OXa338CeuCuuUpAf7alPX7tffQn6EetL3DUhhJYUmDkV9d/h3n99fWtIL8k4sOmBfRcB3hBXfsFNdc947P12biVgD5nNTNLX679/SegB+4aAf1jLAE90EyGEjihgIBeeygCes7TC+raL6i57hmfrc/GrQT0OauZWfpy7e8/AT1w1xwd0B+XGg3De/wE/XV90bX9Pm/P9QWO3FACSwlEA/pSOBObFdAn0B6meEFd+wU11z3js/XZuJWAPmc1M0tfrv39J6AH7hoBfQxLQB9zMorA2QQE9NoTEdBznl5Q135BzXXP+Gx9Nm4loM9ZzczSl2t//wnogbtGQB/DEtDHnIwicDYBAb32RAT0nKcX1LVfUHPdMz5bn41bCehzVjOz9OXa338CeuCuOVNAf132Vhg++lfHP1rf0WsLHL2hBJYS8Mffao5bQM85ekFd+wU11z3js/XZuJWAPmc1M0tfrv39J6AH7hoBPYD1x1ABPW5mBoGjBQT0mhMQ0HOOZ3hBze3AbAIECBC4usDPP/+8+xYE9AD5GQN6YPmGEiBAgMCOAgJ6DltAz/mZTYAAAQJ5AQE9b9haQUBv5VWcAAECtxIQ0HPHKaDn/MwmQIAAgbyAgJ43bK0goLfyKk6AAIFbCQjoueMU0HN+ZhMgQIBAXkBAzxu2VhDQW3kVJ0CAwK0EBPTccQroOT+zCRAgQCAvIKDnDVsrCOitvIoTIEDgVgICeu44BfScn9kECBAgkBcQ0POGrRUE9FZexQkQIHArAQE9d5wCes7PbAIECBDICwjoecPWCgJ6K6/iBAgQuJWAgJ47TgE952c2AQIECOQFBPS8YWsFAb2VV3ECBAjcSkBAzx2ngJ7zM5sAAQIE8gICet6wtYKA3sqrOAECBG4lIKDnjlNAz/mZTYAAAQJ5AQE9b9haQUBv5VWcAAECtxIQ0HPHKaDn/MwmQIAAgbyAgJ43bK0goLfyKk6gXeDXf3337jW++/HX9uu7wFoCAnruvCMBPfICpe7Xc+Fwf4foHeg++irGgcPjvRPph+g99974ly/eIIYtBfRhKgMJnFJAQD/lsdx2UR6vuaMVIO8fICMvvvoh3g/RO9B5CKazwdT9Gb3bPh4voAc8BfQAlqEETiLwLJQ//rR86/OTbMMyLiggoOcOzQtfPJAJWAJW5q7TP/pHQH97B0Xui8z99zhXQA9ICugBLEMJnERgK4BvfX6SbVjGBQUE9NyhCegC+mMH6Yd4P0TvwEgQcR7x8+B7/38AEr3n3hsvoAckBfQAlqEETiKwFcC3Pj/JNizjggICeu7QBAABQEB/ew9F7ovoHShA3j9ARvpHP8T7IXrPCegFYgJ6AaISBHYSiAbv6PidtuEyFxYQ0HOH50VSQBfQBfSPvkUEyHiA9L3a+72ae+r9OdtP0AOSAnoAy1ACBwtEA3d0/MHbc/kLCAjouUOKvEjmrmQ2AQIECBB4LhD5B0FVhgJ6QFJAD2AZSuBggWjgjo4/eHsufwEBAT13SAJ6zs9sAgQIEMgLCOh5w9YKAnorr+IESgWigTs6vnSxit1SQEDPHauAnvMzmwABAgTyAgJ63rC1goDeyqs4gVKBaOCOji9drGK3FBDQc8cqoOf8zCZAgACBvICAnjdsrSCgt/IqTqBUIBq4o+NLF6vYLQUE9NyxCug5P7MJECBAIC8goOcNWysI6K28ihMoFYgG7uj40sUqdksBAT13rAJ6zs9sAgQIEMgLCOh5w9YKAnorr+IEWgS2gvfW5y2LUnQJAQE9d8wCes7PbAIECBDICwjoecPWCgJ6K6/iBFoEtgL41ucti1J0CQEBPXfMAnrOz2wCBAgQyAsI6HnD1goCeiuv4gRaBLYC+NbnLYtSdAkBAT13zAJ6zs9sAgQIEMgLCOh5w9YKAnorr+IE2gWehfHXi37346/t13eBtQQE9Nx5C+g5P7MJECBAIC8goOcNWysI6K28ihNoFxDQ24ld4EFAQM+1g4Ce8zObAAECBPICAnresLWCgN7KqzgBAgRuJSCg545TQM/5mU2AAAECeQEBPW/YWkFAb+VVnAABArcSENBzxymg5/zMJkCAAIG8gICeN2ytIKC38ipOgACBWwkI6LnjFNBzfmYTIECAQF5AQM8btlYQ0Ft5FSdAgMCtBAT03HEK6Dk/swkQIEAgLyCg5w1bKwjorbyKEyBA4FYCAnruOAX0nJ/ZBAgQIJAXENDzhq0VBPRWXsUJECBwKwEB/VbHaTMECBAgQGAXgZcv3iCGoQX0YSoDCRAgsLyAx+vyLQCAAAECBAiEBQT0AJmAHsAylAABAosLCOiLN4DtEyBAgACBCQEBPYAmoAewDCVAgMDiAgL64g1g+wQIECBAYEJAQA+gCegBLEMJECCwuICAvngD2D4BAgQIEJgQENADaAJ6AMtQAgQILC4goC/eALZPgAABAgQmBAT0AJqAHsAylAABAosLCOhvG+Bqz9HPnz8/7eJvvvnmlN19tfWeEtGiCNxY4Keffnqzux9++OHGO953a1XPfQE9cG5Xe7EIbM1QAgQIECgWqHpQFy/r0HJXe45eLfBebb2HNqOLE1hQQEDvPfSq576AHjinq71YBLZmKAECBAgUC1Q9qIuXdWi5qz1HrxZ4r7beQ5vRxQksKCCg9x561XNfQA+c09VeLAJbM5QAAQIEigWqHtTFyzq03NWeo1cLvFdb76HN6OIEFhQQ0HsPveq5L6AHzulqLxaBrRlKgAABAsUCVQ/q4mUdWu5qz9GrBd6rrffQZnRxAgsKCOi9h1713BfQA+d0tReLwNYMJUCAAIFigaoHdfGyDi135ufoe+F2FGzvPxx3tfWOOhpHgACBqwpUPfcF9EAHnPnFIrANQwkQIEBgB4GqB/UOS93tEmd+jl4t8F5tvbs1mQsRIEDgIIGq576AHjjAM79YBLZhKAECBAjsIFD1oN5hqbtd4szP0asF3qutd7cmcyECBAgcJFD13BfQAwd45heLwDYMJUCAAIEdBKoe1DssdbdLnPk5erXAe7X17tZkLkSAAIGDBKqe+wJ64ADP/GIR2IahBAgQILCDQNWDeoel7naJMz9HrxZ4r7be3ZrMhQgQIHCQQNVzX0APHOCZXywC2zCUAAECBHYQqHpQ77DU3S5x5ufo1QLv1da7W5O5EAECBA4SqHruC+iBAzzzi0VgG4YSIECAwA4CVQ/qHZbqEgQIECBAgMBJBAT0wEEI6AEsQwkQILC4gIC+eAPYPgECBAgQmBAQ0ANoAnoAy1ACBAgsLiCgL94Atk+AAAECBCYEBPQAmoAewDKUAAECiwsI6Is3gO0TIECAAIEJAQE9gCagB7AMJUCAwOICAvriDWD7BAgQIEBgQkBAD6AJ6AEsQwkQILC4gIC+eAPYPgECBAgQmBAQ0ANoAnoAy1ACBAgsLiCgL94Atk+AAAECBCYEBPQAmoAewDKUAAECiwsI6Is3gO0TIECAAIEJAQE9gCagB7AMJUCAwOICAvriDWD7BAgQIEBgQkBAD6AJ6AEsQwkQILC4gIC+eAPYPgECBAgQmBAQ0ANoAnoAy1ACBAgsLiCgL94Atk+AAAECBCYEBPQAmoAewDKUAAECiwsI6Is3gO0TIECAAIEJAQE9gCagB7AMJUCAwOICAvriDWD7BAgQIEBgQkBAD6AJ6AEsQwkQILC4gIC+eAPYPgECBAgQmBAQ0ANoAnoAy1ACBAgsLiCgL94Atk+AAAECBCYEBPQAmoAewDKUAAECiwsI6Is3gO0TIECAAIEJAQE9gCagB7AMJUCAwOICAvriDWD7BAgQIEBgQkBAD6AJ6AEsQwkQILC4gIC+eAPYPgECBAgQmBAQ0CfQTCFAgAABAgQIECBAgAABAtUCAnq1qHoECBAgQIAAAQIECBAgQGBCQECfQDOFAAECBAgQIECAAAECBAhUCwjo1aLqESBAgAABAgQIECBAgACBCQEBfQLNFAIECBAgQIAAAQIECBAgUC0goFeLqkeAAAECBAgQIECAAAECBCYEBPQJNFMIECBAgAABAgQIECBAgEC1gIBeLaoeAQIECBAgQIAAAQIECBCYEBDQJ9BMIUCAAAECBAgQIECAAAEC1QICerWoegQIECBAgAABAgQIECBAYEJAQJ9AM4UAAQIECBAgQIAAAQIECFQLCOjVouoRIECAAAECBAgQIECAAIEJAQF9As0UAgQIECBAgAABAgQIECBQLSCgV4uqR4AAAQIECBAgQIAAAQIEJgQE9Ak0UwgQIECAAAECBAgQIECAQLWAgF4tqh4BAgQIECBAgAABAgQIEJgQENAn0EwhQIAAAQIECBAgQIAAAQLVAgJ6tah6BAgQIECAAAECBAgQIEBgQkBAn0AzhQABAgQIECBAgAABAgQIVAsI6NWi6hEgQIAAAQIECBAgQIAAgQkBAX0CzRQCBAgQIECAAAECBAgQIFAtIKBXi6pHgAABAgQIECBAgAABAgQmBAT0CTRTCBAgQIAAAQIECBAgQIBAtYCAXi2qHgECBAgQIECAAAECBAgQmBAQ0CfQTCFAgAABAgQIECBAgAABAtUCAnq1qHoECBAgQIAAAQIECBAgQGBCQECfQDOFAAECBAgQIECAAAECBAhUCwjo1aLqESBAgAABAgQIECBAgACBCQEBfQLNFAIECBAgQIAAAQIECBAgUC0goFeLqkeAAAECBAgQIECAAAECBCYEBPQJNFMIECBAgAABAgQIECBAgEC1gIBeLaoeAQIECBAgQIAAAQIECBCYEBDQJ9BMIUCAAAECBAgQIECAAAEC1QICerWoegQIECBAgAABAgQIECBAYEJAQJ9AM4UAAQIECBAgQIAAAQIECFQLCOjVouoRIECAAAECBAgQIECAAIEJAQF9As0UAgQIECBAgAABAgQIECBQLSCgV4uqR4AAAQIECBAgQIAAAQIEJgQE9Ak0UwgQIECAAAECBAgQIECAQLWAgF4tqh4BAgQIECBAgAABAgQIEJgQENAn0EwhQIAAAQIECBAgQIAAAQLVAgJ6tah6BAgQIECAAAECBAgQIEBgQkBAn0AzhQABAgQIECBAgAABAgQIVAv8F/iYDQJyb+hvAAAAAElFTkSuQmCC\" width=\"1000\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(10, 6))\n",
"plt.subplot(121)\n",
"plt.title(\"원본 관측 (160×210 RGB)\")\n",
"plt.imshow(obs)\n",
"plt.axis(\"off\")\n",
"plt.subplot(122)\n",
"plt.title(\"전처리된 관측 (88×80 그레이스케일)\")\n",
"plt.imshow(img.reshape(88, 80), interpolation=\"nearest\", cmap=\"gray\")\n",
"plt.axis(\"off\")\n",
"save_fig(\"preprocessing_plot\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## DQN 만들기"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [],
"source": [
"reset_graph()\n",
"\n",
"input_height = 88\n",
"input_width = 80\n",
"input_channels = 1\n",
"conv_n_maps = [32, 64, 64]\n",
"conv_kernel_sizes = [(8,8), (4,4), (3,3)]\n",
"conv_strides = [4, 2, 1]\n",
"conv_paddings = [\"SAME\"] * 3 \n",
"conv_activation = [tf.nn.relu] * 3\n",
"n_hidden_in = 64 * 11 * 10 # conv3은 11x10 크기의 64개의 맵을 가집니다\n",
"n_hidden = 512\n",
"hidden_activation = tf.nn.relu\n",
"n_outputs = env.action_space.n # 9개의 행동이 가능합니다\n",
"initializer = tf.contrib.layers.variance_scaling_initializer()\n",
"\n",
"def q_network(X_state, name):\n",
" prev_layer = X_state / 128.0 # 픽셀 강도를 [-1.0, 1.0] 범위로 스케일 변경합니다.\n",
" with tf.variable_scope(name) as scope:\n",
" for n_maps, kernel_size, strides, padding, activation in zip(\n",
" conv_n_maps, conv_kernel_sizes, conv_strides,\n",
" conv_paddings, conv_activation):\n",
" prev_layer = tf.layers.conv2d(\n",
" prev_layer, filters=n_maps, kernel_size=kernel_size,\n",
" strides=strides, padding=padding, activation=activation,\n",
" kernel_initializer=initializer)\n",
" last_conv_layer_flat = tf.reshape(prev_layer, shape=[-1, n_hidden_in])\n",
" hidden = tf.layers.dense(last_conv_layer_flat, n_hidden,\n",
" activation=hidden_activation,\n",
" kernel_initializer=initializer)\n",
" outputs = tf.layers.dense(hidden, n_outputs,\n",
" kernel_initializer=initializer)\n",
" trainable_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,\n",
" scope=scope.name)\n",
" trainable_vars_by_name = {var.name[len(scope.name):]: var\n",
" for var in trainable_vars}\n",
" return outputs, trainable_vars_by_name"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [],
"source": [
"X_state = tf.placeholder(tf.float32, shape=[None, input_height, input_width,\n",
" input_channels])\n",
"online_q_values, online_vars = q_network(X_state, name=\"q_networks/online\")\n",
"target_q_values, target_vars = q_network(X_state, name=\"q_networks/target\")\n",
"\n",
"copy_ops = [target_var.assign(online_vars[var_name])\n",
" for var_name, target_var in target_vars.items()]\n",
"copy_online_to_target = tf.group(*copy_ops)"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'/conv2d/bias:0': <tf.Variable 'q_networks/online/conv2d/bias:0' shape=(32,) dtype=float32_ref>,\n",
" '/conv2d/kernel:0': <tf.Variable 'q_networks/online/conv2d/kernel:0' shape=(8, 8, 1, 32) dtype=float32_ref>,\n",
" '/conv2d_1/bias:0': <tf.Variable 'q_networks/online/conv2d_1/bias:0' shape=(64,) dtype=float32_ref>,\n",
" '/conv2d_1/kernel:0': <tf.Variable 'q_networks/online/conv2d_1/kernel:0' shape=(4, 4, 32, 64) dtype=float32_ref>,\n",
" '/conv2d_2/bias:0': <tf.Variable 'q_networks/online/conv2d_2/bias:0' shape=(64,) dtype=float32_ref>,\n",
" '/conv2d_2/kernel:0': <tf.Variable 'q_networks/online/conv2d_2/kernel:0' shape=(3, 3, 64, 64) dtype=float32_ref>,\n",
" '/dense/bias:0': <tf.Variable 'q_networks/online/dense/bias:0' shape=(512,) dtype=float32_ref>,\n",
" '/dense/kernel:0': <tf.Variable 'q_networks/online/dense/kernel:0' shape=(7040, 512) dtype=float32_ref>,\n",
" '/dense_1/bias:0': <tf.Variable 'q_networks/online/dense_1/bias:0' shape=(9,) dtype=float32_ref>,\n",
" '/dense_1/kernel:0': <tf.Variable 'q_networks/online/dense_1/kernel:0' shape=(512, 9) dtype=float32_ref>}"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"online_vars"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING:tensorflow:From <ipython-input-70-86b805f1efd9>:8: calling reduce_sum (from tensorflow.python.ops.math_ops) with keep_dims is deprecated and will be removed in a future version.\n",
"Instructions for updating:\n",
"keep_dims is deprecated, use keepdims instead\n"
]
}
],
"source": [
"learning_rate = 0.001\n",
"momentum = 0.95\n",
"\n",
"with tf.variable_scope(\"train\"):\n",
" X_action = tf.placeholder(tf.int32, shape=[None])\n",
" y = tf.placeholder(tf.float32, shape=[None, 1])\n",
" q_value = tf.reduce_sum(online_q_values * tf.one_hot(X_action, n_outputs),\n",
" axis=1, keepdims=True)\n",
" error = tf.abs(y - q_value)\n",
" clipped_error = tf.clip_by_value(error, 0.0, 1.0)\n",
" linear_error = 2 * (error - clipped_error)\n",
" loss = tf.reduce_mean(tf.square(clipped_error) + linear_error)\n",
"\n",
" global_step = tf.Variable(0, trainable=False, name='global_step')\n",
" optimizer = tf.train.MomentumOptimizer(learning_rate, momentum, use_nesterov=True)\n",
" training_op = optimizer.minimize(loss, global_step=global_step)\n",
"\n",
"init = tf.global_variables_initializer()\n",
"saver = tf.train.Saver()"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [],
"source": [
"from collections import deque\n",
"\n",
"replay_memory_size = 500000\n",
"replay_memory = deque([], maxlen=replay_memory_size)\n",
"\n",
"def sample_memories(batch_size):\n",
" indices = np.random.permutation(len(replay_memory))[:batch_size]\n",
" cols = [[], [], [], [], []] # 상태, 행동, 보상, 다음 상태, 종료 여부\n",
" for idx in indices:\n",
" memory = replay_memory[idx]\n",
" for col, value in zip(cols, memory):\n",
" col.append(value)\n",
" cols = [np.array(col) for col in cols]\n",
" return cols[0], cols[1], cols[2].reshape(-1, 1), cols[3], cols[4].reshape(-1, 1)"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [],
"source": [
"eps_min = 0.1\n",
"eps_max = 1.0\n",
"eps_decay_steps = 2000000\n",
"\n",
"def epsilon_greedy(q_values, step):\n",
" epsilon = max(eps_min, eps_max - (eps_max-eps_min) * step/eps_decay_steps)\n",
" if np.random.rand() < epsilon:\n",
" return np.random.randint(n_outputs) # 랜덤 행동\n",
" else:\n",
" return np.argmax(q_values) # 최적 행동"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [],
"source": [
"n_steps = 4000000 # 전체 훈련 스텝 횟수\n",
"training_start = 10000 # 10,000번 게임을 반복한 후에 훈련을 시작합니다\n",
"training_interval = 4 # 4번 게임을 반복하고 훈련 스텝을 실행합니다\n",
"save_steps = 1000 # 1,000번 훈련 스텝마다 모델을 저장합니다\n",
"copy_steps = 10000 # 10,000번 훈련 스텝마다 온라인 DQN을 타깃 DQN으로 복사합니다\n",
"discount_rate = 0.99\n",
"skip_start = 90 # 게임의 시작 부분은 스킵합니다 (시간 낭비이므로).\n",
"batch_size = 50\n",
"iteration = 0 # 게임 반복횟수\n",
"checkpoint_path = \"./my_dqn.ckpt\"\n",
"done = True # 환경을 리셋해야 합니다"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"학습 과정을 트래킹하기 위해 몇 개의 변수가 필요합니다:"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {},
"outputs": [],
"source": [
"loss_val = np.infty\n",
"game_length = 0\n",
"total_max_q = 0\n",
"mean_max_q = 0.0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"이제 훈련 반복 루프입니다!"
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO:tensorflow:Restoring parameters from ./my_dqn.ckpt\n",
"반복 2427324\t훈련 스텝 603778/4000000 (15.1)%\t손실 4.245887\t평균 최대-Q 69.582249 "
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-78-2a4d092791d1>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[0;31m# 메모리에서 샘플링하여 타깃 Q-가치를 얻기 위해 타깃 DQN을 사용합니다\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m X_state_val, X_action_val, rewards, X_next_state_val, continues = (\n\u001b[0;32m---> 45\u001b[0;31m sample_memories(batch_size))\n\u001b[0m\u001b[1;32m 46\u001b[0m next_q_values = target_q_values.eval(\n\u001b[1;32m 47\u001b[0m feed_dict={X_state: X_next_state_val})\n",
"\u001b[0;32m<ipython-input-71-909b0e07a829>\u001b[0m in \u001b[0;36msample_memories\u001b[0;34m(batch_size)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msample_memories\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbatch_size\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mindices\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpermutation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mreplay_memory\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0mbatch_size\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0mcols\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;31m# 상태, 행동, 보상, 다음 상태, 종료 여부\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0midx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mindices\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"with tf.Session() as sess:\n",
" if os.path.isfile(checkpoint_path + \".index\"):\n",
" saver.restore(sess, checkpoint_path)\n",
" else:\n",
" init.run()\n",
" copy_online_to_target.run()\n",
" while True:\n",
" step = global_step.eval()\n",
" if step >= n_steps:\n",
" break\n",
" iteration += 1\n",
" print(\"\\r반복 {}\\t훈련 스텝 {}/{} ({:.1f})%\\t손실 {:5f}\\t평균 최대-Q {:5f} \".format(\n",
" iteration, step, n_steps, step * 100 / n_steps, loss_val, mean_max_q), end=\"\")\n",
" if done: # 게임이 종료되면 다시 시작합니다\n",
" obs = env.reset()\n",
" for skip in range(skip_start): # 게임 시작 부분은 스킵합니다\n",
" obs, reward, done, info = env.step(0)\n",
" state = preprocess_observation(obs)\n",
"\n",
" # 온라인 DQN이 해야할 행동을 평가합니다\n",
" q_values = online_q_values.eval(feed_dict={X_state: [state]})\n",
" action = epsilon_greedy(q_values, step)\n",
"\n",
" # 온라인 DQN으로 게임을 플레이합니다.\n",
" obs, reward, done, info = env.step(action)\n",
" next_state = preprocess_observation(obs)\n",
"\n",
" # 재생 메모리에 기록합니다\n",
" replay_memory.append((state, action, reward, next_state, 1.0 - done))\n",
" state = next_state\n",
"\n",
" # 트래킹을 위해 통계값을 계산합니다 (책에는 없습니다)\n",
" total_max_q += q_values.max()\n",
" game_length += 1\n",
" if done:\n",
" mean_max_q = total_max_q / game_length\n",
" total_max_q = 0.0\n",
" game_length = 0\n",
"\n",
" if iteration < training_start or iteration % training_interval != 0:\n",
" continue # 워밍엄 시간이 지난 후에 일정 간격으로 훈련합니다\n",
" \n",
" # 메모리에서 샘플링하여 타깃 Q-가치를 얻기 위해 타깃 DQN을 사용합니다\n",
" X_state_val, X_action_val, rewards, X_next_state_val, continues = (\n",
" sample_memories(batch_size))\n",
" next_q_values = target_q_values.eval(\n",
" feed_dict={X_state: X_next_state_val})\n",
" max_next_q_values = np.max(next_q_values, axis=1, keepdims=True)\n",
" y_val = rewards + continues * discount_rate * max_next_q_values\n",
"\n",
" # 온라인 DQN을 훈련시킵니다\n",
" _, loss_val = sess.run([training_op, loss], feed_dict={\n",
" X_state: X_state_val, X_action: X_action_val, y: y_val})\n",
"\n",
" # 온라인 DQN을 타깃 DQN으로 일정 간격마다 복사합니다\n",
" if step % copy_steps == 0:\n",
" copy_online_to_target.run()\n",
"\n",
" # 일정 간격으로 저장합니다\n",
" if step % save_steps == 0:\n",
" saver.save(sess, checkpoint_path)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"아래 셀에서 에이전트를 테스트하기 위해 언제든지 위의 셀을 중지할 수 있습니다. 그런다음 다시 위의 셀을 실행하면 마지막으로 저장된 파라미터를 로드하여 훈련을 다시 시작할 것입니다."
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO:tensorflow:Restoring parameters from ./my_dqn.ckpt\n"
]
}
],
"source": [
"frames = []\n",
"n_max_steps = 10000\n",
"\n",
"with tf.Session() as sess:\n",
" saver.restore(sess, checkpoint_path)\n",
"\n",
" obs = env.reset()\n",
" for step in range(n_max_steps):\n",
" state = preprocess_observation(obs)\n",
"\n",
" # 온라인 DQN이 해야할 행동을 평가합니다\n",
" q_values = online_q_values.eval(feed_dict={X_state: [state]})\n",
" action = np.argmax(q_values)\n",
"\n",
" # 온라인 DQN이 게임을 플레이합니다\n",
" obs, reward, done, info = env.step(action)\n",
"\n",
" img = env.render(mode=\"rgb_array\")\n",
" frames.append(img)\n",
"\n",
" if done:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4Xu3dP4+2SXYX/hk5JCJBWu2aFwArCzmyLC0gi8BIZIssIke8gMnWL8HOnhdARIQsJkOCAFmGlVZECFkGySm7liUnRIRoUM9sz3T33N11rvs+9e/U55fwW3fdVac+p65n6vtc3U9//tVXX331mf+PAAECBAgQIECAAAECAwQ+F0AGKFuCAAECBAgQIECAAIGvBQQQB4EAAQIECBAgQIAAgWECAsgwagsRIECAAAECBAgQICCAOAMECBAgQIAAAQIECAwTEECGUVuIAAECBAgQIECAAAEBxBkgQIAAAQIECBAgQGCYgAAyjNpCBAgQIECAAAECBAgIIM4AAQIECBAgQIAAAQLDBASQYdQWIkCAAAECBAgQIEBAAHEGCBAgQIAAAQIECBAYJiCADKO2EAECBAgQIECAAAECAogzQIAAAQIECBAgQIDAMAEBZBi1hQgQIECAAAECBAgQEECcAQIECBAgQIAAAQIEhgkIIMOoLUSAAAECBAgQIECAgADiDBAgQIAAAQIECBAgMExAABlGbSECBAgQIECAAAECBAQQZ4AAAQIECBAgQIAAgWECAsgwagsRIECAAAECBAgQICCAOAMECBAgQIAAAQIECAwTEECGUVuIAAECBAgQIECAAAEBxBkgQIAAAQIECBAgQGCYgAAyjNpCBAgQIECAAAECBAgIIM4AAQIECBAgQIAAAQLDBASQYdQWIkCAAAECBAgQIEBAAHEGCBAgQIAAAQIECBAYJiCADKO2EAECBAgQIECAAAECAogzQIAAAQIECBAgQIDAMAEBZBi1hQgQIECAAAECBAgQEECcAQIECBAgQIAAAQIEhgkIIMOoLUSAAAECBAgQIECAgADiDBAgQIAAAQIECBAgMExAABlGbSECBAgQIECAAAECBAQQZ4AAAQIECBAgQIAAgWECAsgwagsRIECAAAECBAgQICCAOAMECBAgQIAAAQIECAwTEECGUVuIAAECBAgQIECAAAEBxBkgQIAAAQIECBAgQGCYgAAyjNpCBAgQIECAAAECBAgIIM4AAQIECBAgQIAAAQLDBASQYdQWIkCAAAECBAgQIEBAAHEGCBAgQIAAAQIECBAYJiCADKO2EAECBAgQIECAAAECAogzQIAAAQIECBAgQIDAMAEBZBi1hQgQIECAAAECBAgQEECcAQIECBAgQIAAAQIEhgkIIMOoLUSAAAECBAgQIECAgADiDBAgQIAAAQIECBAgMExAABlGbSECBAgQIECAAAECBAQQZ4AAAQIECBAgQIAAgWECAsgwagsRIECAAAECBAgQICCAOAMECBAgQIAAAQIECAwTEECGUVuIAAECBAgQIECAAAEBxBkgQIAAAQIECBAgQGCYgAAyjNpCBAgQIECAAAECBAgIIM4AAQIECBAgQIAAAQLDBASQYdQWIkCAAAECBAgQIEBAAHEGCBAgQIAAAQIECBAYJiCADKO2EAECBAgQIECAAAECAogzQIAAAQIECBAgQIDAMAEBZBi1hQgQIECAAAECBAgQEECcAQIECBAgQIAAAQIEhgkIIMOoLUSAAAECBAgQIECAgADiDBAgQIAAAQIECBAgMExAABlGbSECBAgQIECAAAECBAQQZ4AAAQIECBAgQIAAgWECAsgwagsRIECAAAECBAgQICCAOAMECBAgQIAAAQIECAwTEECGUVuIAAECBAgQIECAAAEBxBkgQIAAAQIECBAgQGCYgAAyjNpCBAgQIECAAAECBAgIIM4AgQ0EPv/88w2qVCIBAgTqCXz11Vf1NmVHBCYLCCCTG2B5AhEBASSiZAwBAgTyBQSQfFMzEhBAnAECGwj89RdfbFClEgkQIFBP4IefPtXblB0RmCwggExugOUJRAQEkIiSMQQIEMgXEEDyTc1IQABxBghsIBAJID/68gcb7GTNEn/1079pFsa3SWRABwFnswPqiykjvgJI3x6Y/UwBAeTMvtv1ZgICSN+GRS4hAkjfHpj9toCz2fdkRHwFkL49MPuZAgLImX23680EBJC+DYtcQgSQvj0wuwAy4wxEnn0BZEZnrFldQACp3mH7KyEggPRtY+QSIoD07YHZBZAZZyDy7AsgMzpjzeoCAkj1DttfCQEBpG8bI5cQAaRvD8wugMw4A5FnXwCZ0RlrVhcQQKp32P5KCAggfdsYuYQIIH17YHYBZMYZiDz7AsiMzlizuoAAUr3D9ldCQADp28bIJUQA6dsDswsgM85A5NkXQGZ0xprVBQSQ6h22vxICAkjfNkYuIQJI3x6YXQCZcQYiz74AMqMz1qwuIIBU77D9lRAQQPq2MXIJEUD69sDsAsiMMxB59gWQGZ2xZnUBAaR6h+2vhIAA0reNkUuIANK3B2YXQGacgcizL4DM6Iw1qwsIINU7bH8lBASQvm2MXEIEkL49MLsAMuMMRJ59AWRGZ6xZXUAAqd5h+yshkBVAWv+xjVyyW3M8gY+aZ6VanvbdqmeUS6SWSJ9a+4nMEX0AM9bKmCPLLjLPqedhVJ+yfAWQ6FNsHIG4gAAStzKSwDQBAaTv3w7vdiHKuli15slyiTw4GWtlzBEJDtHg1aqn5T+ylsieWvuJzDFyT1m+AkjkCTaGwDUBAeSal9EEpggIIALIS4Gsi1VrnqwLZ+ShyVgrY44dL8itPmbt6VRfASTyBBtD4JqAAHLNy2gCUwQEEAFEAGl/a9+pF2QB5PafD1kuAsiU/+xZtLiAAFK8wbZXQ0AAEUAEEAHkvT/Nsi7arXlODXgCSI3/jtrFWgICyFr9UA2BmwJZAQRv3yDDl0C2QNalP7uuKvNFfAWQKt22j5UEBJCVuqEWAu8ICCB9j0bkEtL62+G+FZr9VAFns2/nI74CSN8emP1MAQHkzL7b9WYCAkjfhkUuIQJI3x6Y3du5GWcg8uwLIDM6Y83qAgJI9Q7bXwkBAaRvGyOXEAGkbw/MLoDMOAORZ18AmdEZa1YXEECqd9j+SggIIH3bGLmECCB9e2B2AWTGGYg8+wLIjM5Ys7qAAFK9w/ZXQkAA6dvGyCVEAOnbA7MLIDPOQOTZF0BmdMaa1QUEkOodtr8SAgJI3zZGLiECSN8emF0AmXEGIs++ADKjM9asLiCAVO+w/ZUQEED6tjFyCRFA+vbA7ALIjDMQefYFkBmdsWZ1AQGkeoftr4SAANK3jZFLiADStwdmF0BmnIHIsy+AzOiMNasLCCDVO2x/JQQEkL5tjFxCBJC+PTC7ADLjDESefQFkRmesWV1AAKneYfsrISCA9G1j5BIigPTtgdkFkBlnIPLsCyAzOmPN6gICSPUO218JgZUCSOQ/2Fnooy79kT2tVEuWr3nuF1jpPKxUy/2irz8Z2VPruc2Y46kqASSrq+Yh8J2AAOI0ENhAQADp26TWReZp9chlJqPKSC0Z65jjMYGVzsNKtTym+t2nI3tqPSsZcwggWR01D4HXAgKIE0FgAwEBpG+TWhcZAaSv/46zRy63Gfs69WxGfFs2GXMIIBmn2BwEvi8ggDgVBDYQEED6Nql1kRFA+vrvOHvkcpuxr1PPZsS3ZZMxhwCScYrNQUAAcQYIbCkggPRtW+siI4D09d9x9sjlNmNfp57NiG/LJmMOASTjFJuDgADiDBDYUkAA6du21kVGAOnrv+Pskcttxr5OPZsR35ZNxhwCSMYpNgcBAcQZILClgADSt22ti4wA0td/x9kjl9uMfZ16Nt/z/b0/+N1vWf/t//vyQ+I//I2ffvv1P/vTX9wcG/H1r2BlnGRzEHgt4GdAnAgCGwgIIH2bFLmErHTh7Kth9ojASudhpVoidpExAkhEyRgC+woIIPv2TuUHCQggfZstgPT1rTj7Spf+lWrJ6rUAkiVpHgJrCggga/ZFVQReCQggfQ+EANLXt+LsK136V6olq9dv9/TyW6+e17jyLVjPn3n7rViRZ9+3YGV11TwEvhMQQJwGAhsICCB9mxS5hFS85PVVrT37SudhpVqyui6AZEmah8CaAgLImn1RFQFvQAK/fTwrOIyaJ3JRjNTi8ZgvkNHLjDmeJEbNM/JsZgSQl3M8v0HxBmT+s6MCAk8CAohzQGADAW9AbjcpciEadTl7qrBVT1YtGxzZ8iVm9DJjDgHk/aMmgJR/DG1wYwEBZOPmKf0cAQFEADnntO+x04zwkDGHAPL+eXn5z/A+j/IGZI/nS5X1BQSQ+j22wwICAogAUuAYl9pCRnjImEMAEUBKPVg2c4yAAHJMq210ZwEBpG/3Wt86Fb3kZVQZqSVjHXM8JhAJD4+t8M2nI+dhpVoy9nzreXvvZzii6/kZkKiUcQTGCAggY5ytQuAhAQHkIb7mh0+95DVhDHhXYKVL/0q1ZB2Z934I/b3faN5aVwBpCfk6gbECAshYb6sRuEtAALmLLfwhASRMZeCvBVa69K9US9YBWWlPfg9IVlfNQ+A7AQHEaSCwgYAA0rdJAkhf34qzr3RBXqmWrF6vtCcBJKur5iEggDgDBLYSEED6tksA6etbcfaVLsgr1ZLV65X2JIBkddU8BAQQZ4DAVgICSN92CSB9fSvOvtIFeaVasnq90p4EkKyumoeAAOIMENhKQADp2y4BpK9vxdlXuiCvVEtWr1fakwCS1VXzEBBAnAECWwkIIH3bJYD09a04+0oX5JVqyer1SnsSQLK6ah4CAogzQGArAQGkb7sEkL6+FWdf6YK8Ui1ZvV5pTwJIVlfNQ0AAcQYIbCUggPRtlwDS17fi7CtdkFeqJavXK+1JAMnqqnkICCDOAIGtBASQvu0SQPr6Vpx9pQvySrVk9XqlPQkgWV01DwEBxBkgsJXASgFkKzjFEiBA4AOByF8+CCCOEIF8Ab+IMN/UjATSBQSQdFITEiBA4DMBxCEgMEdAAJnjblUClwQEkEtc5Qf/hz/8d809/ot/+6+aYwwgcK/A7/3B73740T/701/cO/XQzwkgQ7ktRuBbAQHEYSCwgYAAskGTBpYogAzEttRNAQHEwSBA4BEBAeQRPZ8lMEhAABkEvckyAsgmjSpcpgBSuLm2RmCAgAAyANkSBB4VEEAeFazx+UjweN6pb8Gq0fOVdtEKHe/VuvK3Y/kWrJVOmFpOEhBATuq2vW4rIIBs27rUwgWQVE6TXRQQQC6CGU6AwLsCAojDQWADAQFkgyZ1LPFt8Hj7duPl19/7mjciHRt08NRvQ8nz245bYWXFNyHegBx8eG19qoAAMpXf4gRiAgJIzKnqKAGkamf335cAsn8P7YDADAEBZIa6NQlcFBBALoIVG/5eALn1LVnegBRr/gLbeQ4Zt95gvPc1b0AWaJwSCCwsIIAs3BylEXgWEEDOPgsCyNn9n717AeTT7BZYn0A5AQGkXEttqKJAVgBpfb/zj778QZOvNcfTBKPmWamWp3236rnX5X/8nf/yqi+RnwF5ruX5s//o//6T7/W2VU9rP9FeNw9VwC6yVla9o+Zp+UfOVMQla56XLn/4Gz+NtPXVmOc3KLv5/vCTAHK52T5AoCEggDgiBDYQEEBuN2nURSbrknfvhVMA+ab/Lb9TzsPbp6HlIoC8/4d85MwIIBv8R1KJ2wkIINu1TMEnCgggAkj03D+/HYlcrFoX14w5onVnrJUxR9ZlPTJPyz8yRySYZc3jDUj0NBtHgEBLQABpCfk6gQUEBBABJHoMBZCPpVa69K9USyTICCDRp9A4AgRaAgJIS8jXCSwgIIAIINFjKIAIILcEMt4OCSDRp9A4AgRaAgJIS8jXCSwgkBVAFtiKEu4Q8BvQ70DzkSECfg/IEGaLECgnIICUa6kNVRQQQCp2Nb4nASRuZeRYAQFkrLfVCFQREECqdNI+SgsIIKXb29ycANIkMqCjwK3fA9L6BYQvf2nhR79HpGPZoakj35rmX8EKURpE4JKAAHKJy2ACcwQEkDnuq6wqgKzSiTPrEED8HpAzT75d9xQQQHrqmptAkoAAkgS56TQCyKaNK1z222+9imz15VuRyPgRY7wBGaFsDQLfFxBAnAoCGwgIIBs0qWOJAkhHXFPfJSCA3MXmQwQI/FpAAHEUCGwgIIBs0KSOJT4HkOd/Yve9//1UwvOYjuWY+lCBl6Hj+W3G22/PuvLtWiswegOyQhfUcKKAAHJi1+15OwEBZLuWpRYsgKRymuxOAQHkTjgfI0DgewICiENBYAMBAWSDJimRAIHtBLwB2a5lCi4iIIAUaaRt1BYQQGr31+4IEJgjIIDMcbcqAQHEGSCwgYAAskGTlEiAwHYCAsh2LVNwEQEBpEgjbaO2QCSAZAj86MsfZEwTmiPyH/5R9URqCW2qMWjUfp7KGLWnDJeRc1Tswag9Rc7UqFpGnnG/iHDkE2qtUwQEkFM6bZ9bCwggfdsXuVhlVFDxcpbhMnKOij0YtafIczKqFgFk5FNjLQL5AgJIvqkZCaQLCCDppK8mjFysMiqoeDnLcBk5R8UejNpT5DkZVYsAMvKpsRaBfAEBJN/UjATSBQSQdFIBpC/psrNXvCCP2pMAsuyxVhiB7QQEkO1apuATBQSQvl2PXKwyKhh1URz5t8MZLiPnqNiDUXuKPCejahl5xv0MyMgn1FqnCAggp3TaPrcWEED6ti9yscqooOLlLMNl5BwVezBqT5HnZFQtAsjIp8ZaBPIFBJB8UzMSSBcQQNJJX00YuVhlVFDxcpbhMnKOij0YtafIczKqFgFk5FNjLQL5AgJIvqkZCaQLCCDppAJIX9JlZ694QR61JwFk2WOtMALbCQgg27VMwScKCCB9ux65WGVUMOqiOPJvhzNcRs5RsQej9hR5TkbVMvKM+xmQkU+otU4REEBO6bR9bi0ggPRtX+RilVFBxctZhsvIOSr2YNSeIs/JqFoEkJFPjbUI5AsIIPmmZiSQLiCApJO+mjByscqooOLlLMNl5BwVezBqT5HnZFQtAsjIp8ZaBPIFBJB8UzMSSBdYKYBkXUIy5smYY+RFJnI5221P6Ye984QVezBqTyPPZtaeMo6Tb8HKUDQHgdcCAogTQWADAQHkdpNGXogyjknWpSprnow97TZHll3WPBl+WbW05hn5vLVqGfkXBwJIxik1BwEBxBkgsJ2AACKAvBRY6XK228OUZZc1T4ZfVi2teQSQjG6ZgwCBJwFvQJwDAhsICCACiACS86C2LtnRv1nPmidjV1m1tOYRQDK6ZQ4CBAQQZ4DAJgIrBZAssqzLTEY9kVoy1mld8DLWeJ5j1J4yax4xV8UejNpT5EyNqiUaFDPOlG/BylA0B4HXAt6AOBEENhAQQPo2KXKxyqig4uUsw2XkHBV7MGpPkedkVC0CyMinxloE8gUEkHxTMxJIFxBA0klfTRi5WGVUUPFyluEyco6KPRi1p8hzMqoWAWTkU2MtAvkCAki+qRkJpAsIIOmkAkhf0mVnr3hBHrUnAWTZY60wAtsJCCDbtUzBJwoIIH27HrlYZVQw6qI48m+HM1xGzlGxB6P2FHlORtUy8oz7GZCRT6i1ThEQQE7ptH1uLSCA9G1f5GKVUUHFy1mGy8g5KvZg1J4iz8moWgSQkU+NtQjkCwgg+aZmJJAuIICkk76aMHKxyqig4uUsw2XkHBV7MGpPkedkVC0CyMinxloE8gUEkHxTMxJIFxBA0kkFkL6ky85e8YI8ak8CyLLHWmEEthMQQLZrmYJPFBBA+nY9crHKqGDURXHk3w5nuIyco2IPRu0p8pyMqmXkGfczICOfUGudIiCAnNJp+9xaQADp277IxSqjgoqXswyXkXNU7MGoPUWek1G1CCAjnxprEcgXEEDyTc1IIF1AAEknfTVh5GKVUUHFy1mGy8g5KvZg1J4iz8moWgSQkU+NtQjkCwgg+aZmJJAu8Plv/kn6nLMnXO0yM9vD+gRWFzj1mf3qlz9bvTXqI7CdgACyXcsUfKKAAHJi1+2ZwFoCAsha/VANgZ0FBJCdu6f2YwQEkGNabaMElhUQQJZtjcIIbCcggGzXMgWfKCCAnNh1eyawloAAslY/VENgZwEBZOfuqf0YAQHkmFbbKIFlBQSQZVujMALbCQgg27VMwScKCCAndt2eCawlIICs1Q/VENhZQADZuXtqP0ZAADmm1TZKYFkBAWTZ1iiMwHYCAsh2LVPwiQICyIldt2cCawkIIGv1QzUEdhYQQHbuntqPERBAjmm1jRJYVkAAWbY1CiOwnYAAsl3LFHyigAByYtftmcBaAgLIWv1QDYGdBQSQnbun9mMEBJBjWm2jBJYVEECWbY3CCGwnIIBs1zIFnyiQFUBaF4gfffmDJm9rjqcJRs2zUi1P+27VM8olUkukT639ROZoHqhfD8hYK2OOLLvIPKeeh1F9ivhGzudXv/xZZJgxBAhcEBBALmAZSmCWgAByW37URSZ60W7VE7kQtebIqiUyT1YtkecmY62MOSLBIWIXmefU8zCqTxHfyNkUQCJKxhC4JiCAXPMymsAUAQFEAHkpELlYZVzyMuaIPjAZa2XMEQkOAsj7XR11NiN9itQSOZ8CSETJGALXBASQa15GE5giIIAIIAJI+1v7BJD3/3jKsMmYIxIcRga8yB/oAkhEyRgC1wQEkGteRhOYIpAVQKYU/86iWZeZlfakFgKVBU59ZgWQyqfa3mYJCCCz5K1L4IKAAHIBy1ACBLoICCBdWE1K4EgBAeTIttv0bgICyG4dUy+BegICSL2e2hGBWQICyCx56xK4ICCAXMAylACBLgICSBdWkxI4UkAAObLtNr2bgACyW8fUS6CegABSr6d2RGCWgAAyS966BC4ICCAXsAwlQKCLgADShdWkBI4UEECObLtN7yYggOzWMfUSqCcggNTrqR0RmCUggMySty6BCwICyAUsQwk8KPCLn/zD5gy/+/P/2RxTbYAAUq2j9kNgnoAAMs/eygTCAgJImMpAAg8LCCC3CQWQh4+WCQgQ+LWAAOIoENhAQADZoElK3F4gEjzebvKkNyECyPZH3AYILCMggCzTCoUQeF9AAHE6CPQXEEA+NhZA+p9BKxA4RUAAOaXT9rm1wF9/8cWQ+n/05Q+GrPO0SOQyM6yYQQvxHQT9wTK3evAcPN6+zXjv//40/duwcutNiDPet9+jfH/46VPfjZidwIECAsiBTbfl/QQEkP16dqtiAWR+HwWQvj2oeMYFkL5nxuxnCgggZ/bdrjcTEEA2a9g75Va8nO3WGQGkb8cqnnEBpO+ZMfuZAgLImX23680EBJDNGiaALNuwjwLIc9HP305162dC3vuab8H6Rk8AWfboK4zAUgICyFLtUAyB2wICSI2TUfFytltnBJC+Hat4xr0B6XtmzH6mgAByZt/tejMBAWSzhnkDsmzDfAtW39YIIH19zU6gioAAUqWT9lFaQACp0d6Kl7PdOiOA9O1YxTPuDUjfM2P2MwUEkDP7btebCQggmzXMG5BlGxb5Fqx7ivczIN+oCSD3nB6fIXCegAByXs/teEMBAWTDpt0oueLlbLfOCCB9O1bxjHsD0vfMmP1MAQHkzL7b9WYCAshmDfMGZNmGCSB9WyOA9PU1O4EqAgJIlU7aR2kBAaRGeyteznbrjADSt2MVz7g3IH3PjNnPFBBAzuy7XW8msFIA+dVP/6apF7mEROZpLrTZgCyXkfPsRBw5U5EA8vf/3n9ubvt//+0/ezXGz4B8wzHybEb63WxkYIAAEkAyhMBFAQHkIpjhBGYICCAz1PPXXO1yFqknX6HfjJELqQDSz18A6WtrdgKVBASQSt20l7ICAkiN1kYu/Pdeot8KZc2zk/y9e377G8+9Abm/66ud8ft38t0nvQHJUDQHgdcCAogTQWADAQFkgyYFSlztchapJ7CtZYY8GkCev43qyjzP4cW3YH1zDCJn6orvR4crMk/G4RRAMhTNQUAAcQYIbCcggGzXspsFr3Y5i9Szk3zkQhr5RYRX5hFAXp+QyJm64iuA7PQEqpVAXMAbkLiVkQSmCawUQLIQIpeQrLVWmSdyOcuqNeI7sp6sfT16Ib2y51vh4qPA8ba2SA9GuIxc44rvo3WN8vUG5NFO+TyB7wsIIE4FgQ0EBJANmhQocbXL2ch6AjwPD4lcSK/sWQC53pIrvtdnf/2JSL8fXePp8wJIhqI5CLwWEECcCAIbCAggGzQpUOJql7OR9QR4Hh4SuZCO3HOknoc3vdgEFX0FkMUOmXJKCAggJdpoE9UFBJAaHV7tcjaynhEdjFz4R+45Us8Il5FrVPQVQEaeIGudIiCAnNJp+9xaQADZun3fFr/a5WxkPSM6GLnwj9xzpJ4RLiPXqOgrgIw8QdY6RUAAOaXT9rm1gACydfsEkEHti1z4K16QB/GGlqnoK4CEWm8QgUsCAsglLoMJzBEQQOa4Z6+62uVsZD3ZlrfmE0BGKH+8xsgzFel3hogAkqFoDgKvBQQQJ4LABgICyAZNCpS42uVsZD0BnoeHRC6kI/ccqefhTS82QUVfAWSxQ6acEgICSIk22kR1AQGkRodXu5yNrGdEByMX/pF7jtQzwmXkGhV9BZCRJ8hapwgIIKd02j63Ftg9gPynv/en3/P/8U9+snVP7il+tcvZyHru8br6mciFf+SeI/Vc3ePq4yv6CiCrnzr17SgggOzYNTUfJyCA1Gj5apezkfWM6GDkwj9yz5F6RriMXKOirwAy8gRZ6xQBAeSUTtvn1gKf/+afpNTfuhBlXx6e33z8/t/+wffqf/tW5NaYlE0fOkmr108s2f2eTX3inmebz1y/1e+s8/3VL382c5vWJlBSQAAp2VabqiYggFTraP/9tC5nAkj/Hlihr0DrjAsgff3NTuARAQHkET2fJTBIYNcA8hHPR29HBrGWXqZ1ORNASrf/iM21zrgAcsQxsMlNBQSQTRun7LMEBJCz+p2x29blTADJUDbHTIHWGRdAZnbH2gQ+FhBAnBACGwhUDCAbsA8tMfLzMld+TqZ1ORNAhqJmh4sAACAASURBVLbXYh0EWmdcAOmAbkoCSQICSBKkaQj0FBBAeuquMbcA8ngfWhfSiqHrcbV9Z2j1WwDZt7cqry8ggNTvsR0WEKgUQN67aPuZkNyD2rqcVbyMn7jn3FOz12ytfgsge/VTtWcJCCBn9dtuNxUQQDZt3MSyW5czAWRicyydItA64wJICrNJCHQREEC6sJqUQK6AAJLrecJsrcuZAHLCKai9x9YZF0Bq99/u9hYQQPbun+oPEdg9gLz8pYNvf5A6+2cfDjkSX2/zI7vW5UwAOemk1Nxr64wLIDX7blc1BASQGn20i+ICAsj3f5N68ZaHtieAvGZqXUgrhq7QQSk6qNVvAaRo422rhIAAUqKNNlFdYKUA0vqP/q1L3ss3IM+9+vFPfvL1/+9f/vznX/+/b//3y56+98/P3lPLrbMyap7IhShSy7PZ815u+UTmidSz07OVtedR80T8R9USCWcr1fJUb6ueiG/kfH/1y59FhhlDgMAFAQHkApahBGYJCCC334C0LiCRS1XkIpM1T+RCFNmTAHL7SYzYZfUgY56MObLOZmSeir6RP9MFkIiSMQSuCQgg17yMJjBFYNcAcuvNx1vAe958PM9R8UL0bPbs8tLrbfC4dRif34Zk2Uw58HcumrXnUfMIIO83OsMmMkfkqAkgESVjCFwTEECueRlNYIqAAHLO33gLIPc/YqOCQ+RtwdOYVj2RC3JrjqxaIvOsVEuWb+S0CSARJWMIXBMQQK55GU1gisBKAeQKQOQNyHs/33FlnUpjb5k9G13xzLos7mR74p536k92ra1+RwJepCYBJKJkDIFrAgLINS+jCUwREECmsE9ZVAC5n711IY38Lf/9q/vkaIFWvwWQ0R2xHoG4gAAStzKSwDSBXQPIM9hHl+ppqIstfOXtxtux/hWsb5rZupAKIIsd+gfLafVbAHkQ2McJdBQQQDrimppAloAAkiW57jwCyOO9aV1IBZDHjVeaodVvAWSlbqmFwGsBAcSJILCBgACyQZMeLFEAeRDQG5DHATebQQDZrGHKJfBCQABxHAhsILB7ANmAeHqJVwJIpNjW5azi24AT9xw5C1XHtPrtDUjVzttXBQEBpEIX7aG8gABSvsWfCSCP97h1Ia0Yuh5X23eGVr8FkH17q/L6AgJI/R7bYQEBAaRAE4NbyPqB/dblrOJl/MQ9B49VyWGtfgsgJdtuU0UEBJAijbSN2gICSO3+vtydAHJ/r1sX0oqh636t/T/Z6rcAsn+P7aCugABSt7d2VkhAACnUzEFbaV3OKl7GT9zzoOO05DKtfgsgS7ZNUQS+FhBAHAQCGwgIIBs0abESW5czAWSxhinnskDrjAsgl0l9gMAwAQFkGLWFCNwv8NdffHH/hy98Mus/2JElW5eHyBy7jVnNd2Q9I3oVOVMj9xypZ4TLyDUq+v7w06eRhNYicISAAHJEm21ydwEBZPcOflP/apezkfWM6GDkwj9yz5F6RriMXKOirwAy8gRZ6xQBAeSUTtvn1gICyNbt+7b41S5nI+sZ0cHIhX/kniP1jHAZuUZFXwFk5Amy1ikCAsgpnbbPrQUEkK3bJ4AMal/kwl/xgjyIN7RMRV8BJNR6gwhcEhBALnEZTGCOgAAyxz171dUuZyPryba8NZ8AMkL54zVGnqlIvzNEBJAMRXMQeC0ggDgRBDYQEEA2aFKgxNUuZyPrCfA8PCRyIR2550g9D296sQkq+gogix0y5ZQQEEBKtNEmqgsIIDU6vNrlbGQ9IzoYufCP3HOknhEuI9eo6CuAjDxB1jpFQAA5pdP2ubWAALJ1+74tfrXL2ch6RnQwcuEfuedIPSNcRq5R0VcAGXmCrHWKgABySqftc2sBAWTr9gkgg9oXufBXvCAP4g0tU9FXAAm13iAClwQEkEtcBhOYIyCAzHHPXnW1y9nIerItb80ngIxQ/niNkWcq0u8MEQEkQ9EcBF4LCCBOBIENBASQDZoUKHG1y9nIegI8Dw+JXEhH7jlSz8ObXmyCir4CyGKHTDklBASQEm20ieoCKwWQyKUqcgmJzFOtr1kuI+fZqQeRMzXSLlLPTr6RWiv6CiCRzhtD4JqAAHLNy2gCUwQEkCns6YuudjmL1JOO0HHCyIU/sueR83TkmDJ1RV8BZMpRsmhxAQGkeINtr4aAAFKjj6tdziL17CQ/MjhE7CL17OQbqTXLJWueSM2tMQJIS8jXCVwXEECum/kEgeECAshw8i4LZl2qRs7TBaLTpJEL/0i7SD2dKKZNW9FXAJl2nCxcWEAAKdxcW6sjIIDU6OVql7NIPTvJRy78kT2PnGcn30itFX0FkEjnjSFwTUAAueZlNIEpAisFkCyAyCUva61V5olczrJqPdE3YqcHEaX7x1T0FUDuPw8+SeA9AQHE2SCwgYAAskGTAiVWvJwFtr3UED3o246KvgJI3zNj9jMFBJAz+27XmwkIIJs17J1yK17OduuMHvTtWEVfAaTvmTH7mQICyJl9t+vNBASQzRomgCzbsIoX5JWwK/oKICudMLVUERBAqnTSPkoLCCA12lvxcrZbZ/Sgb8cq+gogfc+M2c8UEEDO7LtdbyYggGzWMG9Alm1YxQvyStgVfQWQlU6YWqoICCBVOmkfpQUEkBrtrXg5260zetC3YxV9BZC+Z8bsZwoIIGf23a43ExBANmuYNyDLNqziBXkl7Iq+AshKJ0wtVQQEkCqdtI/SAgJIjfZWvJzt1hk96Nuxir4CSN8zY/YzBQSQM/tu15sJCCCbNcwbkGUbVvGCvBJ2RV8BZKUTppYqAgJIlU7aR2kBAaRGeyteznbrjB707VhFXwGk75kx+5kCAsiZfbfrzQQ+/80/2azidrm/+unfNAeNvMw0izGAwOECpz6zX/3yZ4d33vYJ5AsIIPmmZiSQLiCApJOakACBiwICyEUwwwkQeFdAAHE4CGwg0DuA/MUf/9evFX7rj/7xuxqRMVcoT73MXDHadezzWXlb//P5evn1j87crvuvWvepz6w3IFVPtH3NFBBAZupbm0BQQAAJQhm2hIAAskQb0osQQNJJTUjgWAEB5NjW2/hOAr0CSPZbjSump15mrhjtNPa90HFrD9567NTZ72o99Zn1BmTP86rqtQUEkLX7ozoCXwsIIA7C6gICyOoderw+AeRxQzMQIPCNgADiJBDYQKBXAHm79cj35n900Xz7Pf4f/U33qZeZDY7bXSVeORcvF/A25C7uKR869Zn1BmTKcbNocQEBpHiDba+GgABSo4+VdyGAVO7uN3sTQOr32A4JjBIQQEZJW4fAAwKjAshHJd7zLTYf/YzJqZeZB47Bdh+NnBlvQPZp66nPrDcg+5xRle4jIIDs0yuVHiwggBzc/I23LoBs3LwbpQsgtfppNwRmCgggM/WtTSAoIIAEoQxbSkAAWaodDxcjgDxMaAICBH4tIIA4CgQ2EFg9gNz6BXNPrL1/CD3rQpQ1zwZHaWqJtwKJb8Ga2pJLi5/6nPgWrEvHxGACIQEBJMRkEIG5AgLIbf+sC1HWPHNPyfqrCyDr9+ijCk99TgSQvc+t6tcUEEDW7IuqCLwSWCGAvG3Jo99ek3GZyZjjaV9Z8zi2HwsIIHufkFOfEwFk73Or+jUFBJA1+6IqAgLIZ5999qMvf/DhSci6EGXN49gKIJXPwKnPiQBS+VTb2ywBAWSWvHUJXBBY8Q3IrfLf/g13758BuUBo6ASBR9+STSjZkh8ICCCOBwECWQICSJakeQh0FBBAOuKaupuAANKNdsrEAsgUdosSKCkggJRsq01VExBAqnW03n4iYePWrv0rWPucBQFkn16plMDqAgLI6h1SH4HPPvtMAHEMVhcQQFbv0OP1CSCPG5qBAIFvBAQQJ4HABgK9AsjzpfGev4WOXDj9DMgGhyupxMh5eF7qnvOWVKZpHhAQQB7A81ECBF4JCCAOBIENBASQDZp0eIkCSP0DIIDU77EdEhglIICMkrYOgQcEBJAH8Hx0iIAAMoR56iICyFR+ixMoJSCAlGqnzVQV6BVAnr0i34p15YL5PK9vwap6Ij/e13tnxbde7X0eBJC9+6d6AisJCCArdUMtBN4REEAcjZ0EBJCduhWvVQCJWxlJgMDHAgKIE0JgA4HeASRCcOUNSORvuk+9zESsjSGwosCpz6zfhL7iaVTT7gICyO4dVP8RAgLIEW22SQJLCwggS7dHcQS2EhBAtmqXYk8V+Osvvhiy9R99+YPmOh+9CYm8+XheYKXLjFputz3i0jwwiQMi5zNjuci+1XJbepTL0+qRPmWchx9++pQxjTkIEHghIIA4DgQ2EBBA+jYpcpEZdbHarZa+nXk9ux7MvfSvdDYFkJFPnrUI5AsIIPmmZiSQLrBSAMna3EqXGbV4A/JSwHm4/zyMCokCSNafxOYhMEdAAJnjblUClwQEkEtclwe7cN5/4byM/cAHRl1unYf7z8OoHgkgDzxIPkpgAQEBZIEmKIFAS0AAaQk99nUXzvsvnI/JX/v0qMut83D/eRjVIwHk2rNjNIHVBASQ1TqiHgI3BASQvsfChfP+C2ffzryefdTl1nm4/zyM6pEAMvLJsxaBfAEBJN/UjATSBQSQdNJXE7pw3n/h7NsZAcTZfP+ERWwyzqd/BStD0RwEXgsIIE4EgQ0EBJC+TYpcZEb9ze5utfTtjACy23kY9Zx4AzLyybMWgXwBASTf1IwE0gUEkHRSb0ACpJHLb2CatCGjLreRfavldltHuQggaY+ViQhMERBAprBblMA1AQHkmtfV0S6ct8UiLletHxk/6nIb2bdaBJBHzrLPEjhdQAA5/QTY/xYCAkjfNrlwCiAvBZyH+8/DqGDmDUjfPxPNTqC3gADSW9j8BBIEBJC+FyIXzvt9E453eIpRl1vn4f7zMKpHAkj4sTGQwJICAsiSbVEUgdcCAkjfC5EL5/2+I5/VUZdb5+H+8zCqRwLIyCfPWgTyBQSQfFMzEkgXEED6XohcOO/3TT/sH0w46nLrPNx/Hkb1SAAZ+eRZi0C+gACSb2pGAukCAkjfC5EL5/2+6YddAHkl4Gy+fyAiNhnn0+8ByVA0B4HXAgKIE0FgAwEBpG+TIheZUX+zu1stfTvzenY9uK19oos3ICOfPGsRyBcQQPJNzUggXUAASSf1t8wB0kgYCkyTNuTEi3akBye6CCBpj5WJCEwREECmsFuUwDUBAeSa19XRLnm3xSIuV60fGX/iRTvSgxNdBJBHniSfJTBfQACZ3wMVEGgKCCBNoocGuOQJIC8FnIf7z8OoMCSAPPRHng8TmC4ggExvgQIItAUEkLbRIyNcOO+/cD7ifvWzoy63zsP952FUjwSQq0+P8QTWEhBA1uqHagjcFBBA+h4MF877L5x9O/N69lGXW+fh/vMwqkcCyMgnz1oE8gUEkHxTMxJIFxBA0klfTejCef+Fs29nBBBn8/0TFrHJOJ/+Gd4MRXMQeC0ggDgRBDYQEED6NilykRn1N7u71dK3MwLIbudh1HPiDcjIJ89aBPIFBJB8UzMSSBcQQNJJvQEJkEYuv4Fp0oaMutxG9q2W220d5SKApD1WJiIwRUAAmcJuUQLXBASQa15XR7tw3haLuFy1fmT8qMttZN9qEUAeOcs+S+B0AQHk9BNg/1sICCB92+TCKYC8FHAe7j8Po4KZNyB9/0w0O4HeAgJIb2HzE0gQiASQkf/hT9jSUlO4cC7VjunFOA/TWzCsgEiv/RD6sHZY6CABAeSgZtvqvgICSN/eRS4howLeSrX0VV939pV6sFIt63bs/soivgLI/b4+SeA9AQHE2SCwgYAA0rdJkUuIANK3ByvN7jys1I2+tUR6LYD07YHZzxQQQM7su11vJpAdQP7lv/m7TYF//6//T3NMlQGRS4gAUqXb7X04D22jR0b8tx//efPjv/OX/7Q5JmNApNcCSIa0OQi8FhBAnAgCGwgIIH2bFLmECCB9e7DS7M5D324IIH19zU5gBwEBZIcuqfF4gawAEnnz8Rb7hDchLpzHP2KvAJyHPuchEjzertz7TUik196A9DkPZj1bQAA5u/92v4mAANK3UZFLiDcgfXuw0uzOQ59uCCB9XM1KYEcBAWTHrqn5OIFHAsittx5v32o8jznhbcetw+PCedwj9eGGnYfc8/AcPN6+zXjv//60+tuw0utNSKTX3oDkngezEXgSEECcAwIbCAggfZsUuYR4A9K3ByvN7jzkdkMAyfU0G4EKAgJIhS7aQ3mBXgHk7duRl29ATnor4sJZ/hG6tEHn4RJXc/B7bzNufUvW85sOb0CarAYQ2FpAANm6fYo/RUAA6dtpF86+vrvN7jzkdkwAyfU0G4EKAgJIhS7aQ3mB7ADy6T/+r1dmX/zzf/D1//YzIO8fJd+CVf4x+3aDAkhur30LVq6n2QhUEBBAKnTRHsoLCCB9W+zC2dd3t9mdh9yOCSC5nmYjUEFAAKnQRXsoLyCA9G2xC2df391mdx5yOyaA5HqajUAFAQGkQhftobxAVgB5+61Xb+GevxXr5f/9hG/LcuEs/whd2qDzcImrOfie3//xdlL/DG+T2QACWwkIIFu1S7GnCgggfTvvwtnXd7fZnYfcjgkguZ5mI1BBQACp0EV7KC/wSAC5F8c/w/tazg+h33uS9vucAJLbMwEk19NsBCoICCAVumgP5QUEkL4tduHs67vb7M5DbscEkFxPsxGoICCAVOiiPZQXeCSAvP1lg09Yzz/X8dEvInxGPeFNiAtn+Ufo0gadh0tczcECSJPIAALHCQggx7XchncUEED6ds2Fs6/vbrM7D7kdE0ByPc1GoIKAAFKhi/ZQXuCRAPIS5723GR+95fAG5BtBPwNS/jH7doMCSG6vBZBcT7MRqCAggFTooj2UFxBA+rbYhbOv726zOw+5HRNAcj3NRqCCgABSoYv2UF7gkQDy8uc83vudHie85fjokLhwln+ELm3QebjEFR789hcS3voFhZEx4QUDAyO9/uGnT4GZDCFA4IqAAHJFy1gCkwQEkL7wkUuIb8Hq24OVZnce+nQjEi4iYzKri/RaAMkUNxeBbwQEECeBwAYCAkjfJkUuIQJI3x6sNLvz0KcbkXARGZNZXaTXAkimuLkICCDOAIFtBB4JINtscmKhkUuIADKxQYOXdh4Gg09cLtJrAWRigyxdVsAbkLKttbFKAgJI325GLiECSN8erDS787BSN/rWEum1ANK3B2Y/U0AAObPvdr2ZwEoBJPIf7Cxel/4syfvmGdnrSIWjzkOkllFjIj0Y5RKpJctlpT0JIFldNQ+B7wQEEKeBwAYCAkjfJkUuVqMuRH13em32iMu1GR8brQe3/Ua5jDwPK+1JAHnsufVpArcEBBDngsAGAgJI3yZFLlajLkR9d3pt9ojLtRkfG60HAshjJ+j7n46ccQEkW918BPwrWM4AgS0EBJC+bYpcQlx++/YgMrseCCCRc3JlTOTZF0CuiBpLICbgDUjMySgCUwUEkL78kUuIy2/fHkRm1wMBJHJOroyJPPsCyBVRYwnEBASQmJNRBKYKCCB9+SOXEJffvj2IzK4HAkjknFwZE3n2BZArosYSiAkIIDEnowhMFRBA+vJHLiEuv317EJldDwSQyDm5Miby7AsgV0SNJRATEEBiTkYRmCoggPTlj1xCXH779iAyux4IIJFzcmVM5NkXQK6IGksgJiCAxJyMIjBVQADpyx+5hLj89u1BZHY9EEAi5+TKmMizL4BcETWWQExAAIk5GUVgqoAA0pc/cglx+e3bg8jseiCARM7JlTGRZ18AuSJqLIGYgAASczKKwFQBAaQvf+QS4vLbtweR2fVAAImckytjIs++AHJF1FgCMQEBJOZkFIGpAlkBpPUf28gFrzVHJlSknoz1InuK1NKaJ2OOp/2Omqe1nwz7K3NE9n1lvh3GRnoQccmYJzJHlmlkTxlrRfYkgGRIm4PAawEBxIkgsIGAANK3SZFLSORC1JonYw4BpO9ZWG321pmqeh4iz0pGryK+AkiGtDkICCDOAIHtBASQvi2LXEIiF6LWPBlzVL1wRjoc8YvMs9OY1pmqeh5G9TriK4Ds9MSodRcBb0B26ZQ6jxYQQPq2P3IJiVyIWvNkzFH1whnpcMQvMs9OY1pnqup5GNXriK8AstMTo9ZdBASQXTqlzqMFsgJIBmLkP9gZ60QvVhlrRfY06kKUsZ+sOSIuWWtF5tGD20qjXEaeh5X2JIBEnk5jCFwTEECueRlNYIqAANKXPXKxGnUh6rvTa7NHXK7N+NhoPRBAHjtB3/905IwLINnq5iPw2WcCiFNAYAMBAaRvkyKXEJffvj2IzK4HAkjknFwZE3n2BZArosYSiAkIIDEnowhMFRBA+vJHLiEuv317EJldDwSQyDm5Miby7AsgV0SNJRATEEBiTkYRmCoggPTlj1xCXH779iAyux4IIJFzcmVM5NkXQK6IGksgJiCAxJyMIjBVQADpyx+5hLj89u1BZHY9EEAi5+TKmMizL4BcETWWQExAAIk5GUVgqoAA0pc/cglx+e3bg8jseiCARM7JlTGRZ18AuSJqLIGYgAASczKKwFQBAaQvf+QS4vLbtweR2fVAAImckytjIs++AHJF1FgCMQEBJOZkFIGpAgJIX/7IJcTlt28PIrPrgQASOSdXxkSefQHkiqixBGICAkjMySgCUwUEkL78kUuIy2/fHkRm1wMBJHJOroyJPPsCyBVRYwnEBASQmJNRBKYKCCB9+SOXEJffvj2IzK4HAkjknFwZE3n2BZArosYSiAkIIDEnowhMFRBA+vJHLiF9K7g2+6iL+Goup+67dToquqy0JwGkdQJ9ncB1AQHkuplPEBguIID0JV/tot3a7UqXs1atmV8/dd8tw4ouK+1JAGmdQF8ncF1AALlu5hMEhgsIIH3JBZDbvqu5rHQp7Xsir81e0WWlPQkg186j0QQiAgJIRMkYApMFBJC+DVjtot3a7UqXs1atmV8/dd8tw4ouK+1JAGmdQF8ncF1AALlu5hMEhgsIIH3JBRBvQF4KOA/zz4MA0vfPPLMTmC0ggMzugPUJBAQEkADSA0NcOOdfOCPtW+lSGql31JiKLivtyRuQUSfZOicJCCAnddtetxUQQPq2TgARQLwBaT9jI58TAaTdDyMI7CwggOzcPbUfIyCA9G31yItVxk5Wupxl7Cc6x6n7bvlUdFlpT96AtE6grxO4LiCAXDfzCQLDBQSQvuQCiDcg3oC0n7GRz4kA0u6HEQR2FhBAdu6e2o8REED6tnrkxSpjJytdzjL2E53j1H23fCq6rLQnb0BaJ9DXCVwXEECum/kEgeECAkhfcgHEGxBvQNrP2MjnRABp98MIAjsLCCA7d0/txwgIIH1bPfJilbGTyOUssqfWPJE5MvYTnSOj3tYcT7Wstu+WT9aeWvOMdGnV0jKJfj2yJ29AoprGEYgLCCBxKyMJTBMQQPrSRy4hfSu4NnvkchbZU2ueyBzXKn9sdEa9rTkEkPd7NPI8RPr02Gn65tORPQkgGdLmIPBaQABxIghsICCA9G1S5BLSt4Jrs0cuZ5E9teaJzHGt8sdGZ9TbmiN6KX1sJ7mfztpTa56R56FVS5ZgZE8CSJa2eQh8JyCAOA0ENhAQQPo2KXIJ6VvBtdkjl7PInlrzROa4VvljozPqbc0hgLzfo5HnIdKnx07TN5+O7EkAyZA2B4HXAgKIE0FgAwEBpG+TIpeQvhVcmz1yOYvsqTVPZI5rlT82OqPe1hzRS+ljO8n9dNaeWvOMPA+tWrIEI3sSQLK0zUPgOwEBxGkgsIGAANK3SZFLSN8Krs2+0uXsWuWPjT513y21ii4r7UkAaZ1AXydwXUAAuW7mEwSGCwggfckFkNu+q7msdCnteyKvzV7RZaU9CSDXzqPRBCICAkhEyRgCkwUEkL4NWO2i3drtSpezVq2ZXz913y3Dii4r7UkAaZ1AXydwXUAAuW7mEwSGCwggfckFEG9AXgo4D/PPgwDS9888sxOYLSCAzO6A9QkEBASQANIDQ1w45184I+1b6VIaqXfUmIouK+3JG5BRJ9k6JwkIICd12163FRBA+rZOABFAvAFpP2MjnxMBpN0PIwjsLCCA7Nw9tR8jIID0bfXIi1XGTla6nGXsJzrHqftu+VR0WWlP3oC0TqCvE7guIIBcN/MJAsMFBJC+5AKINyDegLSfsZHPiQDS7ocRBHYWEEB27p7ajxEQQPq2euTFKmMnK13OMvYTnePUfbd8KrqstCdvQFon0NcJXBcQQK6b+QSB4QICSF9yAcQbEG9A2s/YyOdEAGn3wwgCOwsIIDt3T+3HCAggfVs98mKVsZOVLmcZ+4nOceq+Wz4VXVbakzcgrRPo6wSuCwgg1818gsBwgZUCyPDNW5AAAQKdBCJ/+SCAdMI37dECAsjR7bf5XQQEkF06pU4CBHYSEEB26pZaKwkIIJW6aS9lBQSQsq3dfmP/7cd/3tzD7/zlP22OMYDADAEBZIa6NQl89pkA4hQQ2EBAANmgSYeWKIAc2vgi2xZAijTSNrYTEEC2a5mCTxQQQE7s+tp7jgSPtzvwJmTtnp5YnQByYtfteQUBAWSFLqiBQENAAHFEVhMQQFbriHruERBA7lHzGQKPCwggjxuagUB3AQGkO3HXBX7vD3736/n/7E9/0XWdEZO/DR633mo8j/HGY0RHrPGIgADyiJ7PErhfQAC5384nCQwTEECGUXdZSADpwmpSAg8LCCAPE5qAwF0CAshdbD5EYKyAADLW22rvC7z3BuTWt2Q9vwHxRsSJWlVAAFm1M+qqLiCAVO+w/ZUQEEBKtLHEJgSQEm20iV8LCCCOAoE5AgLIHHerErgkIIBc4jK4o4AA0hHX1MMFBJDh5BYk8LWAAOIgENhAQADp26TIJeRHX/6gWcR78/zhb/z0689Gfgi9dy0vtXhqEwAAEPpJREFUN9Ha061afvVXf/XKIeuH0DP2nTHH0+ay5mkeGAOmC0R6/cNPn6bXqQAC1QQEkGodtZ+SAgJI37ZGLiGty/pHF1cBpN2/jB5kzCGAtHtVaUTkzAgglTpuL6sICCCrdEIdBD4QEED6Ho/IJeSRAPJcfcYcT3ONmifyBuRKZz76Z3kzepAxhwBypaP7j42cGQFk/z7bwXoCAsh6PVERge8JCCB9D0XkEpJx6c+YQwB5/yyM6mO0B31PrdkzBCJnRgDJkDYHgdcCAogTQWADAQFkgyYdUuI9vwH9mcYvJjzkkCy4zb/44//6dVW/9Uf/+FV1AsiCzVLSEQICyBFttsndBQSQ3TtYp34BpE4vT9qJAHJSt+11BwEBZIcuqfF4AQHk+COwDIAAskwrFHJBQAC5gGUogQECAsgAZEsQeFRAAHlU0OezBASQLEnzjBQQQEZqW4tAW0AAaRsZQWC6gAAyvQUK+LWAAOIoVBLwMyCVumkvOwkIIDt1S63HCgggx7Z+uY0LIMu1REEPCAggD+D5KIEHBASQB/B8lMAoAQFklLR1WgICSEvI13cSEEB26pZaKwkIIJW6aS9lBQSQsq3dbmPPAeT5n9R9+7+fNhQZs93GFVxSQAAp2Vab2kBAANmgSUokIIA4A6sIRMJFZMwq+1HH2QICyNn9t/t5AgLIPHsrEwgLCCBhKgMJECAQFhBAwlQGEkgVEEBSOU1GoI+AANLH1awECJwtIICc3X+7nycggMyztzKBsEAkgIQnM5AAAQIEwgI//PQpPNZAAgRiAgJIzMkoAlMFBJCp/BYnQOBgAQHk4ObbejcBAaQbrYkJ5AkIIHmWZiJAgMAVAQHkipaxBGICAkjMySgCUwUEkKn8FidA4GABAeTg5tt6NwEBpButiQnkCXz++ed5k5mJAAECBMICX331VXisgQQIxAQEkJiTUQSmCgggU/ktToDAwQICyMHNt/VuAgJIN1oTE8gTEEDyLM1EgACBKwICyBUtYwnEBASQmJNRBKYKCCBT+S1OoKzAf/rt3/7e3n7/v//3svu9Z2MCyD1qPkPgYwEBxAkhsIGAALJBk5RIYEMBAaTdNAGkbWQEgasCAshVMeMJTBAQQCagW5JAYYHn4HHrbcfbUHL6GxEBpPCDYGvTBASQafQWJhAXEEDiVkYSINAWEEDaRs8jBJC4lZEEogICSFTKOAITBQSQifiWJnCYwNtw8lFYOYFGADmhy/Y4WkAAGS1uPQJ3CAggd6D5CAECdwkIIK/ZBJC7jpEPEfhQQABxQAhsICCAbNAkJRIoIvDeG4+XPxty0s+FCCBFDrZtLCUggCzVDsUQuC0ggDgZBAiMEhBAvAEZddasc66AAHJu7+18IwEBZKNmKZXApgKR4PG8NW9ANm2ysgksIiCALNIIZRD4SEAAcT4IEOgtIIDcFvYtWL1PnvlPFBBATuy6PW8nIIBs1zIFE9hOQAARQLY7tAreVkAA2bZ1Cj9JQAA5qdv2SmCcwEc/WH7rt6S/reyEb8XyBmTcebTSOQICyDm9ttONBQSQjZundAILCwgg7eYIIG0jIwhcFRBArooZT2CCgAAyAd2SBA4QuPWW4/mthjcg3xwAAeSAB8EWhwsIIMPJLUjguoAAct3MJwgQaAsIIG0jAaRtZASBqwICyFUx4wlMEBBAJqBbkkBhgStvNz4KKYWJvt2aAHJCl+1xtIAAMlrcegTuEBBA7kDzEQIE3hUQQOKHQwCJWxlJICoggESljCMwUUAAmYhvaQIFBa4EkILbv7QlAeQSl8EEQgICSIjJIAJzBQSQuf5WJ1BNQACJd1QAiVsZSSAqIIBEpYwjMFFAAJmIb2kChQVO//mOSGsFkIiSMQSuCQgg17yMJjBFQACZwm5RAuUFBJB2iwWQtpERBK4KCCBXxYwnMEFAAJmAbkkCBwgIIO0mCyBtIyMIXBUQQK6KGU9ggoAAMgHdkgQIEPCLCJ0BAl0EBJAurCYlkCsggOR6mo0AAQJRAW9AolLGEYgLCCBxKyMJTBMQQKbRW5gAgcMFBJDDD4DtdxEQQLqwmpRAroAAkutpNgIECEQFBJColHEE4gICSNzKSALTBASQafQWJkDgcAEB5PADYPtdBASQLqwmJZArIIDkepqNAAECUQEBJCplHIG4gAAStzKSwDQBAWQavYUJEDhcQAA5/ADYfhcBAaQLq0kJ5AoIILmeZiNAgEBUQACJShlHIC4ggMStjCQwTUAAmUZv4QcEfv7pJ3d/+idf/Pzrz947x/Pn3yvgo3lbn717Uz64pYAAsmXbFL24gACyeIOUR+BJQABxDnYUuDc8PO1VANmx4zVrFkBq9tWu5goIIHP9rU4gJCCAhJgMWkxAAFmsIcq5S0AAuYvNhwh8KCCAOCAENhAQQDZokhIfEngbVj56A/LIt0jdCkXvrfXIOg9h+PBSAgLIUu1QTBEBAaRII22jtoAAUru/dvf9n/UQQJyKVQQEkFU6oY5KAgJIpW7aS1kBAaRsa4/f2HtvPp5hPnpjcQWvtc7Lua6MvVKDsXsKCCB79k3VawsIIGv3R3UEvhYQQByEqgKty74AUrXz++xLANmnVyrdR0AA2adXKj1YQAA5uPkFt34lVNz7g+xvf36jFXS8ASl40JK2JIAkQZqGwAsBAcRxILCBgACyQZOUGBYQQMJUBi4gIIAs0AQllBMQQMq11IYqCgggFbt67p6uBJArSln/wtWVtyVX6jN2TwEBZM++qXptAQFk7f6ojsDXAgKIg1BJQACp1M36exFA6vfYDscLCCDjza1I4LKAAHKZzAcWFOgVPJ63Gpk/6y3JgrxK6iQggHSCNe3RAgLI0e23+V0EBJBdOqXOjwQiAeERwcj8Asgjwmd+VgA5s+923VdAAOnra3YCKQICSAqjSSYLRALCIyVG5hdAHhE+87MCyJl9t+u+AgJIX1+zE0gREEBSGE0yWSASEN6WmPXP8F6Z9+0/4TuZzfKTBQSQyQ2wfEkBAaRkW22qmoAAUq2jZ+5HADmz77vvWgDZvYPqX1FAAFmxK2oi8EZAAHEkCBAgMEdAAJnjbtXaAgJI7f7aXREBAaRII22DAIHtBASQ7Vqm4A0EBJANmqREAgKIM0CAAIE5AgLIHHer1hYQQGr31+6KCAggRRppGwQIbCcggGzXMgVvICCAbNAkJRIQQJwBAgQIzBEQQOa4W7W2gABSu792V0RAACnSSNsgQGA7AQFku5YpeAMBAWSDJimRgADiDBAgQGCOgAAyx92qtQUEkNr9tbsiAgJIkUbaBgEC2wkIINu1TMEbCAggGzRJiQQEEGeAAAECcwQEkDnuVq0tIIDU7q/dFREQQIo00jYIENhOQADZrmUK3kBAANmgSUokIIA4AwQIEJgjIIDMcbdqbQEBpHZ/7a6IgABSpJG2QYDAdgICyHYtU/AGAgLIBk1SIgEBxBkgQIDAHAEBZI67VWsLCCC1+2t3RQQEkCKNtA0CBLYTEEC2a5mCNxAQQDZokhIJCCDOAAECBOYICCBz3K1aW0AAqd1fuysiIIAUaaRtECCwnYAAsl3LFLyBgACyQZOUSEAAcQYIECAwR0AAmeNu1doCAkjt/tpdEQEBpEgjbYMAge0EBJDtWqbgDQQEkA2apEQCAogzQIAAgTkCAsgcd6vWFhBAavfX7ooICCBFGmkbBAhsJyCAbNcyBW8gIIBs0CQlEhBAnAECBAjMERBA5rhbtbaAAFK7v3ZXREAAKdJI2yBAYDsBAWS7lil4AwEBZIMmKZGAAOIMECBAYI6AADLH3aq1BQSQ2v21uyICAkiRRtoGAQLbCQgg27VMwRsICCAbNEmJBAQQZ4AAAQJzBASQOe5WrS0ggNTur90VERBAijTSNggQ2E5AANmuZQreQEAA2aBJSiQggDgDBAgQmCMggMxxt2ptAQGkdn/troiAAFKkkbZBgMB2AgLIdi1T8AYCAsgGTVIiAQHEGSBAgMAcAQFkjrtVawsIILX7a3dFBASQIo20DQIEthMQQLZrmYI3EBBANmiSEgkIIM4AAQIE5ggIIHPcrVpbQACp3V+7KyIggBRppG0QILCdgACyXcsUvIGAALJBk5RIQABxBggQIDBHQACZ427V2gICSO3+2l0RAQGkSCNtgwCB7QQEkO1apuANBASQDZqkRAICiDNAgACBOQICyBx3q9YWEEBq99fuCBAgQIAAAQIECCwlIIAs1Q7FECBAgAABAgQIEKgtIIDU7q/dESBAgAABAgQIEFhKQABZqh2KIUCAAAECBAgQIFBbQACp3V+7I0CAAAECBAgQILCUgACyVDsUQ4AAAQIECBAgQKC2gABSu792R4AAAQIECBAgQGApAQFkqXYohgABAgQIECBAgEBtAQGkdn/tjgABAgQIECBAgMBSAgLIUu1QDAECBAgQIECAAIHaAgJI7f7aHQECBAgQIECAAIGlBASQpdqhGAIECBAgQIAAAQK1BQSQ2v21OwIECBAgQIAAAQJLCQggS7VDMQQIECBAgAABAgRqCwggtftrdwQIECBAgAABAgSWEhBAlmqHYggQIECAAAECBAjUFhBAavfX7ggQIECAAAECBAgsJSCALNUOxRAgQIAAAQIECBCoLSCA1O6v3REgQIAAAQIECBBYSkAAWaodiiFAgAABAgQIECBQW0AAqd1fuyNAgAABAgQIECCwlIAAslQ7FEOAAAECBAgQIECgtoAAUru/dkeAAAECBAgQIEBgKQEBZKl2KIYAAQIECBAgQIBAbQEBpHZ/7Y4AAQIECBAgQIDAUgICyFLtUAwBAgQIECBAgACB2gICSO3+2h0BAgQIECBAgACBpQQEkKXaoRgCBAgQIECAAAECtQUEkNr9tTsCBAgQIECAAAECSwkIIEu1QzEECBAgQIAAAQIEagsIILX7a3cECBAgQIAAAQIElhIQQJZqh2IIECBAgAABAgQI1BYQQGr31+4IECBAgAABAgQILCUggCzVDsUQIECAAAECBAgQqC0ggNTur90RIECAAAECBAgQWEpAAFmqHYohQIAAAQIECBAgUFtAAKndX7sjQIAAAQIECBAgsJSAALJUOxRDgAABAgQIECBAoLaAAFK7v3ZHgAABAgQIECBAYCkBAWSpdiiGAAECBAgQIECAQG0BAaR2f+2OAAECBAgQIECAwFICAshS7VAMAQIECBAgQIAAgdoCAkjt/todAQIECBAgQIAAgaUEBJCl2qEYAgQIECBAgAABArUFBJDa/bU7AgQIECBAgAABAksJCCBLtUMxBAgQIECAAAECBGoLCCC1+2t3BAgQIECAAAECBJYSEECWaodiCBAgQIAAAQIECNQWEEBq99fuCBAgQIAAAQIECCwlIIAs1Q7FECBAgAABAgQIEKgtIIDU7q/dESBAgAABAgQIEFhKQABZqh2KIUCAAAECBAgQIFBbQACp3V+7I0CAAAECBAgQILCUgACyVDsUQ4AAAQIECBAgQKC2gABSu792R4AAAQIECBAgQGApAQFkqXYohgABAgQIECBAgEBtAQGkdn/tjgABAgQIECBAgMBSAgLIUu1QDAECBAgQIECAAIHaAgJI7f7aHQECBAgQIECAAIGlBASQpdqhGAIECBAgQIAAAQK1BQSQ2v21OwIECBAgQIAAAQJLCQggS7VDMQQIECBAgAABAgRqCwggtftrdwQIECBAgAABAgSWEhBAlmqHYggQIECAAAECBAjUFhBAavfX7ggQIECAAAECBAgsJSCALNUOxRAgQIAAAQIECBCoLfD/AaTzE41g8rbkAAAAAElFTkSuQmCC\" width=\"640\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"<matplotlib.animation.FuncAnimation at 0x7fcca59262e8>"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"plot_animation(frames)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 추가 자료"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 브레이크아웃(Breakout)을 위한 전처리"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"다음은 Breakout-v0 아타리 게임을 위한 DQN을 훈련시키기 위해 사용할 수 있는 전처리 함수입니다:"
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [],
"source": [
"def preprocess_observation(obs):\n",
" img = obs[34:194:2, ::2] # 자르고 크기를 줄입니다.\n",
" return np.mean(img, axis=2).reshape(80, 80) / 255.0"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {},
"outputs": [],
"source": [
"env = gym.make(\"Breakout-v0\")\n",
"obs = env.reset()\n",
"for step in range(10):\n",
" obs, _, _, _ = env.step(1)\n",
"\n",
"img = preprocess_observation(obs)"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,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AQIzCOQ5Ye94xzvWu592PXIr6Y1vfON1ofL0pz991x/3+YUK5Ll/T37yk1cvfelLV1e4whUWqmDZBAgQIECAAAECBMYSUMSNlafVECBQVCC3YebtqXnu2EG3kBYd21iNBPLCiEc84hHrW21veMMbNprCaQkQIECAAAECBAgQOGkBRdxJi/o+AgQIECBAgAABAgQIECBAgAABAgcIKOJcFgQIECBAgAABAgQIECBAgAABAgRmEFDEzYDsFAQIECBAgAABAgQIECBAgAABAgQUca4BAgQIECBAgAABAgQIECBAgAABAjMIKOJmQHYKAgQIECBAgAABAgQIECBAgAABAoo41wABAgQIECBAgAABAgQIECBAgACBGQQUcTMgOwUBAgQIECBAgAABAgQIECBAgAABRZxrYFaBS1ziErOez8kIECBAgMAcAhdffPEcp3EOAgQIECBAgACBzgUUcZ0H2Nv4irjeEjMvAQIECGwjoIjbRslnCBAgQIAAAQIEFHGugVkFFHGzcjsZAQIECMwkoIibCdppCBAgQIAAAQKdCyjiOg+wt/EVcb0lZl4CBAgQ2EZAEbeNks8QIECAAAECBAgo4lwDswoo4mbldjICBAgQmElAETcTtNMQIECAAAECBDoXUMR1HmBv4yviekvMvAQIECCwjYAibhslnyFAgAABAgQIEFDEuQZmFVDEzcrtZAQIECAwk4AibiZopyFAgAABAgQIdC6giOs8wN7GV8T1lph5CRAgQGAbAUXcNko+Q4AAAQIECBAgoIhzDcwqoIibldvJCBAgQGAmAUXcTNBOQ4AAAQIECBDoXEAR13mAvY2viOstMfMSIECAwDYCirhtlHyGAAECBAgQIEBAEecamFVAETcrt5MRIECAwEwCiriZoJ2GAAECBAgQINC5gCKu8wB7G18R11ti5iVAgACBbQQUcdso+QwBAgQIECBAgIAizjUwq4AiblZuJyNAgACBmQQUcTNBOw0BAgQIECBAoHMBRVznAfY2viKuTmIXXnhh82EuuOCCrWcw79ZUpz64i+/u3972J3q7HtpqTXv2XbIY+ZpUxE17nfl2AgQIECBAgMAoAoq4UZLsZB2KuDpB7fLL81RT7/JLuXl3T2EX392/ve1P9HY9tNWa9uy7ZDHyNamIm/Y68+0ECBAgQIAAgVEEFHGjJNnJOhRxdYLa5Zfn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F37hjO9LSfb5n//5Z/zzOYq4rC+/+B915P9uvOIVr7j+yBve8IbVta51rdWXfumXrn7xF3/xyJ/713/912OvlEte8pKrD/iAD1hlJ9L7v//7r25yk5us/viP//jIn8u5f/u3f/vAz/zd3/3d6kM+5EOOPe9RH3jhC1+4+qzP+qz1OfI/HbUENtfgV3zFV6xLuJZF3F/91V+tcr1c//rXX33SJ33SiUD9wR/8weqzP/uzV9/xHd+x+r7v+75ji7h8/ulPf/rqzW9+8+ryl7/86g53uMPqFre4xRmz/MVf/MXqute97mrjlg+8+93vXt3gBjdY3fzmN1895jGPOZH5fQmBngQUcT2lNcCsirgBQrQEAgQIEDhDQBHnojhJgc0vrkd954Mf/ODV937v9576yFFF3KYw2P99+fl8z/7joCLur//6r1c//dM/feQyv+u7vmuVgmtzHLUj7nd/93cP/KV97wlym16KshzbFnGbYu24PB796EevvvZrv3anIu647/zf//3fs74FN7sZzzvvvLVJdiBtjvy75YlPfOLqZ37mZ1Z//ud/vvrP//zPdemRwuRbvuVb1gXH/uOd73zn6qEPfejqaU972rok+eiP/ujVXe5yl/XOxA/90A89bhln/eetZk0x+yd/8idbz32zm91sdde73vW0z//Yj/3Y6u///u/P+I473vGO65J27zW4TRGX6/BHfuRH1rm98Y1vXOXvVHbPPfCBD1wXwPuPXXfEZQdmduE94hGPWH/ncceLX/zi1XOe85wzPpZbx9/znvesr6WXvvSlWxVx2Rn4VV/1VaunPvWp6++79KUvvdqU31/wBV+wvl4//MM//NS5Diri8ofPf/7zV8ki13tMHQSWJKCIW1LaBdaqiCsQghEIECBA4MQFFHEnTrroL3z7299+WhkTjJRSufXxUY961Oq1r33t+ha5W93qVqecjiridsU8qIjb7JY56ruyw+2yl73sqY9sU8Sl6PjiL/7iA782z5Xb/Nm2RVz+Lqb8OOzIrrcnPOEJ6x2CX/ZlX3ZsEfeqV71q9cmf/MnHEm6eq5dbC3OL4a7Hl3zJl6wzTeH5kR/5kad+/B73uMd6B+BlLnOZ1d3udrfVR33UR63+9E//dL3zMTv5siPpNre5zanP//u///u6OEppl/It5V4KlpRy2T2V9Z/rzr3D1tZq1tw2mUy3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Am0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAJCXKZpOAsBAgQIECBAgAABAgQIECBAgEBbASGu7WhdjAABAgQIECBAgAABAgQIECBAIJOAEJdpGs5CgAABAgQIECBAgAABAgQIECDQVkCIaztaFyNAgAABAgQIECBAgAABAgQIEMgkIMRlmoazECBAgAABAgQIECBAgAABAgQItBUQ4tqO1sUIECBAgAABAgQIECBAgAABAgQyCQhxmabhLAQIECBAgAABAgQIECBAgAABAm0FhLi2o3UxAgQIECBAgAABAgQIECBAgACBTAL/BQoPio6BQ6d7AAAAAElFTkSuQmCC\" width=\"1000\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(10, 6))\n",
"plt.subplot(121)\n",
"plt.title(\"원본 관측 (160×210 RGB)\")\n",
"plt.imshow(obs)\n",
"plt.axis(\"off\")\n",
"plt.subplot(122)\n",
"plt.title(\"전처리된 관측 (80×80 그레이스케일)\")\n",
"plt.imshow(img, interpolation=\"nearest\", cmap=\"gray\")\n",
"plt.axis(\"off\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"여기서 볼 수 있듯이 하나의 이미지는 볼의 방향과 속도에 대한 정보가 없습니다. 이 정보들은 이 게임에 아주 중요합니다. 이런 이유로 실제로 몇 개의 연속된 관측을 연결하여 환경의 상태를 표현하는 것이 좋습니다. 한 가지 방법은 관측당 하나의 채널을 할당하여 멀티 채널 이미지를 만드는 것입니다. 다른 방법은 `np.max()` 함수를 사용해 최근의 관측을 모두 싱글 채널 이미지로 합치는 것입니다. 여기에서는 이전 이미지를 흐리게하여 DQN이 현재와 이전을 구분할 수 있도록 했습니다."
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {},
"outputs": [],
"source": [
"def combine_observations_multichannel(preprocessed_observations):\n",
" return np.array(preprocessed_observations).transpose([1, 2, 0])\n",
"\n",
"def combine_observations_singlechannel(preprocessed_observations, dim_factor=0.5):\n",
" dimmed_observations = [obs * dim_factor**index\n",
" for index, obs in enumerate(reversed(preprocessed_observations))]\n",
" return np.max(np.array(dimmed_observations), axis=0)\n",
"\n",
"n_observations_per_state = 3\n",
"preprocessed_observations = deque([], maxlen=n_observations_per_state)\n",
"\n",
"obs = env.reset()\n",
"for step in range(10):\n",
" obs, _, _, _ = env.step(1)\n",
" preprocessed_observations.append(preprocess_observation(obs))"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support.' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option)\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>')\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" event.shiftKey = false;\n",
" // Send a \"J\" for go to next cell\n",
" event.which = 74;\n",
" event.keyCode = 74;\n",
" manager.command_mode();\n",
" manager.handle_keydown(event);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABOIAAALuCAYAAAADlLkHAAAgAElEQVR4XuzdCayuV1k24K8iDjggkyggagQUTYEyRJxHFKIMjjFGRUPAOAVOo0AiiiAqIhQEgyEoEESDxlkicUJRjIpQRCqIEhQUooKICioo9s/7/Tl4aE979m7ftZ5hXTtp8v9277We57pfcnbvs/f3XXTllVdeefBBgAABAgQIECBAgAABAgQIECBAgMBQgYsUcUN9HU6AAAECBAgQIECAAAECBAgQIEDgKKCI8yAQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIECAAAECBAgQIECAAAECBAgQmCCgiJuA7AoCBAgQIECAAAECBAgQIECAAAECijjPAAECBAgQIECAAAECBAgQIECAAIEJAoq4CciuIECAAAECBAgQIECAAAECBAgQIKCI8wwQIEBgosB73vOe4203uMENdr/1yiuvPGznv9/7vd/xnxU+Rnqu4GdHAgQIECDg+wfPAAECBOYKKOLmeruNAIGCAn/zN39zeMMb3nC4853vfLjJTW5yvTb4uI/7uMM73vGOw1vf+tbrdc72xa9+9auP/3zO53zO4Ra3uMXhBS94weG+973v4dGPfvTh+77v+67z+ds35C9+8YsPN77xjQ+XXHLJdT7nfF/4a7/2a4fXvva1h4c85CGHD//wDz/12S960YsOb3vb2w5f+ZVfefzazfPtb3/78R8fBAgQ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0F1JgAABAgQIECBAgAABAgQIECCwnoAibr3MbUyAAAECBAgQIECAAAECBAgQIBAgoIgLQHclAQIECBAgQIAAAQIECBAgQIDAegKKuPUytzEBAgQIECBAgAABAgQIECBAgECAgCIuAN2VBAgQIECAAAECBAgQIECAAAEC6wko4tbL3MYECBAgQIAAAQIECBAgQIAAAQIBAoq4AHRXEiBAgAABAgQIECBAgAABAgQIrCegiFsvcxsTIECAAAECBAgQIECAAAECBAgECCjiAtBdSYAAAQIECBAgQIAAAQIECBAgsJ6AIm69zG1MgAABAgQIECBAgAABAgQIECAQIKCIC0B3JQECBAgQIECAAAECBAgQIECAwHoCirj1MrcxAQIECBAgQIAAAQIECBAgQIBAgIAiLgDdlQQIECBAgAABAgQIECBAgAABAusJKOLWy9zGBAgQIECAAAECBAgQIECAAAECAQKKuAB0VxIgQIAAAQIECBAgQIAAAQIECKwnoIhbL3MbEyBAgAABAgQIECBAgAABAgQIBAgo4gLQXUmAAAECBAgQIECAAAECBAgQILCegCJuvcxtTIAAAQIECBAgQIAAAQIECBAgECCgiAtAdyUBAgQIECBAgAABAgQIECBAgMB6Aoq49TK3MQECBAgQIECAAAECBAgQIECAQICAIi4A3ZUECBAgQIAAAQIECBAgQIAAAQLrCSji1svcxgQIECBAgAABAgQIECBAgAABAgECirgAdFcSIECAAAECBAgQIECAAAECBAisJ6CIWy9zGxMgQIAAAQIECBAgQIAAAQIECAQIKOIC0F1JgAABAgQIECBAgAABAgQIECCwnoAibr3MbUyAAAECBAgQIECAAAECBAgQIBAgoIgLQHclAQIECBAgQIAAAQIECBAgQIDAegKKuPUytzEBAgQIECBAgAABAgQIECBAgECAgCIuAN2VBAgQIECAAAECBAgQIECAAAEC6wko4tbL3MYECBAgQIAAAQIECBAgQIAAAQIBAoq4AHRXEiBAgAABAgQIECBAgAABAgQIrCegiFsvcxsTIECAAAECBAgQIECAAAECBAgECCjiAtBdSYAAAQIECBAgQIAAAQIECBAgsJ6AIm69zG1MgAABAgQIECBAgAABAgQIECAQIKCIC0B3JQECBAgQIECAAAECBAgQIECAwHoCirj1MrcxAQIECBAgQIAAAQIECBAgQIBAgIAiLgDdlQQIECBAgAABAgQIECBAgAABAusJKOLWy9zGBAgQIECAAAECBAgQIECAAAECAQKKuAB0VxIgQIAAAQIECBAgQIAAAQIECKwnoIhbL3MbEyBAgAABAgQIECBAgAABAgQIBAgo4gLQXUmAAAECBAgQIECAAAECBAgQILCegCJuvcxtTIAAAQIECBAgQIAAAQIECBAgECCgiAtAdyUBAgQIECBAgAABAgQIECBAgMB6Aoq49TK3MQECBAgQ+H/t2DENAAAAwjD/rpGxgyoglA8CBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgYAjLkAXSYAAAQIECBAgQIAAAQIECBAg8CfgiPvbXGMCBAgQIECAAAECBAgQIECAAIFAwBEXoIskQIAAAQIECBAgQIAAAQIECBD4E3DE/W2uMQECBAgQIECAAAECBAgQIECAQCDgiAvQRRIgQIAAAQIECBAgQIAAAQIECPwJOOL+NteYAAECBAgQIECAAAECBAgQIEAgEHDEBegiCRAgQIAAAQIECBAgQIAAAQIE/gQccX+ba0yAAAECBAgQIECAAAECBAgQIBAIOOICdJEECBAgQIAAAQIECBAgQIAAAQJ/Ao64v801JkCAAAECBAgQIECAAAECBAgQCAQccQG6SAIECBAgQIAAAQIECBAgQIAAgT8BR9zf5hoTIECAAAECBAgQIECAAAECBAgEAo64AF0kAQIECBAgQIAAAQIECBAgQIDAn4Aj7m9zjQkQIECAAAECBAgQIECAAAECBAIBR1yALpIAAQIECBAgQIAAAQIECBAgQOBPwBH3t7nGBAgQIECAAAECBAgQIECAAAECgcAAPKlSj3ktttsAAAAASUVORK5CYII=\" width=\"1000\">"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"img1 = combine_observations_multichannel(preprocessed_observations)\n",
"img2 = combine_observations_singlechannel(preprocessed_observations)\n",
"\n",
"plt.figure(figsize=(10, 6))\n",
"plt.subplot(121)\n",
"plt.title(\"멀티 채널 상태\")\n",
"plt.imshow(img1, interpolation=\"nearest\")\n",
"plt.axis(\"off\")\n",
"plt.subplot(122)\n",
"plt.title(\"싱글 채널 상태\")\n",
"plt.imshow(img2, interpolation=\"nearest\", cmap=\"gray\")\n",
"plt.axis(\"off\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 연습문제 해답"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Coming soon..."
]
}
],
"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.5.5"
},
"nav_menu": {},
"toc": {
"navigate_menu": true,
"number_sections": true,
"sideBar": true,
"threshold": 6,
"toc_cell": false,
"toc_section_display": "block",
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 1
}