{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "nbpresent": { "id": "d84a79e5-1ef9-4dea-9e2e-690a912553b4" }, "slideshow": { "slide_type": "slide" } }, "source": [ "# CFFI, Ctypes, Cython, Cppyy: \n", "# Wrapping C code from Python\n", "\n", "\n", "## Matti Picus\n", "\n", "### Follow along at https://github.com/mattip/c_from_python" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "0cfda04e-01da-4674-a90f-b132bbebf844" }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Who am I?\n", "\n", "- Core PyPy developer (ask me later)\n", "- Working on NumPy 2018 - 2020\n", "- Experienced (old?) programmer, been doing image processing on and off since 1980\n", "- Open Source evangelist" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "7a62ed08-bb60-4ebb-b5f2-fbb7af3a0b6e" } }, "source": [ "## Thanks for tuning in.\n", "\n", "Here is what we will do\n", "\n", "

\n", "
\n", "The ``mandelbrot`` fractal image \n", " \n", " - Pure Python \n", " - Pure C \n", " - Measure speed of execution\n", "
\n", "

\n", "
\n", "How to mix C and Python \n", " - Ctypes \n", " - CFFI\n", " - Cython\n", " - Cppyy\n", "
\n", "

\n", "
\n", "Comparison \n", " - Boilerplate \n", " - Maintenance \n", " - Speed\n", "
\n", "

\n", "A pop quiz\n", "

\n", "Questions" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "scrolled": false }, "outputs": [], "source": [ "# Ignore the man behind the curtain\n", "from __future__ import print_function, division\n", "%matplotlib notebook\n", "from timeit import default_timer as timer\n", "import numpy as np\n", "from PIL import Image\n", "import subprocess\n", "import os, sys\n", "import numpy as np\n", "from matplotlib.pylab import imshow, show, figure, subplots, get_cmap" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "f0475485-ed91-4e5b-a3bd-7469b3b8947b" } }, "source": [ "## Our mission\n", "\n", "Create a fractal image. Hmm, what is an image? We decide to define a simple structure to hold the image: width, height, data-as-pointer" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "nbpresent": { "id": "848e2511-04eb-46bf-9d63-633d999a87ee" }, "scrolled": false }, "outputs": [], "source": [ "class Img(object):\n", " def __init__(self, width, height):\n", " self.width = width\n", " self.height = height\n", " self.data = bytearray(width*height)\n", "\n", "width = 1500\n", "height = 1000\n", "image = Img(width, height)" ] }, { "cell_type": "markdown", "metadata": { "nbpresent": { "id": "38c6569d-3e69-4e8e-9638-25fc31c455ec" } }, "source": [ "OK, how do we make a fractal image? We loop over the image, doing a calculation at each pixel location $x$, $y$.\n", "For reasons known to only a select few, we normalize the horizontal $x$ values to be from -2 to 1 and the vertical $y$ values to -1 to 1, and then call a function [`mandel`](https://en.wikipedia.org/wiki/Mandelbrot_set) with these normalized values. $x$, $y$ will become the real, imaginary parts of a complex number and we will do some math on that value. \n", "\n", "Also, our system architect (geez I don't like him) is adamant that every function return a status, so our calculation function must accept a pointer to the value to be returned. This makes more sense in C, but can be done in python as well, although awkwardly.\n", "\n", "Since each framework has a different idea on what it means to create this pointer, we pass it in to our outer function (the one that will loop over all the pixels and calculate the actual pixel value. Now we can re-use the function in each of the frameworks more easily. At least the system architect agreed to that!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The looping function\n", "\n", "These links are placeholders for later use \n", "[as used in ctypes](#Ctypes-use) \n", "[as used in CFFI](#CFFI-use) \n", "[as used in Cython](#Cython-use) \n", "[as used in Cppyy](#Cppyy-use) \n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "nbpresent": { "id": "ab8a8171-ccc1-40d7-88fb-e05d0ae3dd40" }, "scrolled": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def create_fractal(image, iters, func, oneval):\n", " ''' Call a function for each pixel in the image, where\n", " -2 < real < 1 over the columns and\n", " -1 < imag < 1 over the rows\n", " '''\n", " pixel_size_x = 3.0 / image.width\n", " pixel_size_y = 2.0 / image.height\n", " for y in range(image.height):\n", " imag = y * pixel_size_y - 1\n", " yy = y * image.width\n", " for x in range(image.width):\n", " real = x * pixel_size_x - 2\n", " \n", " ret = func(real, imag, iters, oneval) # <---- HERE is the real work\n", " if ret < 0:\n", " return ret\n", " image.data[yy + x] = oneval[0]\n", " return 0" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "nbpresent": { "id": "c687a4f1-bace-4d72-8fdf-d08ae4c34a19" }, "scrolled": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# This is the calculating function in python\n", "\n", "def mandel(x, y, max_iters, value):\n", " \"\"\"\n", " Given the real and imaginary parts of a complex number,\n", " determine if it is a candidate for membership in the Mandelbrot\n", " set given a fixed number of iterations.\n", " \"\"\"\n", " i = 0\n", " c = complex(x,y)\n", " z = 0.0j\n", " for i in range(max_iters):\n", " z = z*z + c\n", " if (z.real*z.real + z.imag*z.imag) >= 4:\n", " value[0] = i\n", " return 0\n", " value[0] = max_iters\n", " return max_iters" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "nbpresent": { "id": "e5336db1-ebd6-4ddf-93fe-1de2f67af19a" }, "scrolled": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pure python required 5.36 secs\n" ] } ], "source": [ "# OK, lets try it out. Here is our pure python fractal generator\n", "\n", "oneval = bytearray(1) # this is a kind of pointer in pure python\n", "s = timer()\n", "ret = create_fractal(image, 20, mandel, oneval)\n", "e = timer()\n", "if ret < 0:\n", " print('bad ret value from creat_fractal')\n", "pure_python = e - s\n", "print('pure python required {:.2f} secs'.format(pure_python))\n", "im = Image.frombuffer(\"L\", (width, height), image.data, \"raw\", \"L\", 0, 1)\n", "im.save('python_numpy.png')" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "nbpresent": { "id": "2477301c-828f-4c7a-9276-1536e4518ab6" }, "scrolled": false, "slideshow": { "slide_type": "slide" } }, "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 = $('
');\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", " '
');\n", " var titletext = $(\n", " '
');\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 = $('
');\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 = $('');\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 = $('');\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 = $('
')\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 = $('');\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 = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var 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= 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": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from matplotlib.pylab import imshow, show, figure, subplots\n", "fig, ax = subplots(1,2)\n", "with open('c.raw', 'rb') as fid:\n", " img = Image.frombytes(data=fid.read(), size=(1500,1000), mode=\"L\")\n", "ax[0].imshow(np.asarray(img, dtype='uint8'))\n", "ax[0].set_title('Pure C, {:d} millisecs'.format(pure_c))\n", "\n", "img = Image.open('python_numpy.png')\n", "ax[1].imshow(np.asarray(img, dtype='uint8'))\n", "ax[1].set_title('Pure Python, {:.0f} millisecs'.format(1000*pure_python));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cool. We now have a version in pure C that runs in under 200 ms. \n", "\n", "But hang on, we wanted this to be part of a whole pipeline, where we can use and reuse the functions ``mandel`` and ``create_fractal``. \n", "\n", "Note that we compiled ``libcreate_fractal.so`` which wraps our fast C function as a shared object, so maybe we can call it from Python?\n", "\n", "We have heard of four methods to interface C with Python: ctypes, CFFI, Cython, Cppyy. Let's try them out." ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "# We will need this to tell the various systems where to find things\n", "if sys.maxsize < 2 ** 33:\n", " arch = '32'\n", "else:\n", " arch = '64'\n", "if sys.platform == 'win32':\n", " dllname = './create_fractal%s.dll' % arch\n", " libname = 'create_fractal%s' % arch\n", "else:\n", " dllname = './libcreate_fractal.so'\n", " libname = 'create_fractal'" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": false }, "outputs": [], "source": [ "#ctypes\n", "# First all the declarations. Each function and struct must be redefined ...\n", "import ctypes\n", "\n", "class CtypesImg(ctypes.Structure):\n", " _fields_ = [('width', ctypes.c_int),\n", " ('height', ctypes.c_int),\n", " ('data', ctypes.POINTER(ctypes.c_uint8)), # HUH?\n", " ]\n", " array_cache = {}\n", " def __init__(self, width, height):\n", " self.width = width\n", " self.height = height\n", " # Create a class type to hold the data.\n", " # Since this creates a type, cache it for reuse rather\n", " # than create a new one each time\n", " if width*height not in self.array_cache:\n", " self.array_cache[width*height] = ctypes.c_uint8 * (width * height)\n", " # Note this keeps the img.data alive in the interpreter\n", " self.data = self.array_cache[width*height]() # !!!!!!\n", "\n", " def asmemoryview(self):\n", " # There must be a better way, but this code will not\n", " # be timed, so explicit trumps implicit\n", " ret = self.array_cache[self.width*self.height]()\n", " for i in range(self.width * self.height):\n", " ret[i] = self.data[i]\n", " return memoryview(ret)\n", "\n", "ctypesimg = CtypesImg(width, height)\n", "\n", " \n", "# Load the shared object (DLL in Windows)\n", "cdll = ctypes.cdll.LoadLibrary(dllname)\n", "\n", "#Fish the function pointers from the shared object and define the interfaces\n", "create_fractal_ctypes = cdll.create_fractal\n", "create_fractal_ctypes.argtypes = [CtypesImg, ctypes.c_int]\n", "\n", "mandel_ctypes = cdll.mandel\n", "mandel_ctypes.argtypes = [ctypes.c_float, ctypes.c_float, ctypes.c_int, \n", " ctypes.POINTER(ctypes.c_uint8)]" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "this function takes at least 2 arguments (0 given)\n" ] } ], "source": [ "# We have \"typed\" the function, it knows what to expect, so this should error\n", "try:\n", " create_fractal_ctypes()\n", "except TypeError as e:\n", " print(str(e))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ctypes use\n", "\n", "Let's run this, twice. Once to call the C implementation of create_fractal, and again with\n", "the python implementation of [create_fractal](#The-looping-function) which calls the c-mandel function \n", "1.5 million times" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ctypes calling create_fractal required 144.21 millisecs\n", "ctypes calling mandel required 2198.44 millisecs\n" ] } ], "source": [ "s = timer()\n", "create_fractal_ctypes(ctypesimg, 20)\n", "e = timer()\n", "ctypes_onecall = e - s\n", "print('ctypes calling create_fractal required {:.2f} millisecs'.format(1000*ctypes_onecall))\n", "im = Image.frombuffer(\"L\", (width, height), ctypesimg.asmemoryview(), 'raw', 'L', 0, 1)\n", "im.save('ctypes_fractal.png')\n", "\n", "value = (ctypes.c_uint8*1)()\n", "s = timer()\n", "create_fractal(ctypesimg, 20, mandel_ctypes, value)\n", "e = timer()\n", "ctypes_mandel = e - s\n", "print('ctypes calling mandel required {:.2f} millisecs'.format(1000*ctypes_mandel))\n", "im = Image.frombuffer(\"L\", (width, height), ctypesimg.asmemoryview(), 'raw', 'L', 0, 1)\n", "im.save('ctypes_mandel.png')" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "scrolled": false }, "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 = $('
');\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", " '
');\n", " var titletext = $(\n", " '
');\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 = $('
');\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 = $('');\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 = $('');\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 = $('
')\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 = $('');\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 = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var 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= 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": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, ax = subplots(1,2)\n", "ctypes1 = Image.open('cffi_fractal.png')\n", "ctypes2 = Image.open('cffi_mandel.png')\n", "ax[0].imshow(np.asarray(ctypes1, dtype='uint8'))\n", "ax[0].set_title('cffi one call {:.2f} millisecs'.format(1000*cffi_onecall))\n", "\n", "ax[1].imshow(np.asarray(ctypes2, dtype='uint8'))\n", "ax[1].set_title('cffi 1.5e6 calls {:.2f} millisecs'.format(1000*cffi1_mandel));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That was ABI mode (ctypes). Can we do better?\n", "Cffi has four [modes](https://cffi.readthedocs.io/en/latest/overview.html#other-cffi-modes), based on whether or not the code is compiled, and whether the resulting module can be imported or `dlopen`ed" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "generating .\\cffiAPI.c\n", "(already up-to-date)\n", "the current directory is 'c:\\\\pypy_stuff\\\\c_from_python'\n", "running build_ext\n", "building 'cffiAPI' extension\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\bin\\HostX86\\x86\\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -Ic:\\pypy_stuff\\Anaconda3_v5.2\\include -Ic:\\pypy_stuff\\Anaconda3_v5.2\\include \"-IC:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\include\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\ucrt\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\shared\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\um\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\winrt\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\cppwinrt\" \"-IC:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\include\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\ucrt\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\shared\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\um\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\winrt\" \"-IC:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\cppwinrt\" /TccffiAPI.c /Fo.\\Release\\cffiAPI.obj\n", "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\bin\\HostX86\\x86\\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:c:\\pypy_stuff\\Anaconda3_v5.2\\libs /LIBPATH:c:\\pypy_stuff\\Anaconda3_v5.2\\PCbuild\\win32 \"/LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\lib\\x86\" \"/LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\ucrt\\x86\" \"/LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\um\\x86\" \"/LIBPATH:C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Tools\\MSVC\\14.14.26428\\lib\\x64\" \"/LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\ucrt\\x64\" \"/LIBPATH:C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\um\\x64\" /EXPORT:PyInit_cffiAPI .\\Release\\cffiAPI.obj /OUT:.\\cffiAPI.cp36-win32.pyd /IMPLIB:.\\Release\\cffiAPI.cp36-win32.lib\n" ] }, { "data": { "text/plain": [ "'c:\\\\pypy_stuff\\\\c_from_python\\\\cffiAPI.cp36-win32.pyd'" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#cffi - API mode (like cython)\n", "import cffi\n", "ffiAPI = cffi.FFI()\n", "# Two stages, cdef reads the headers, then compile builds the module\n", "\n", "with open('mandel.c', 'rt') as fid:\n", " code = fid.read()\n", "with open('create_fractal.c', 'rt') as fid:\n", " code += fid.read()\n", " \n", "ffiAPI.cdef('\\n'.join(cleaned))\n", "ffiAPI.set_source('cffiAPI', code)\n", "ffiAPI.compile(verbose=True)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cffi calling create_fractal required 38.27 millisecs\n", "cffi calling mandel required 718.42 millisecs\n" ] } ], "source": [ "import cffiAPI\n", "\n", "img = cffiAPI.ffi.new('Img[1]')\n", "img[0].width = width\n", "img[0].height = height\n", "#img[0].data = ffi.new('unsigned char[%d]' % (width*height,)) # NO NO NO NO\n", "# This is C - we must keep the pointer alive !!!\n", "data1 = cffiAPI.ffi.new('unsigned char[%d]' % (width*height,))\n", "img[0].data = data1\n", "\n", "s = timer()\n", "cffiAPI.lib.create_fractal(img[0], 20)\n", "e = timer()\n", "cffiAPI_onecall = e - s\n", "print('cffi calling create_fractal required {:.2f} millisecs'.format(1000 * cffiAPI_onecall))\n", "m = Image.frombuffer('L', (width, height), ffi.buffer(data1), 'raw', 'L', 0, 1)\n", "im.save('cffiAPI_fractal.png')\n", "\n", "data2 = cffiAPI.ffi.new('unsigned char[%d]' % (width*height,))\n", "img[0].data = data2\n", "value = cffiAPI.ffi.new('unsigned char[1]')\n", "\n", "s = timer()\n", "create_fractal(img[0], 20, cffiAPI.lib.mandel, value)\n", "e = timer()\n", "cffiAPI_mandel = e - s\n", "print('cffi calling mandel required {:.2f} millisecs'.format(1000*cffiAPI_mandel))\n", "im = Image.frombuffer('L', (width, height), ffi.buffer(data2), 'raw', 'L', 0, 1)\n", "im.save('cffi_mandel.png')" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "%load_ext Cython" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hang on, isn't Cython used for compiling python to C? \n", "\n", "Well, yes, but, in this case we already have C code from our contractor...\n", "So we can use Cython to call our already compiled C quickly. This allows us to compare apples to apples. We will also see how to use python in cyton later on" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", " \n", " Cython: _cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.pyx\n", " \n", "\n", "\n", "

Generated by Cython 0.28.2

\n", "

\n", " Yellow lines hint at Python interaction.
\n", " Click on a line that starts with a \"+\" to see the C code that Cython generated for it.\n", "

\n", "
 01: 
\n", "
+02: cdef extern from 'create_fractal.h':
\n", "
  __pyx_t_1 = __Pyx_PyDict_NewPresized(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_1) < 0) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "
 03:     ctypedef struct Img:
\n", "
 04:         int width
\n", "
 05:         int height
\n", "
 06:         unsigned char * data
\n", "
 07: 
\n", "
 08:     int create_fractal(Img img, int iters);
\n", "
 09:     int mandel(float real, float imag, int max_iters,
\n", "
 10:                unsigned char * val);
\n", "
 11: 
\n", "
+12: def cython_create_fractal(pyimg, iters):
\n", "
/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_1cython_create_fractal(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/\n",
       "static PyMethodDef __pyx_mdef_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_1cython_create_fractal = {\"cython_create_fractal\", (PyCFunction)__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_1cython_create_fractal, METH_VARARGS|METH_KEYWORDS, 0};\n",
       "static PyObject *__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_1cython_create_fractal(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {\n",
       "  PyObject *__pyx_v_pyimg = 0;\n",
       "  PyObject *__pyx_v_iters = 0;\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cython_create_fractal (wrapper)\", 0);\n",
       "  {\n",
       "    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_pyimg,&__pyx_n_s_iters,0};\n",
       "    PyObject* values[2] = {0,0};\n",
       "    if (unlikely(__pyx_kwds)) {\n",
       "      Py_ssize_t kw_args;\n",
       "      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);\n",
       "      switch (pos_args) {\n",
       "        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  0: break;\n",
       "        default: goto __pyx_L5_argtuple_error;\n",
       "      }\n",
       "      kw_args = PyDict_Size(__pyx_kwds);\n",
       "      switch (pos_args) {\n",
       "        case  0:\n",
       "        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_pyimg)) != 0)) kw_args--;\n",
       "        else goto __pyx_L5_argtuple_error;\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  1:\n",
       "        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_iters)) != 0)) kw_args--;\n",
       "        else {\n",
       "          __Pyx_RaiseArgtupleInvalid(\"cython_create_fractal\", 1, 2, 2, 1); __PYX_ERR(0, 12, __pyx_L3_error)\n",
       "        }\n",
       "      }\n",
       "      if (unlikely(kw_args > 0)) {\n",
       "        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, \"cython_create_fractal\") < 0)) __PYX_ERR(0, 12, __pyx_L3_error)\n",
       "      }\n",
       "    } else if (PyTuple_GET_SIZE(__pyx_args) != 2) {\n",
       "      goto __pyx_L5_argtuple_error;\n",
       "    } else {\n",
       "      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "    }\n",
       "    __pyx_v_pyimg = values[0];\n",
       "    __pyx_v_iters = values[1];\n",
       "  }\n",
       "  goto __pyx_L4_argument_unpacking_done;\n",
       "  __pyx_L5_argtuple_error:;\n",
       "  __Pyx_RaiseArgtupleInvalid(\"cython_create_fractal\", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 12, __pyx_L3_error)\n",
       "  __pyx_L3_error:;\n",
       "  __Pyx_AddTraceback(\"_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.cython_create_fractal\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return NULL;\n",
       "  __pyx_L4_argument_unpacking_done:;\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_cython_create_fractal(__pyx_self, __pyx_v_pyimg, __pyx_v_iters);\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_cython_create_fractal(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pyimg, PyObject *__pyx_v_iters) {\n",
       "  Img __pyx_v_cimg;\n",
       "  int __pyx_v_citers;\n",
       "  __Pyx_memviewslice __pyx_v_tmp = { 0, 0, { 0 }, { 0 }, { 0 } };\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cython_create_fractal\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_1);\n",
       "  __PYX_XDEC_MEMVIEW(&__pyx_t_2, 1);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.cython_create_fractal\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __PYX_XDEC_MEMVIEW(&__pyx_v_tmp, 1);\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "/* … */\n",
       "  __pyx_tuple__22 = PyTuple_Pack(5, __pyx_n_s_pyimg, __pyx_n_s_iters, __pyx_n_s_cimg, __pyx_n_s_citers, __pyx_n_s_tmp); if (unlikely(!__pyx_tuple__22)) __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_tuple__22);\n",
       "  __Pyx_GIVEREF(__pyx_tuple__22);\n",
       "/* … */\n",
       "  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_1cython_create_fractal, NULL, __pyx_n_s_cython_magic_95bdf0f67f7d980128); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cython_create_fractal, __pyx_t_1) < 0) __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_codeobj__23 = (PyObject*)__Pyx_PyCode_New(2, 0, 5, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__22, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_C_Users_Matti_ipython_cython__cy, __pyx_n_s_cython_create_fractal, 12, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__23)) __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "
 13:     cdef Img cimg
\n", "
 14:     cdef int citers
\n", "
+15:     cdef unsigned char[::1] tmp = pyimg.data
\n", "
  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pyimg, __pyx_n_s_data); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 15, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_t_2 = __Pyx_PyObject_to_MemoryviewSlice_dc_unsigned_char(__pyx_t_1, PyBUF_WRITABLE); if (unlikely(!__pyx_t_2.memview)) __PYX_ERR(0, 15, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_v_tmp = __pyx_t_2;\n",
       "  __pyx_t_2.memview = NULL;\n",
       "  __pyx_t_2.data = NULL;\n",
       "
 16: 
\n", "
+17:     citers = iters
\n", "
  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_iters); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 17, __pyx_L1_error)\n",
       "  __pyx_v_citers = __pyx_t_3;\n",
       "
+18:     cimg.width = pyimg.width
\n", "
  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pyimg, __pyx_n_s_width); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 18, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 18, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_v_cimg.width = __pyx_t_3;\n",
       "
+19:     cimg.height = pyimg.height
\n", "
  __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pyimg, __pyx_n_s_height); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 19, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 19, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_v_cimg.height = __pyx_t_3;\n",
       "
+20:     cimg.data = &tmp[0]
\n", "
  __pyx_t_4 = 0;\n",
       "  __pyx_t_3 = -1;\n",
       "  if (__pyx_t_4 < 0) {\n",
       "    __pyx_t_4 += __pyx_v_tmp.shape[0];\n",
       "    if (unlikely(__pyx_t_4 < 0)) __pyx_t_3 = 0;\n",
       "  } else if (unlikely(__pyx_t_4 >= __pyx_v_tmp.shape[0])) __pyx_t_3 = 0;\n",
       "  if (unlikely(__pyx_t_3 != -1)) {\n",
       "    __Pyx_RaiseBufferIndexError(__pyx_t_3);\n",
       "    __PYX_ERR(0, 20, __pyx_L1_error)\n",
       "  }\n",
       "  __pyx_v_cimg.data = (&(*((unsigned char *) ( /* dim=0 */ ((char *) (((unsigned char *) __pyx_v_tmp.data) + __pyx_t_4)) ))));\n",
       "
+21:     return create_fractal(cimg, citers)
\n", "
  __Pyx_XDECREF(__pyx_r);\n",
       "  __pyx_t_1 = __Pyx_PyInt_From_int(create_fractal(__pyx_v_cimg, __pyx_v_citers)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 21, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_r = __pyx_t_1;\n",
       "  __pyx_t_1 = 0;\n",
       "  goto __pyx_L0;\n",
       "
 22: 
\n", "
 23: 
\n", "
+24: cpdef int cython_mandel(float real, float imag, int max_iters,
\n", "
static PyObject *__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_3cython_mandel(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/\n",
       "static int __pyx_f_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_cython_mandel(float __pyx_v_real, float __pyx_v_imag, int __pyx_v_max_iters, __Pyx_memviewslice __pyx_v_val, CYTHON_UNUSED int __pyx_skip_dispatch) {\n",
       "  int __pyx_r;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cython_mandel\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_WriteUnraisable(\"_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.cython_mandel\", __pyx_clineno, __pyx_lineno, __pyx_filename, 1, 0);\n",
       "  __pyx_r = 0;\n",
       "  __pyx_L0:;\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_3cython_mandel(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/\n",
       "static PyObject *__pyx_pw_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_3cython_mandel(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {\n",
       "  float __pyx_v_real;\n",
       "  float __pyx_v_imag;\n",
       "  int __pyx_v_max_iters;\n",
       "  __Pyx_memviewslice __pyx_v_val = { 0, 0, { 0 }, { 0 }, { 0 } };\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cython_mandel (wrapper)\", 0);\n",
       "  {\n",
       "    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_real,&__pyx_n_s_imag,&__pyx_n_s_max_iters,&__pyx_n_s_val,0};\n",
       "    PyObject* values[4] = {0,0,0,0};\n",
       "    if (unlikely(__pyx_kwds)) {\n",
       "      Py_ssize_t kw_args;\n",
       "      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);\n",
       "      switch (pos_args) {\n",
       "        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  0: break;\n",
       "        default: goto __pyx_L5_argtuple_error;\n",
       "      }\n",
       "      kw_args = PyDict_Size(__pyx_kwds);\n",
       "      switch (pos_args) {\n",
       "        case  0:\n",
       "        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_real)) != 0)) kw_args--;\n",
       "        else goto __pyx_L5_argtuple_error;\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  1:\n",
       "        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_imag)) != 0)) kw_args--;\n",
       "        else {\n",
       "          __Pyx_RaiseArgtupleInvalid(\"cython_mandel\", 1, 4, 4, 1); __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "        }\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  2:\n",
       "        if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_max_iters)) != 0)) kw_args--;\n",
       "        else {\n",
       "          __Pyx_RaiseArgtupleInvalid(\"cython_mandel\", 1, 4, 4, 2); __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "        }\n",
       "        CYTHON_FALLTHROUGH;\n",
       "        case  3:\n",
       "        if (likely((values[3] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_val)) != 0)) kw_args--;\n",
       "        else {\n",
       "          __Pyx_RaiseArgtupleInvalid(\"cython_mandel\", 1, 4, 4, 3); __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "        }\n",
       "      }\n",
       "      if (unlikely(kw_args > 0)) {\n",
       "        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, \"cython_mandel\") < 0)) __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "      }\n",
       "    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {\n",
       "      goto __pyx_L5_argtuple_error;\n",
       "    } else {\n",
       "      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);\n",
       "      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);\n",
       "    }\n",
       "    __pyx_v_real = __pyx_PyFloat_AsFloat(values[0]); if (unlikely((__pyx_v_real == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "    __pyx_v_imag = __pyx_PyFloat_AsFloat(values[1]); if (unlikely((__pyx_v_imag == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "    __pyx_v_max_iters = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_max_iters == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "    __pyx_v_val = __Pyx_PyObject_to_MemoryviewSlice_dc_unsigned_char(values[3], PyBUF_WRITABLE); if (unlikely(!__pyx_v_val.memview)) __PYX_ERR(0, 25, __pyx_L3_error)\n",
       "  }\n",
       "  goto __pyx_L4_argument_unpacking_done;\n",
       "  __pyx_L5_argtuple_error:;\n",
       "  __Pyx_RaiseArgtupleInvalid(\"cython_mandel\", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 24, __pyx_L3_error)\n",
       "  __pyx_L3_error:;\n",
       "  __Pyx_AddTraceback(\"_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.cython_mandel\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return NULL;\n",
       "  __pyx_L4_argument_unpacking_done:;\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_2cython_mandel(__pyx_self, __pyx_v_real, __pyx_v_imag, __pyx_v_max_iters, __pyx_v_val);\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_2cython_mandel(CYTHON_UNUSED PyObject *__pyx_self, float __pyx_v_real, float __pyx_v_imag, int __pyx_v_max_iters, __Pyx_memviewslice __pyx_v_val) {\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cython_mandel\", 0);\n",
       "  __Pyx_XDECREF(__pyx_r);\n",
       "  if (unlikely(!__pyx_v_val.memview)) { __Pyx_RaiseUnboundLocalError(\"val\"); __PYX_ERR(0, 24, __pyx_L1_error) }\n",
       "  __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_f_46_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c_cython_mandel(__pyx_v_real, __pyx_v_imag, __pyx_v_max_iters, __pyx_v_val, 0)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 24, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_r = __pyx_t_1;\n",
       "  __pyx_t_1 = 0;\n",
       "  goto __pyx_L0;\n",
       "\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_1);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_95bdf0f67f7d980128cef3f0d2496e5c.cython_mandel\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __PYX_XDEC_MEMVIEW(&__pyx_v_val, 1);\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "
 25:                         unsigned char[::1] val):
\n", "
+26:     return mandel(real, imag, max_iters, &val[0])
\n", "
  __pyx_t_1 = 0;\n",
       "  __pyx_t_2 = -1;\n",
       "  if (__pyx_t_1 < 0) {\n",
       "    __pyx_t_1 += __pyx_v_val.shape[0];\n",
       "    if (unlikely(__pyx_t_1 < 0)) __pyx_t_2 = 0;\n",
       "  } else if (unlikely(__pyx_t_1 >= __pyx_v_val.shape[0])) __pyx_t_2 = 0;\n",
       "  if (unlikely(__pyx_t_2 != -1)) {\n",
       "    __Pyx_RaiseBufferIndexError(__pyx_t_2);\n",
       "    __PYX_ERR(0, 26, __pyx_L1_error)\n",
       "  }\n",
       "  __pyx_r = mandel(__pyx_v_real, __pyx_v_imag, __pyx_v_max_iters, (&(*((unsigned char *) ( /* dim=0 */ ((char *) (((unsigned char *) __pyx_v_val.data) + __pyx_t_1)) )))));\n",
       "  goto __pyx_L0;\n",
       "
" ], "text/plain": [ "" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%cython -a -I. -L. -l $libname --link-args=-Wl,-rpath=.\n", "\n", "cdef extern from 'create_fractal.h':\n", " ctypedef struct Img:\n", " int width\n", " int height\n", " unsigned char * data\n", " \n", " int create_fractal(Img img, int iters);\n", " int mandel(float real, float imag, int max_iters,\n", " unsigned char * val);\n", " \n", "def cython_create_fractal(pyimg, iters):\n", " cdef Img cimg\n", " cdef int citers\n", " cdef unsigned char[::1] tmp = pyimg.data\n", " \n", " citers = iters\n", " cimg.width = pyimg.width\n", " cimg.height = pyimg.height\n", " cimg.data = &tmp[0]\n", " return create_fractal(cimg, citers)\n", "\n", "\n", "cpdef int cython_mandel(float real, float imag, int max_iters,\n", " unsigned char[::1] val):\n", " return mandel(real, imag, max_iters, &val[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cython use\n", "\n", "Let's run this, twice. Once to call the C implementation of create_fractal, and again with\n", "the python implementation of [create_fractal](#The-looping-function) which calls the c-mandel function \n", "1.5 million times" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cython onecall required 137.41 millisecs\n", "cython many calls required 916.12 millisecs\n" ] } ], "source": [ "# use it, remember we have \"image\" from the pure python version \n", "\n", "s = timer()\n", "cython_create_fractal(image, 20)\n", "e = timer()\n", "cython_onecall = e - s\n", "print('cython onecall required {:.2f} millisecs'.format(1000*cython_onecall))\n", "im = Image.frombuffer(\"L\", (width, height), image.data, \"raw\", \"L\", 0, 1)\n", "im.save('cython_fractal.png')\n", "\n", "value = bytearray(1)\n", "s = timer()\n", "create_fractal(image, 20, cython_mandel, value)\n", "e = timer()\n", "cython1_mandel = e - s\n", "print('cython many calls required {:.2f} millisecs'.format(1000*cython1_mandel))\n", "im = Image.frombuffer(\"L\", (width, height), image.data, \"raw\", \"L\", 0, 1)\n", "im.save('cython_mandel.png')" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "scrolled": false }, "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 = $('
');\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", " '
');\n", " var titletext = $(\n", " '
');\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 = $('
');\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 = $('');\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 = $('');\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 = $('
')\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 = $('