{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modeling of the thickness of the sensor\n",
    "\n",
    "In this notebook we will re-use the experiment done at ID28 and previously calibrated and model in 3D the detector.\n",
    "\n",
    "This detector is a Pilatus 1M with a 450µm thick silicon sensor. Let's first have a look at the absorption coefficients of this sensor material: https://physics.nist.gov/PhysRefData/XrayMassCoef/ElemTab/z14.html\n",
    "\n",
    "First we retieve the results of the previous step, then calculate the absorption efficiency:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "%pylab nbagg"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "wavelength: 6.968e-11m,\t dist: 2.845e-01m,\t poni1: 8.865e-02m,\t poni2: 8.931e-02m,\t energy: 17.793keV\n"
     ]
    }
   ],
   "source": [
    "import numpy\n",
    "import fabio, pyFAI, pyFAI.units, pyFAI.detectors, pyFAI.azimuthalIntegrator\n",
    "import json\n",
    "with open(\"id28.json\") as f:\n",
    "    calib = json.load(f)\n",
    "\n",
    "thickness = 450e-6\n",
    "wavelength = calib[\"wavelength\"]\n",
    "dist = calib[\"param\"][calib['param_names'].index(\"dist\")]\n",
    "poni1 = calib[\"param\"][calib['param_names'].index(\"poni1\")]\n",
    "poni2 = calib[\"param\"][calib['param_names'].index(\"poni2\")]\n",
    "energy = pyFAI.units.hc/(wavelength*1e10)\n",
    "print(\"wavelength: %.3em,\\t dist: %.3em,\\t poni1: %.3em,\\t poni2: %.3em,\\t energy: %.3fkeV\" % \n",
    "      (wavelength, dist, poni1, poni2, energy))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Absorption coeficient at 17.8 keV"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "µ = 1537.024214 m^-1 hence absorption efficiency for 450µm: 49.9 %\n"
     ]
    }
   ],
   "source": [
    "# density from https://en.wikipedia.org/wiki/Silicon\n",
    "rho = 2.3290 # g/cm^3\n",
    "\n",
    "#Absorption from https://physics.nist.gov/PhysRefData/XrayMassCoef/ElemTab/z14.html\n",
    "# Nota: enegies are in MeV !\n",
    "Si_abs = \"\"\"\n",
    "   2.00000E-03  2.777E+03  2.669E+03 \n",
    "   3.00000E-03  9.784E+02  9.516E+02 \n",
    "   4.00000E-03  4.529E+02  4.427E+02 \n",
    "   5.00000E-03  2.450E+02  2.400E+02 \n",
    "   6.00000E-03  1.470E+02  1.439E+02 \n",
    "   8.00000E-03  6.468E+01  6.313E+01 \n",
    "   1.00000E-02  3.389E+01  3.289E+01 \n",
    "   1.50000E-02  1.034E+01  9.794E+00 \n",
    "   2.00000E-02  4.464E+00  4.076E+00 \n",
    "   3.00000E-02  1.436E+00  1.164E+00 \n",
    "   4.00000E-02  7.012E-01  4.782E-01 \n",
    "   5.00000E-02  4.385E-01  2.430E-01 \n",
    "   6.00000E-02  3.207E-01  1.434E-01 \n",
    "   8.00000E-02  2.228E-01  6.896E-02 \n",
    "   1.00000E-01  1.835E-01  4.513E-02 \n",
    "   1.50000E-01  1.448E-01  3.086E-02 \n",
    "   2.00000E-01  1.275E-01  2.905E-02 \n",
    "   3.00000E-01  1.082E-01  2.932E-02 \n",
    "   4.00000E-01  9.614E-02  2.968E-02 \n",
    "   5.00000E-01  8.748E-02  2.971E-02 \n",
    "   6.00000E-01  8.077E-02  2.951E-02 \n",
    "   8.00000E-01  7.082E-02  2.875E-02 \n",
    "   1.00000E+00  6.361E-02  2.778E-02 \n",
    "   1.25000E+00  5.688E-02  2.652E-02 \n",
    "   1.50000E+00  5.183E-02  2.535E-02 \n",
    "   2.00000E+00  4.480E-02  2.345E-02 \n",
    "   3.00000E+00  3.678E-02  2.101E-02 \n",
    "   4.00000E+00  3.240E-02  1.963E-02 \n",
    "   5.00000E+00  2.967E-02  1.878E-02 \n",
    "   6.00000E+00  2.788E-02  1.827E-02 \n",
    "   8.00000E+00  2.574E-02  1.773E-02 \n",
    "   1.00000E+01  2.462E-02  1.753E-02 \n",
    "   1.50000E+01  2.352E-02  1.746E-02 \n",
    "   2.00000E+01  2.338E-02  1.757E-02 \"\"\"\n",
    "data = numpy.array([[float(i) for i in line.split()] for line in Si_abs.split(\"\\n\") if line])\n",
    "energy_tab, mu_over_rho, mu_en_over_rho = data.T\n",
    "abs_18 = numpy.interp(energy, energy_tab*1e3, mu_en_over_rho) \n",
    "mu = abs_18*rho*1e+2\n",
    "eff = 1.0-numpy.exp(-mu*thickness)\n",
    "\n",
    "print(\"µ = %f m^-1 hence absorption efficiency for 450µm: %.1f %%\"%(mu, eff*100))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "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\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\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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6d5775UxRldddZX++c9/KjExUbfccouio6P11FNPKT8/v0y1FwaAnTt3qlGjRoqOjtbkyZOLbF/47/Xaa6/5rfd4POrYsaNcLpe++eYbv/eaNWsmY4wWL15c5HiF11KsXbu2dD88AJwjAgCAkPPxxx+rdu3afnePqVWrljp37qwZM2YU2f5cA8DUqVNljNFdd9111tomT54sY4y6detW5L38/HwrSJw+6BcOlP/5z3+K7PPOO+/IGKPRo0ef9bMlqWnTpnr00Uet1z/++KNcLpfat2+v3Nxca/2cOXNkjPH7+detWydjjDZv3lyqzzqd3QDQvXv3Ml38ezqfz6fk5GRVqFDBrxfat2+vVatWlfo4pweAb775RnXq1NHvfvc7LViwoMi2hw8fVoUKFUq8WLfwr/29e/f2W//f//632N/P119/LWOMmjZtWup6AeBcEQAAhKS8vDzNnTtXL774ou68807VqFHDGgD/+te/yufzWdueawBITEyUMUZvvfXWWet65plnShzmJemvf/2rjDGaPn26ta4wAPz2r8aSNH/+fBljir3OoDg1a9b0+8v0+PHjZYzRnDlzimzbrFkzv58/LS1NxhitWLGiVJ91OjsBIDMzU1WqVCnTxb+FPB6PunTpomrVqmn8+PHat2+fsrKyNGvWLMXFxSk6Otrvd1ya2tu3b6/q1avr0ksvLfFaiNmzZ8sYo4SEhGLvZvTcc88Ve/3BiRMnVL16dVWqVElHjx611vfo0UPGGI0bN65MPz8AnAsCAICwUFBQoI8//lhVq1aVMUbTpk2z3jvXAPC3v/1Nxphiv134rccee0zGGKWmphb7flJSkowxevfdd611p18E/FtlvUNNTEyMxo8fb70eMGCAjDFKS0srsu3999/v9/PPmzdPxhj9+OOPpfqs4uosSwAYM2ZMmS/+LZScnFzinYsK/wpf2js8FdZeeI1Hx44dSzwNqvAbnrMtbdu2LbJvz549ZYzRqFGjJJ0KBVWrVlVWVlbpf3gAOEcEAABhpfAi4aeeespa58Q3AP/973+Lfb/wG4DTA0ogA0DdunX9tn3rrbdK/AagRYsWfj9/z549demll5bqc0qqsywBoPDc9+LOiz+bG2644YynK9WsWVPGGB0+fPisxyqs/aWXXtLjjz8uY4xuu+02ZWdnF9m28OLjp59+usw1F96d6Nprr5V06rSg00/ZAoDyQAAAEFZee+01GWP05JNPWuvK8xqAwucUPPTQQ0Xey8/PV/369Uu8BiAQAeDOO+9UmzZtrNfff/+9XC6XOnTooLy8PGv9okWL/P5KPmvWLMXExBR7W83SKGsAWL16ta2LfwtdffXVMsZo4cKFRd7LycmR2+2WMUbHjh0767F++zv+97//bd196cSJE37bHjhwQFFRUbYf2FUYXNasWaMWLVrIGKPVq1fbOhYA2EUAABBSpkyZonnz5hW59aMk7du3T3FxcTLG6JNPPrHWB/IuQMXd5724uwBVqFChyIWow4YNkzFGHTp08FsfyAAwYcIEVahQwe8puH//+99ljNHVV1+tp59+Wg899JAqVqyo+Ph4XXDBBbrjjjtUoUIFPfTQQ37XTpRFWQPAo48+KmOMXn/99TNuV/hk5t8+P6HweRDt27cvcrpO3759rfP0y1L76b/jfv36yRijm266qcjpOX/5y1+s6zIKCgqKHO+nn34qcqvXQoXXZBTe3anw2wAAKE8EAAAhpfCptbVr19af//xn9e7dW71799af//xnVa5cWcYY3X333QG9CFj69TkAhaeVtGnTRklJSfrXv/6l2267TRUqVPDbdvr06YqOjlbFihX14IMPql+/frrtttusurdv3+63fSADgMfj0WWXXaa2bdtat8EsKChQcnKy6tatq4oVK6px48aaOHGiBg0apAsvvFC33XabX2AqrWnTpunhhx/Www8/rI4dO8oYowYNGljrnn322WL3y8rKUtWqVRUTE3PWi38L/z0efvhhv/V79uxR3bp1ZYxRvXr19Pjjj+vpp5+2brdZuXJlrVy5slQ/R0m/48LnCTRr1szvwt2srCzrr/fx8fF65JFH1LdvX/31r39VQkKCjDH68MMPi/2sY8eOqVq1ata1AqW9uxMABBIBAEBI2bVrl8aMGaPOnTvriiuuUPXq1RUdHa3atWurU6dOev/994t8OxCoJwGnp6erV69eqlevnqKjo1WrVi01a9ZMAwcOLLLt119/rc6dO+uiiy5SdHS0LrvsMj3++OP65ZdfimwbyAAgSampqYqKitJf/vIXv1t/BlrhhbglLSVdhDtu3LhSX/xbUgCQpIMHD+rZZ5/VlVdeqZiYGEVHR+vyyy9X9+7di73ouSRn+h0PHTrUuk3n6WElNzdXo0ePVsuW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AAAA8CMEAACA5s6dK2OMNm7ceFHHeeaZZ9S3b99LNKvqmTFjhtq2bauioiJb/n0A8BUEAACoBWca7DNb48aN1bp1a/Xr109Tp07ViRMnamUe7777rubOnXvO+V1MANi3b58aNmyolStXXsQMrXM6nbrmmms0depUW/59APAVBAAAqAVnGuxXX31V8+bN07///W9FRUWpX79+qlevnoKCgrR9+/Yan0doaKh69+59zvldTAB4/vnnFRISchGzu3jDhw9XUFCQKioqbJ0HAHgzAgAA1ILzNdhJSUkKCAhQUFCQTp8+XaPzqKkAUFJSoquuukqjR4++yBlenE2bNskYo6SkJFvnAQDejAAAALXgQg12VFSUjDGaNWuW23hGRoYeeOABtWzZUo0bN1a3bt303//+t9JjJycn66mnnlKrVq10+eWX69FHH9WxY8dc+wUFBbndhmSMcYWBM8dYu3atXnjhBV111VVq2rSp7r//fh06dOiC39/KlStljNHq1asrnVtWVpbb+KpVq2SM0apVq1xjvXv3VmhoqLZv367bbrtNAQEBat++vT7//HNJ0urVq9WjRw81adJEISEhSkxMrHQurVq10nPPPXfBOQOAvyIAAEAtuFAAyMnJkTFGf/rTn1xj6enpat68uTp16qSJEydq+vTpuu2221SvXj0tWrTI49idO3dWr169NG3aNA0dOlT169fXbbfd5rodZvHixfrlL3+pm266SfPmzdO8efOUkJDgdoxbbrlFffr0UXR0tF588UU1aNBADz744AW/v9dee0316tVTQUFBpd93VQPAtddeq7Zt2yoyMlLR0dHq1KmTGjRooE8++USBgYF65ZVXNGXKFLVp00bNmzev9NmJvn37qlu3bhecMwD4KwIAANSCqtxi07x5c91yyy2ur++44w517tzZ7a02FRUVCg8P1w033OBx7G7duqmkpMQ1/uabb8oY43bF4EK3APXt29ft/vkXXnhBDRo0UH5+/nm/v8GDB+vKK68853GrGgCMMZo/f75rbPfu3TLGqH79+tqwYYNrPD4+XsaYSh9ofuqppxQQEHDe+QKAPyMAAEAtqEoAaNOmjTp06CBJOnr0qOrVq6fx48fr8OHDbtu4ceNkjNH333/vduyYmBi34508eVIOh0NPP/20a+xCAeCzzz5zG1+0aJGMMRd8QHnAgAGuuVd23KoGgMsuu8zjAd4WLVooNDTUbSw/P1/GGI0ZM8bj3xwxYoSMMSosLDzvnAHAXxEAAKAWVPcKQGpqqsf9+mdvW7ZscTt2Za/fbNu2rfr37+/6+kIB4Od/ZZd+atTPvrf/bAMGDFD79u3PedyqBoCbbrrJ4xhBQUG66667PMaNMXr22Wc9xocPHy5jTI0/UA0AvooAAAC1oKrPAAwcOFCS9NVXX8kYo4iICCUmJla6nbn//VIGgLPnV1mjXpnBgwerVatW5zzu2QHgzEPDlT0EfLagoCDdfffdHuPGGA0dOtRj/Mknn1TTpk3PO18A8GcEAACoBVV9C9CcOXMkSXl5eTLG6KWXXqrysatyC9DNN99cIwHgzEPAZz8rcOa4aWlpbuMLFiyosQDAQ8AAcH4EAACoBVVZB+D666+X0+l0jd9+++1q1aqVDhw44PGZn7+a80IPAcfGxrrGevbsqa5du1Z5flUNAElJSZW+f//Mcc9enffBBx/0uGpxqQJAq1atNGzYsPPOFwD8GQEAAGrB2SsBz507V2+88YZrJeB27dppx44dbp/ZuXOnWrZsqSuvvFIjR47UrFmzNH78eP3+979Xly5dPI595jWg0dHRevbZZ1W/fn399re/dXuo9u9//7vr4eIFCxa4GvaLDQDFxcW68sorPa5YnDluq1atNHz4cM2cOVMPPvigWrRoIWOM/vKXv7j+zUsRAM4sBLZixYrzzhcA/BkBAABqwZlG+MzWqFEjBQYG6s4779TUqVMrfZ+9JO3du1ePPfaYAgMD1bBhQ7Vp00b33HOPvvjiC49jn1kIrGXLlrrsssv0yCOP6OjRo27HO3jwoO6++25dfvnllS4EZjUASNJzzz3n8SagM8edN2+eOnXqpEaNGqlHjx5KS0vT7bffrhYtWiguLk7SpQkAI0aM0HXXXefxJiEAwE8IAADg46ryhqHasHfvXjVs2NDtr+/negi4JhQVFSkwMFBTpkyp8X8LAHwZAQAAfJy3BABJGjJkiPr27ev6ujYDwIwZM9S2bVu3hdMAAJ4IAADg47wpAJytNgMAAKBqCAAA4OMIAACA6iAAAAAAAH6EAAAAAAD4EQIAAAAA4EcIAAAAAIAfIQAAAAAAfoQAAAAAAPgRAgAAAADgRwgAAAAAgB8hAAAAAAB+5P8Bz0gxxY3vQ04AAAAASUVORK5CYII=\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Silicon @ 17.8 keV')"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "depth = numpy.linspace(0, 1000, 100)\n",
    "res = numpy.exp(-mu*depth*1e-6)\n",
    "fig, ax = subplots()\n",
    "ax.plot(depth, res, \"-\")\n",
    "ax.set_xlabel(\"Depth (µm)\")\n",
    "ax.set_ylabel(\"Residual signal\")\n",
    "ax.set_title(\"Silicon @ 17.8 keV\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is consistent with:\n",
    "http://henke.lbl.gov/optical_constants/filter2.html\n",
    "\n",
    "Now we can model the detector\n",
    "\n",
    "## Modeling of the detector:\n",
    "\n",
    "The detector is seen as a 2D array of voxel. Let vox, voy and voz be the dimention of the detector in the three dimentions.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Detector Pilatus 1M\t PixelSize= 1.720e-04, 1.720e-04 m\n",
      "0.000172 0.000172 0.00045\n"
     ]
    }
   ],
   "source": [
    "detector= pyFAI.detector_factory(calib[\"detector\"])\n",
    "print(detector)\n",
    "\n",
    "vox = detector.pixel2 # this is not a typo\n",
    "voy = detector.pixel1 # x <--> axis 2\n",
    "voz = thickness\n",
    "\n",
    "print(vox, voy, voz)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The intensity grabbed in this voxel is the triple integral of the absorbed signal coming from this pixel or from the neighboring ones.\n",
    "\n",
    "There are 3 ways to perform this intergral:\n",
    "* Volumetric analytic integral. Looks feasible with a change of variable in the depth\n",
    "* Slice per slice, the remaining intensity depand on the incidence angle + pixel splitting between neighbooring pixels\n",
    "* raytracing: the decay can be solved analytically for each ray, one has to throw many ray to average out the signal.\n",
    "\n",
    "For sake of simplicity, this integral will be calculated numerically using this raytracing algorithm.\n",
    "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.3443&rep=rep1&type=pdf\n",
    "\n",
    "Knowing the input position for a X-ray on the detector and its propagation vector, this algorithm allows us to calculate  the length of the path in all voxel it crosses in a fairly efficient way.\n",
    "\n",
    "To speed up the calculation, we will use a few tricks:\n",
    "* One ray never crosses more than 16 pixels, which is reasonable considering the incidance angle \n",
    "* we use numba to speed-up the calculation of loops in python\n",
    "* We will allocate the needed memory by chuncks of 1 million elements\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from numba import jit \n",
    "\n",
    "BLOCK_SIZE = 1<<20 # 1 milion\n",
    "BUFFER_SIZE = 16 \n",
    "BIG = numpy.finfo(numpy.float32).max\n",
    "\n",
    "mask = numpy.load(\"mask.npy\").astype(numpy.int8)\n",
    "from scipy.sparse import csr_matrix, csc_matrix, linalg"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(array([0, 0, 1, 1], dtype=int32), array([0, 1, 1, 2], dtype=int32), array([0.00029791, 0.00029791, 0.00059583, 0.00059583], dtype=float32))\n",
      "The slowest run took 11.36 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
      "8.3 µs ± 7.26 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "9.64 µs ± 81.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n"
     ]
    }
   ],
   "source": [
    "@jit\n",
    "def calc_one_ray(entx, enty, \n",
    "                 kx, ky, kz,\n",
    "                 vox, voy, voz):\n",
    "    \"\"\"For a ray, entering at position (entx, enty), with a propagation vector (kx, ky,kz),\n",
    "    calculate the length spent in every voxel where energy is deposited from a bunch of photons comming in the detector \n",
    "    at a given position and and how much energy they deposit in each voxel. \n",
    "    \n",
    "    Direct implementation of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.3443&rep=rep1&type=pdf\n",
    "    \n",
    "    :param entx, enty: coordinate of the entry point in meter (2 components, x,y)\n",
    "    :param kx, ky, kz: vector with the direction of the photon (3 components, x,y,z)\n",
    "    :param vox, voy, voz: size of the voxel in meter (3 components, x,y,z)\n",
    "    :return: coordinates voxels in x, y and length crossed when leaving the associated voxel\n",
    "    \"\"\"\n",
    "    array_x = numpy.empty(BUFFER_SIZE, dtype=numpy.int32)\n",
    "    array_x[:] = -1\n",
    "    array_y = numpy.empty(BUFFER_SIZE, dtype=numpy.int32)\n",
    "    array_y[:] = -1\n",
    "    array_len = numpy.empty(BUFFER_SIZE, dtype=numpy.float32)\n",
    "    \n",
    "    #normalize the input propagation vector\n",
    "    n = numpy.sqrt(kx*kx + ky*ky + kz*kz)\n",
    "    kx /= n\n",
    "    ky /= n\n",
    "    kz /= n\n",
    "    \n",
    "    assert kz>0\n",
    "    step_X = -1 if kx<0.0 else 1\n",
    "    step_Y = -1 if ky<0.0 else 1\n",
    "    \n",
    "    assert vox>0\n",
    "    assert voy>0\n",
    "    assert voz>0\n",
    "        \n",
    "    X = int(entx//vox)\n",
    "    Y = int(enty//voy)\n",
    "    \n",
    "    if kx>0.0:\n",
    "        t_max_x = ((entx//vox+1)*(vox)-entx)/ kx\n",
    "    elif kx<0.0:\n",
    "        t_max_x = ((entx//vox)*(vox)-entx)/ kx\n",
    "    else:\n",
    "        t_max_x = BIG\n",
    "\n",
    "    if ky>0.0:\n",
    "        t_max_y = ((enty//voy+1)*(voy)-enty)/ ky\n",
    "    elif ky<0.0:\n",
    "        t_max_y = ((enty//voy)*(voy)-enty)/ ky\n",
    "    else:\n",
    "        t_max_y = BIG\n",
    "    \n",
    "    #Only one case for z as the ray is travelling in one direction only\n",
    "    t_max_z = voz / kz\n",
    "       \n",
    "    t_delta_x = abs(vox/kx) if kx!=0 else BIG\n",
    "    t_delta_y = abs(voy/ky) if ky!=0 else BIG\n",
    "    t_delta_z = voz/kz\n",
    "    \n",
    "    finished = False\n",
    "    last_id = 0\n",
    "    array_x[last_id] = X\n",
    "    array_y[last_id] = Y\n",
    "    \n",
    "    while not finished:\n",
    "        if t_max_x < t_max_y:\n",
    "            if t_max_x < t_max_z:\n",
    "                array_len[last_id] = t_max_x\n",
    "                last_id+=1\n",
    "                X += step_X\n",
    "                array_x[last_id] = X\n",
    "                array_y[last_id] = Y\n",
    "                t_max_x += t_delta_x\n",
    "            else:\n",
    "                array_len[last_id] = t_max_z\n",
    "                finished = True\n",
    "        else:\n",
    "            if t_max_y < t_max_z:\n",
    "                array_len[last_id] = t_max_y\n",
    "                last_id+=1\n",
    "                Y += step_Y\n",
    "                array_x[last_id] = X\n",
    "                array_y[last_id] = Y                \n",
    "                t_max_y += t_delta_y\n",
    "            else:\n",
    "                array_len[last_id] = t_max_z\n",
    "                finished = True\n",
    "        if last_id>=array_len.size-1:\n",
    "            print(\"resize arrays\")\n",
    "            old_size = len(array_len)\n",
    "            new_size = (old_size//BUFFER_SIZE+1)*BUFFER_SIZE\n",
    "            new_array_x = numpy.empty(new_size, dtype=numpy.int32)\n",
    "            new_array_x[:] = -1\n",
    "            new_array_y = numpy.empty(new_size, dtype=numpy.int32)\n",
    "            new_array_y[:] = -1\n",
    "            new_array_len = numpy.empty(new_size, dtype=numpy.float32)\n",
    "            new_array_x[:old_size] = array_x\n",
    "            new_array_y[:old_size] = array_y\n",
    "            new_array_len[:old_size] = array_len\n",
    "            array_x = new_array_x\n",
    "            array_y = new_array_y\n",
    "            array_len = new_array_len\n",
    "    return array_x[:last_id], array_y[:last_id], array_len[:last_id]\n",
    "\n",
    "print(calc_one_ray(0.0,0.0, 1,1,1, 172e-6, 172e-6, 450e-6))\n",
    "import random\n",
    "%timeit calc_one_ray(10+random.random(),11+random.random(),\\\n",
    "                     random.random()-0.5,random.random()-0.5,0.5+random.random(), \\\n",
    "                     vox, voy, voz)\n",
    "%timeit calc_one_ray.py_func(10+random.random(),11+random.random(),\\\n",
    "                     random.random()-0.5,random.random()-0.5,0.5+random.random(), \\\n",
    "                     vox, voy, voz)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we are able to perform raytracing for any ray comming in the detector, we can calculate the contribution to the neighboring pixels, using the absorption law (the length travelled is already known). \n",
    "To average-out the signal, we will sample a few dozens of rays per pixel to get an approximatation of the volumic integrale. \n",
    "\n",
    "Now we need to store the results so that this transformation can be represented as a sparse matrix multiplication:\n",
    "\n",
    "b = M.a\n",
    "\n",
    "Where b is the recorded image (blurred) and a is the \"perfect\" signal. \n",
    "M being the sparse matrix where every pixel of a gives a limited number of contribution to b.\n",
    "\n",
    "Each pixel in *b* is represented by one line in *M* and we store the indices of *a* of interest with the coefficients of the matrix.\n",
    "So if a pixel i,j contributes to (i,j), (i+1,j), (i+1,j+1), there are only 3 elements in the line. \n",
    "This is advantagous for storage.\n",
    "\n",
    "We will use the CSR sparse matrix representation:\n",
    "https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_.28CSR.2C_CRS_or_Yale_format.29\n",
    "where there are 3 arrays:\n",
    "* data: containing the actual non zero values\n",
    "* indices: for a given line, it contains the column number of the assocated data (at the same indice)\n",
    "* idptr: this array contains the index of the start of every line.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from numba import jitclass, int8, int32, int64, float32, float64\n",
    "spec = [(\"vox\",float64),(\"voy\",float64),(\"voz\",float64),(\"mu\",float64),\n",
    "        (\"dist\",float64),(\"poni1\",float64),(\"poni2\",float64),\n",
    "        (\"width\", int64),(\"height\", int64),(\"mask\", int8[:,:]),\n",
    "        (\"sampled\", int64), (\"data\", float32[:]),(\"indices\", int32[:]),(\"idptr\", int32[:]),\n",
    "       ]\n",
    "@jitclass(spec)\n",
    "class ThickDetector(object):\n",
    "    \"Calculate the point spread function as function of the geometry of the experiment\"\n",
    "    \n",
    "    def __init__(self, vox, voy, thickness, mask, mu, \n",
    "                 dist, poni1, poni2):\n",
    "        \"\"\"Constructor of the class:\n",
    "        \n",
    "        :param vox, voy: detector pixel size in the plane\n",
    "        :param thickness: thickness of the sensor in meters\n",
    "        :param mask: \n",
    "        :param mu: absorption coefficient of the sensor material\n",
    "        :param dist: sample detector distance as defined in the geometry-file\n",
    "        :param poni1, poni2: coordinates of the PONI as defined in the geometry \n",
    "        \"\"\"\n",
    "        self.vox = vox\n",
    "        self.voy = voy\n",
    "        self.voz = thickness\n",
    "        self.mu = mu\n",
    "        self.dist=dist\n",
    "        self.poni1 = poni1\n",
    "        self.poni2 = poni2\n",
    "        self.width = mask.shape[-1]\n",
    "        self.height = mask.shape[0]\n",
    "        self.mask = mask\n",
    "        self.sampled = 0\n",
    "        self.data = numpy.zeros(BLOCK_SIZE, dtype=numpy.float32)\n",
    "        self.indices = numpy.zeros(BLOCK_SIZE,dtype=numpy.int32)\n",
    "        self.idptr = numpy.zeros(self.width*self.height+1, dtype=numpy.int32)\n",
    "        \n",
    "    def calc_one_ray(self, entx, enty):\n",
    "        \"\"\"For a ray, entering at position (entx, enty), with a propagation vector (kx, ky,kz),\n",
    "        calculate the length spent in every voxel where energy is deposited from a bunch of photons comming in the detector \n",
    "        at a given position and and how much energy they deposit in each voxel. \n",
    "\n",
    "        Direct implementation of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.3443&rep=rep1&type=pdf\n",
    "\n",
    "        :param entx, enty: coordinate of the entry point in meter (2 components, x,y)\n",
    "        :return: coordinates voxels in x, y and length crossed when leaving the associated voxel\n",
    "        \"\"\"\n",
    "        array_x = numpy.empty(BUFFER_SIZE, dtype=numpy.int32)\n",
    "        array_x[:] = -1\n",
    "        array_y = numpy.empty(BUFFER_SIZE, dtype=numpy.int32)\n",
    "        array_y[:] = -1\n",
    "        array_len = numpy.empty(BUFFER_SIZE, dtype=numpy.float32)\n",
    "\n",
    "        #normalize the input propagation vector\n",
    "        kx = entx - self.poni2\n",
    "        ky = enty - self.poni1\n",
    "        kz = self.dist\n",
    "        n = numpy.sqrt(kx*kx + ky*ky + kz*kz)\n",
    "        kx /= n\n",
    "        ky /= n\n",
    "        kz /= n\n",
    "\n",
    "        step_X = -1 if kx<0.0 else 1\n",
    "        step_Y = -1 if ky<0.0 else 1\n",
    "\n",
    "        X = int(entx/self.vox)\n",
    "        Y = int(enty/self.voy)\n",
    "\n",
    "        if kx>0.0:\n",
    "            t_max_x = ((entx//self.vox+1)*(self.vox)-entx)/ kx\n",
    "        elif kx<0.0:\n",
    "            t_max_x = ((entx//self.vox)*(self.vox)-entx)/ kx\n",
    "        else:\n",
    "            t_max_x = BIG\n",
    "\n",
    "        if ky>0.0:\n",
    "            t_max_y = ((enty//self.voy+1)*(self.voy)-enty)/ ky\n",
    "        elif ky<0.0:\n",
    "            t_max_y = ((enty//self.voy)*(self.voy)-enty)/ ky\n",
    "        else:\n",
    "            t_max_y = BIG\n",
    "\n",
    "        #Only one case for z as the ray is travelling in one direction only\n",
    "        t_max_z = self.voz / kz\n",
    "\n",
    "        t_delta_x = abs(self.vox/kx) if kx!=0 else BIG\n",
    "        t_delta_y = abs(self.voy/ky) if ky!=0 else BIG\n",
    "        t_delta_z = self.voz/kz\n",
    "\n",
    "        finished = False\n",
    "        last_id = 0\n",
    "        array_x[last_id] = X\n",
    "        array_y[last_id] = Y\n",
    "\n",
    "        while not finished:\n",
    "            if t_max_x < t_max_y:\n",
    "                if t_max_x < t_max_z:\n",
    "                    array_len[last_id] = t_max_x\n",
    "                    last_id+=1\n",
    "                    X += step_X\n",
    "                    array_x[last_id] = X\n",
    "                    array_y[last_id] = Y\n",
    "                    t_max_x += t_delta_x\n",
    "                else:\n",
    "                    array_len[last_id] = t_max_z\n",
    "                    last_id+=1\n",
    "                    finished = True\n",
    "            else:\n",
    "                if t_max_y < t_max_z:\n",
    "                    array_len[last_id] = t_max_y\n",
    "                    last_id+=1\n",
    "                    Y += step_Y\n",
    "                    array_x[last_id] = X\n",
    "                    array_y[last_id] = Y                \n",
    "                    t_max_y += t_delta_y\n",
    "                else:\n",
    "                    array_len[last_id] = t_max_z\n",
    "                    last_id+=1\n",
    "                    finished = True\n",
    "            if last_id>=array_len.size-1:\n",
    "                print(\"resize arrays\")\n",
    "                old_size = len(array_len)\n",
    "                new_size = (old_size//BUFFER_SIZE+1)*BUFFER_SIZE\n",
    "                new_array_x = numpy.empty(new_size, dtype=numpy.int32)\n",
    "                new_array_x[:] = -1\n",
    "                new_array_y = numpy.empty(new_size, dtype=numpy.int32)\n",
    "                new_array_y[:] = -1\n",
    "                new_array_len = numpy.empty(new_size, dtype=numpy.float32)\n",
    "                new_array_x[:old_size] = array_x\n",
    "                new_array_y[:old_size] = array_y\n",
    "                new_array_len[:old_size] = array_len\n",
    "                array_x = new_array_x\n",
    "                array_y = new_array_y\n",
    "                array_len = new_array_len\n",
    "        return array_x[:last_id], array_y[:last_id], array_len[:last_id]\n",
    "\n",
    "    def one_pixel(self, row, col, sample):\n",
    "        \"\"\"calculate the contribution of one pixel to the sparse matrix and populate it.\n",
    "\n",
    "        :param row: row index of the pixel of interest\n",
    "        :param col: column index of the pixel of interest\n",
    "        :param sample: Oversampling rate, 10 will thow 10x10 ray per pixel\n",
    "\n",
    "        :return: the extra number of pixel allocated\n",
    "        \"\"\"\n",
    "        if self.mask[row, col]:\n",
    "            return (numpy.empty(0, dtype=numpy.int32),\n",
    "                    numpy.empty(0, dtype=numpy.float32))\n",
    "\n",
    "        counter = 0\n",
    "        tmp_size = 0\n",
    "        last_buffer_size = BUFFER_SIZE\n",
    "        tmp_idx = numpy.empty(last_buffer_size, dtype=numpy.int32)\n",
    "        tmp_idx[:] = -1\n",
    "        tmp_coef = numpy.zeros(last_buffer_size, dtype=numpy.float32)\n",
    "\n",
    "        pos = row * self.width + col\n",
    "        start = self.idptr[pos]\n",
    "        for i in range(sample):\n",
    "            posx = (col+1.0*i/sample)*vox\n",
    "            for j in range(sample):\n",
    "                posy = (row+1.0*j/sample)*voy\n",
    "                array_x, array_y, array_len = self.calc_one_ray(posx, posy)\n",
    "\n",
    "                rem = 1.0\n",
    "                for i in range(array_x.size):\n",
    "                    x = array_x[i]\n",
    "                    y = array_y[i]\n",
    "                    l = array_len[i]\n",
    "                    if (x<0) or (y<0) or (y>=self.height) or (x>=self.width):\n",
    "                        break\n",
    "                    elif (self.mask[y, x]):\n",
    "                        continue\n",
    "                    idx = x + y*self.width\n",
    "                    dos = numpy.exp(-self.mu*l)\n",
    "                    value = rem - dos\n",
    "                    rem = dos\n",
    "                    for j in range(last_buffer_size):\n",
    "                        if tmp_size >= last_buffer_size:\n",
    "                            #Increase buffer size\n",
    "                            new_buffer_size = last_buffer_size + BUFFER_SIZE\n",
    "                            new_idx = numpy.empty(new_buffer_size, dtype=numpy.int32)\n",
    "                            new_coef = numpy.zeros(new_buffer_size, dtype=numpy.float32)\n",
    "                            new_idx[:last_buffer_size] = tmp_idx\n",
    "                            new_idx[last_buffer_size:] = -1\n",
    "                            new_coef[:last_buffer_size] = tmp_coef\n",
    "                            last_buffer_size = new_buffer_size\n",
    "                            tmp_idx = new_idx\n",
    "                            tmp_coef = new_coef\n",
    "\n",
    "                        if tmp_idx[j] == idx:\n",
    "                            tmp_coef[j] += value\n",
    "                            break\n",
    "                        elif tmp_idx[j] < 0:\n",
    "                            tmp_idx[j] = idx\n",
    "                            tmp_coef[j] = value\n",
    "                            tmp_size +=1\n",
    "                            break     \n",
    "        return tmp_idx[:tmp_size], tmp_coef[:tmp_size]\n",
    "\n",
    "    def calc_csr(self, sample):\n",
    "        \"\"\"Calculate the CSR matrix for the whole image\n",
    "        :param sample: Oversampling factor\n",
    "        :return: CSR matrix\n",
    "        \"\"\"\n",
    "        size = self.width * self.height\n",
    "        allocated_size = BLOCK_SIZE\n",
    "        idptr = numpy.zeros(size+1, dtype=numpy.int32) \n",
    "        indices = numpy.zeros(allocated_size, dtype=numpy.int32)\n",
    "        data = numpy.zeros(allocated_size, dtype=numpy.float32)\n",
    "        self.sampled = sample*sample\n",
    "        pos = 0\n",
    "        start = 0\n",
    "        for row in range(self.height):\n",
    "            for col in range(self.width):    \n",
    "                line_idx, line_coef = self.one_pixel(row, col, sample)\n",
    "                line_size = line_idx.size\n",
    "                if line_size == 0:\n",
    "                    new_size = 0\n",
    "                    pos+=1\n",
    "                    idptr[pos] = start\n",
    "                    continue\n",
    "\n",
    "                stop = start + line_size\n",
    "                \n",
    "                if stop >= allocated_size:\n",
    "                    new_buffer_size = allocated_size +  BLOCK_SIZE\n",
    "                    new_idx = numpy.zeros(new_buffer_size, dtype=numpy.int32)\n",
    "                    new_coef = numpy.zeros(new_buffer_size, dtype=numpy.float32)\n",
    "                    new_idx[:allocated_size] = indices\n",
    "                    new_coef[:allocated_size] = data\n",
    "                    allocated_size = new_buffer_size\n",
    "                    indices = new_idx\n",
    "                    data = new_coef\n",
    "\n",
    "                indices[start:stop] = line_idx\n",
    "                data[start:stop] = line_coef\n",
    "                pos+=1\n",
    "                idptr[pos] = stop\n",
    "                start = stop\n",
    "    \n",
    "        last = idptr[-1]\n",
    "        self.data = data\n",
    "        self.indices = indices\n",
    "        self.idptr = idptr\n",
    "        return (self.data[:last]/self.sampled, indices[:last], idptr)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 4.38 s, sys: 47.7 ms, total: 4.43 s\n",
      "Wall time: 4.43 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(array([0., 0., 0., ..., 0., 0., 0.], dtype=float32),\n",
       " array([      2,       2,       4, ..., 1023180, 1023181, 1023182],\n",
       "       dtype=int32),\n",
       " array([      0,       0,       0, ..., 1902581, 1902582, 1902583],\n",
       "       dtype=int32))"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "thick = ThickDetector(vox,voy, thickness=thickness, mu=mu, dist=dist, poni1=poni1, poni2=poni2, mask=mask)\n",
    "%time thick.calc_csr(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 21.4 s, sys: 104 ms, total: 21.5 s\n",
      "Wall time: 21.4 s\n"
     ]
    }
   ],
   "source": [
    "thick = ThickDetector(vox,voy, thickness=thickness, mu=mu, dist=dist, poni1=poni1, poni2=poni2, mask=mask)\n",
    "%time pre_csr = thick.calc_csr(8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Validation of the CSR matrix obtained:\n",
    "\n",
    "For this we will build a simple 2D image with one pixel in a regular grid and calculate the effect of the transformation calculated previously on it. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "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\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\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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q+nPx3lYgNvGAlyz1L+t8+d+7ucm/bd1nnrqqdP+tSzA7c15lJOzKCsL8ERNzBUL8PCzAJdT6QtX05+P87AAt40FuGaph8oll1wSCxcujCeffHLX177whS9EVVVxyy23TPvXsgC3N+dRTs6irCzAEzUxVyzAw88CXE6lL1xNfz7OwwLcNhbgmtx0000xMjISl156aVRVFWeddVaMjIzEyMhIPPvssxER8eSTT8aBBx4YRxxxRNx4443xoQ99KJYuXRqvfvWrk/+c1mQswO3NeZSTsygrC/D/1eRcsQAPPwtwOZW+cDX9+TgPC3DbWIBrsnz58kn/bZpjY2O7ftymTZvijW98YyxatChe9rKXxfnnnx/f+9739ujXtAC3N+dRTs6irCzA/1eTc8UCPPwswOVU+sLV9OfjPCzAbWMB7jALcHtzHuXkLMrKAtwsC3B9WYDLqfSFq+nPx3lYgNvGAtxhFuD25jzKyVmUlQW4WRbg+rIAl1PpC1fTn4/zsAC3jQW4wyzA7c15lJOzKCsLcLMswPVlAS6n0heupj8f52EBbhsLcIdNZwGWpD6We496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwHuMA8VSUqXe496qAyYK5KULvceNVeaYwEuzOOPPx7nnHNO/MIv/EIsXLgwjj766PjgBz8Y//zP/zzt7+WhIknpcu/RNj9UzBVJqq/ce7TNc6XtLMAFefLJJ+NlL3tZLF++PK6//vpYv359XHjhhVFVVbz1rW+d9vfzUJGkdLn3aFsfKuaKJNVb7j3a1rnSBRbgglx33XW7/Q3xW7/1W1FVVfzwhz+c1vfzUJGkdLn3aFsfKuaKJNVb7j3a1rnSBRbgglx99dVRVVU8/fTTE74+Z86ceP7556f1/TxUJCld7j3a1oeKuSJJ9ZZ7j7Z1rnSBBbggd955566/Le2hhx6KJ598Mj772c/GkiVL4vd///en/f08VCQpXe492taHirkiSfWWe4+2da50gQW4MCMjI7Fw4cKoqmpX73//+6f8eVu2bIlNmzaNa3R01ENFkhJNpQsPFXNFkuprKl2YK21nAS7MrbfeGqtXr45PfOIT8dd//ddx8cUXx1577RU33XRT8uetXbt23OPmX+ahIkm7bypdeKiYK5JUX1PpwlxpOwtwQf7rf/2vsXDhwvjud7877usXXnhhLFq0KH7wgx9M+nP9lXpJmn5TaftDxVyRpHqbStvnShdYgAvy+te/Pl73utdN+Prtt98eVVXF3XffPa3v55/VkqR0ufdoWx8q5ook1VvuPdrWudIFFuCCrFixIlatWjXh67fddltUVRV33nnntL6fh4okpcu9R9v6UDFXJKnecu/Rts6VLrAAF+TNb35zzJ8/P775zW+O+/qv//qvx5w5c+Kpp56a1vfzUJGkdLn3aFsfKuaKJNVb7j3a1rnSBRbggtxzzz2x9957xyte8Yq45ppr4k/+5E/ijDPOiKqq4t3vfve0v5+HiiSly71H2/pQMVckqd5y79G2zpUusAAX5oEHHogzzjgjDj744Jg3b16sWLEirrvuunjxxRen/b08VCQpXe492uaHirkiSfWVe4+2ea60nQW4wzxUJCld7j3qoTJgrkhSutx71FxpjgW4wzxUJCld7j3qoTJgrkhSutx71FxpjgW4wzxUJCld7j3qoTJgrkhSutx71FxpjgW4wzxU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1hjGWfi9seemc1f9h//wH6KqqnjnO98Z69evjw9/+MNx+OGHx9y5c+OBBx4Y92P93ph95ko5zJSymCvlMFPYyQLcQ2eeeWYsXbo0fvCDH+z62j/90z/FvvvuG2eddda4H/vhD3845s2bN+E3+8/aeRn/1V/91VD+mLtsOuexO294wxtiyZIl8cILL+z62gMPPBBVVcVHPvKRXV974YUX4ogjjogTTzxxdv8EOmQYZ+H3xp7LPY8XX3wxFi5cGO94xzvG/fxvf/vbux6JO/m9MRzmSjnMlLKYK+UwU9jJAtxD++23X5x99tkTvv6rv/qrMX/+/Ni6dWtEDP72j5//+Z+P3/iN34iIwYXwz//8z7v9nv/yMv7Rj34UL7744vD+BDom9zx255/+6Z9izpw5ceGFF477+pVXXhl77713PPfcc+O+/qEPfSiqqoonn3xydv7gO2YYZ+H3xp7LPY8f//jHUVVVXHbZZeN+3PPPPx9z5syJq6++etfX/N4YDnOlHGZKWcyVcpgp7GQB7qH58+fHb/3Wb034+tlnnz3unx/5+te/HlVVxbXXXhu//du/HfPnz4+qquLVr351/K//9b/G/dydl/G+++4bVVXF3nvvHSeffHI8+OCDtfw5tVnueezORz/60aiqKu6+++5xXz/ttNPimGOOmfDjv/CFL0RVVfE3f/M3M/8D76BhnIXfG3tuOuexatWqWLx4cfyX//Jf4jvf+U783d/9XbzjHe+IAw88MJ544oldP87vjeEwV8phppTFXCmHmcJOFuAeevWrXx0rVqyIl156adfXtm/fHsuWLYuqquK//bf/FhERt99+e1RVFQceeGAcddRRsWHDhtiwYUMcddRRMX/+/Pi7v/u7XT//y1/+crz97W+PT33qU/Hf//t/j+uvvz4OPPDAWLBgQXzta1+r/c+xTXLPY3f+9b/+13HIIYfEjh07xn392GOPjVNPPXXCj3/00Uejqqr40z/909n7E+iQYZyF3xt7bjrn8a1vfSte85rXjPuXwRx++OET/rkuvzeGw1wph5lSFnOlHGYKO1mAe+iWW26JqqriggsuiEcffTS+/vWvxznnnBPz5s2Lqqri1ltvjYiIv/iLv4iqqmL+/Pnj/haO73znOzFv3rw4//zzk7/Ot771rVi4cGGsXr16qH8+bZd7Hj/rm9/8ZlRVFf/+3//7Cf/Z4YcfHmecccaErz/xxBNRVVV87GMfm/U/jy4Yxlnsjt8beaZzHt/73vfiXe96V1x22WVx++23x8033xzLli2LX/qlXxr3L4Xxe2M4zJVymCllMVfKYaawkwW4p973vvft+g1fVVX8m3/zb+L9739/VFUVd9xxR0RE/NVf/VVUVRWnnHLKhJ9/yimnxGGHHTblr3PuuefG/Pnzx/3VNibKOY+ftWbNmqiqKr761a9O+M/8Fck9N9tnMRm/N/LknMeLL74Yr3rVq+Lf/bt/N+7nPv744zFv3ry46qqrdn3N743hMVfKYaaUxVwph5lChAW41374wx/GvffeG4888khERLz3ve+Nqqri0UcfjYjB32JTVVWce+65E37uOeecEy972cum/DWuvPLKqKpqwr8cgImmOo+fdeSRR8bRRx+92//MP5MyM7N5FpPxeyPfVOfxxS9+Maqq2u3/7uJxxx0XJ5100q7/2++N4TJXymGmlMVcKYeZggWYXV772tfGL/7iL+76Z01+9KMfxbx58+L1r3/9hB/7+te/Po466qgpv+fb3/72WLBgwYR/foWp/ex5/Ev3339/VFUV11xzzW5/7hVXXLHbfyvhddddF1Xl30o4XTM5i8n4vbHnfvY8/vIv/zKqqoo777xzwo895phjYtWqVbv+b7836mWulMNMKYu5Ug4zpX8swERExGc/+9moqirWrVs37uu/9mu/FnvvvXd84xvf2PW1xx57LPbee+/43d/93V1f+/73vz/hez788MMxb968eOtb3zq8P/COmuw8dvq93/u9qKoq/uEf/mG3//nO4fkv/3fptm3bFkceeeS4i5upzfQs/N6YXbs7j69+9au7/rmuf2njxo0xZ86cuOSSS3Z9ze+N+pgr5TBTymKulMNM6ScLcA/dc8898YY3vCE+/OEPx5/92Z/Fu9/97th7773jTW9604T/LblHH3009t133zjkkEPi+uuvj+uvvz4OOeSQ+Lmf+7n4x3/8x10/7pRTTokzzzwzrr322vjEJz4Rv//7vx+LFi2K/fffPx577LG6/xRbZTrnERHx0ksvxUEHHRQnnHBC8vueffbZMXfu3Ljyyitj/fr18brXvS7mzp0b99xzz7D+VFpvGGfh98aem855nH766VFVVbztbW+LW265JdasWRNLly6NxYsXT/i3dvq9MfvMlXKYKWUxV8phprCTBbiH/uEf/iHe+MY3xstf/vLYZ5994pd+6Zfi+uuvj+3bt+/2x2/cuDFOO+20WLx4cey3337xa7/2a/H444+P+zE33HBDrFy5Mg444ICYO3duHHLIIfHOd74zvvWtb9Xxp9Rq0z2Pz3/+81FVVdx4443J7/vCCy/EFVdcEQcffHDss88+8drXvjY+//nPD+NPoTOGcRZ+b+y56ZzHj3/847jmmmvil3/5l2PhwoWx//77x5vf/OZ46KGHJvxYvzdmn7lSDjOlLOZKOcwUdrIAAwAA0AsWYAAAAHrBAgwAAEAvWIAB2j+UOQAAIABJREFUAADoBQswAAAAvWABBgAAoBcswAAAAPSCBRgAAIBesAADAADQCxZgAAAAesECDAAAQC9YgAEAAOgFCzAAAAC9YAEGAACgFyzAAAAA9IIFGAAAgF6wAAMAANALFmAAAAB6wQIMAABAL1iAAQAA6AULMAAAAL1gAQYAAKAXLMAAAAD0ggUYAACAXrAAAwAA0AsWYAAAAHrBAgwAAEAvWIABAADoBQswAAAAvWABBgAAoBcswAAAAPSCBRgAAIBesAADAADQCxZgAAAAesECDAAAQC9YgAEAAOgFCzAAAAC9YAEGAACgFyzAAAAA9IIFGAAAgF6wAAMAANALFmAAAAB6wQIMAABAL1iAAQAA6AULMAAAAL1gAQYAAKAXLMAAAAD0ggUYAACAXrAAAwAA0AsWYAAAAHrBAgwAAEAvWIABAADoBQswAAAAvWABrsHWrVtjzZo1sXr16li6dGlUVRUbNmzY7Y/dsWNH3HzzzXH88cfHggUL4oADDohTTjklHn744Xr/oAEolrkCAHvGAlyDsbGxqKoqli1bFieffHLyoXLBBRfE3Llz4+KLL45PfvKT8fGPfzwuuOCCuOuuu+r9gwagWOYKAOwZC3ANtm3bFps3b46IiAcffHDSh8ptt90WVVXF7bffXvMfIQBtYq4AwJ6xANcs9VBZtWpVrFy5MiIGf8va888/X/MfHQBtY64AQD4LcM0me6g899xzsddee8Vll10W733ve2PfffeNqqrisMMOi9tuu62ZP1gAimeuAEA+C3DNJnuofO1rX4uqquLAAw+Mgw46KG6++eb4zGc+EytXroy99tor7rzzzuT33bJlS2zatGlc9913X3z605+OjRs3TvjPJElTt3HjxhgdHY1nnnlmiJNhZswVSWpPbZgrXWcBrtlkD5UvfelLUVVVVFUV999//66vb926NV7+8pfHSSedlPy+a9eu3fXzJUmz2+jo6DBGwqwwVySpfZU8V7rOAlyzyR4qO79+2GGHTfg5F110UcybNy9efPHFSb/v7v5K/ec+97moqiqOq06ME6rTJUk/01R/pX50dDSqqoqNGzfO9jiYNeaKJJVTF+ZK11mAazbZQ+Wpp56KqqrihBNOmPBzrr766qiqKp599tlp/VqbNm0afM/q9Dhtr3dIkn6m3Ht006ZN07p/62SuSFI55d6jJc+VrrMA1yz1b+s8+OCD49BDD53w9Xe9612xYMGC2LFjx7R+LQ8VSUqXe4+W/FAxVySpnHLv0ZLnStdZgGuWeqhcfvnlUVVV3HXXXbu+9vTTT8eSJUvizDPPnPav5aEiSely79GSHyrmiiSVU+49WvJc6ToLcE1uuummGBkZiUsvvTSqqoqzzjorRkZGYmRkZNffgva9730vDjnkkNhvv/1i7dq18dGPfjRWrFgRCxcujIcffnjav6aHiiSly71HS3yomCuSVF6592iJc6UvLMA1Wb58+aT/FrixsbFdP+6JJ56It73tbbFkyZJYuHBhnHrqqfGVr3xlj35NDxVJSpd7j5b4UDFXJKm8cu/REudKX1iAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO2w6D5Udm4+c1Zq+fNqe8ygnZ1FWs30eufeoh8rAdObK8hvXzWqrV1w1qzX93+W6m/XzOPrqWa3pz8dZ9PMsTtvrHfHKG9bNarn3qLnSHAtwh1mA25vzKCdnUVYW4GZZgNubpaucnEVZWYD7xwLcYRbg9uY8yslZlJUFuFkW4PZm6SonZ1FWFuD+sQB3mAW4vTmPcnIWZWUBbpYFuL1ZusrJWZSVBbh/LMAdZgFub86jnJxFWVmAm2UBbm+WrnJyFmVlAe4fC3CHWYDbm/MoJ2dRVhbgZlmA25ulq5ycRVlZgPvHAtxhFuD25jzKyVmUlQW4WRbg9mbpKidnUVYW4P6xAHeYBbi9OY9ychZlZQFulgW4vVm6yslZlJUFuH8swB1mAW5vzqOcnEVZWYCbZQFub5aucnIWZWUB7h8LcIdZgNub8ygnZ1FWFuBmWYDbm6WrnJxFWVmA+8cC3GEW4PbmPMrJWZSVBbhZFuD2ZukqJ2dRVhbg/rEAd5gFuL05j3JyFmVlAW6WBbi9WbrKyVmUlQW4fyzAHWYBbm/Oo5ycRVlZgJtlAW5vlq5ychZlZQHuHwtwh1mA25vzKCdnUVYW4GZZgNubpaucnEVZWYD7xwLcYRbg9uY8yslZlJUFuFkW4PZm6SonZ1FWFuD+sQB3mAW4vTmPcnIWZWUBbpYFuL1ZusrJWZSVBbh/LMAdZgFub86jnJxFWVmAm2UBbm+WrnJyFmVlAe4fC3CHWYDbm/MoJ2dRVhbgZlmA25ulq5ycRVlZgPvHAtxhFuD25jzKyVmUlQW4WRbg9mbpKidnUVYW4P6xAHeYBbi9OY9ychZlZQFulgW4vVm6yslZlJUFuH8swB02nYeKJPWx3HvUQ2VgOnNl1XnrZrXTX/uHs1rT/92ru1XnrpvVTl/5wVmt6c/HWfTzLIZxHrn3qLnSHAtwh1mAJSld7j3qoTJgAW5vlq5ychZlZQHuHwtwh1mAJSld7j3qoTJgAW5vlq5ychZlZQHuHwtwh1mAJSld7j3qoTJgAW5vlq5ychZlZQHuHwtwDbZu3Rpr1qyJ1atXx9KlS6OqqtiwYUPy5/zkJz+JY445Jqqqio985CN79OtagCUpXe49WtpDpQ1zxQJcVpaucnIWZWUB7h8LcA3GxsaiqqpYtmxZnHzyyVkPlT/+4z+OxYsXW4AlaYjl3qOlPVTaMFcswGVl6SonZ1FWFuD+sQDXYNu2bbF58+aIiHjwwQenfKhs2bIl9t9//7jmmmsswJI0xHLv0dIeKm2YKxbgsrJ0lZOzKCsLcP9YgGuW81C56KKLYuXKlfHtb3/bAixJQyz3Hi35oVLqXLEAl5Wlq5ycRVlZgPvHAlyzqR4qDzzwQMyZMyfuu+++XX+LmwVYkoZT7j1a8kOl1LliAS4rS1c5OYuysgD3jwW4ZqmHyk9/+tNYuXJlnHfeeRER03qobNmyJTZt2jSu0dFRC7AkJZpKGx4qpc4VC3BZWbrKyVmUlQW4fyzANUs9VP78z/88Fi5cGE8++WRETO+hsnbt2qiqardZgCVp902lDQ+VUueKBbisLF3l5CzKygLcPxbgmk32UHnuuefioIMOijVr1uz6mv8PsCQNt6m04aFS6lyxAJeVpaucnEVZWYD7xwJcs8keKh/4wAdi6dKl8eijj8bY2FiMjY3FvffeG1VVxfve974YGxuL7du3T+vX8s8AS1K63Hu05IdKqXPFAlxWlq5ychZlZQHuHwtwzSZ7qFxwwQWT/q1mO3vooYem9WtZgCUpXe49WvJDpdS5YgEuK0tXOTmLsrIA948FuGaTPVQ2btwYd9xxx7jWr18fVVXFhRdeGHfccUc8++yz0/q1LMCSlC73Hi35oVLqXLEAl5Wlq5ycRVlZgPvHAlyTm266KUZGRuLSSy+NqqrirLPOipGRkRgZGZn0AeJ/BkmShlvuPVriQ6X0uWIBLitLVzk5i7KyAPePBbgmy5cvn/RvQRsbG9vtz7EAS9Jwy71HS3yolD5XLMBlZekqJ2dRVhbg/rEAd5gFWJLS5d6jHioDFuD2ZukqJ2dRVhbg/rEAd5gFWJLS5d6jHioDFuD2ZukqJ2dRVhbg/rEAd5gFWJLS5d6jHioDFuD2ZukqJ2dRVhbg/rEAd5gFWJLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V5liAO8xDRZLS5d6jHioD5ookpcu9R82V/7+9ew+2qy7vP77MDZJwCygXFbAhBDEFO7QQwWFEbhEKWMEIClVr6QilXtpJoNYpKYRAqRkRqGJoGVrTWtQKqTMdKGj9pQwWitEKAQSkGUDEoPUGmkTA5/fHGlKPJ/me7w7n7PVda73eM+8/3OyzT04e9vN5PpFz0hwKcIdxqJBk2tw96lCpkSskmTZ3j8qV5lCAO4xDhSTT5u5Rh0qNXCHJtLl7VK40hwLcYRwqJJk2d486VGrkCkmmzd2jcqU5FOAO41AhybS5e9ShUiNXSDJt7h6VK82hAHcYhwpJps3dow6VGrlCkmlz96hcaQ4FuMM4VEgybe4edajUyBWSTJu7R+VKcyjAHcahQpJpc/eoQ6VGrpBk2tw9KleaQwHuMA4Vkkybu0cdKjVyhSTT5u5RudIcCvAQePrpp+PCCy+MBQsWxKxZs6Kqqrj++utHPOf555+P66+/Pk4++eR45StfGTNmzIh58+bF0qVLY8OGDdv0eR0qJJk2d4+WdqjIFZIs09w9Wlqu9AkFeAisW7cuqqqKffbZJ4466qgtHipPP/10fVS87nVxySWXxLXXXhu/93u/F5MmTYqjjjoqfvGLXwz8eR0qJJk2d4+WdqjIFZIs09w9Wlqu9AkFeAhs3LgxnnzyyYiIuPvuu7d4qGzatCnuuOOOUR970UUXRVVVcdtttw38eR0qJJk2d4+WdqjIFZIs09w9Wlqu9AkFeMhs7VDZGvfcc09UVRVXXXXVwJ/LoUKSaXP3aMmHilwhyXLM3aMl50rXUYCHzKCHyq233hpVVcWnP/3pgT+XQ4Uk0+bu0ZIPFblCkuWYu0dLzpWuowAPmUEPlWOPPTZ22mmn+OEPf5h83vr162Pt2rUjXLVqlUOFJBOORRsOFblCkuU4Fm3Ila6jAA+ZQQ6VZcuWRVVV8YlPfGLM5y5ZsiSqqtqiDhWS3LJj0YZDRa6QZDmORRtypesowEMm91C54YYb4iUveUn8/u//ftbr+pN6khzcsWjDoSJXSLIcx6INudJ1FOAhk3Oo3HrrrTFt2rQ46aST4tlnn93mz+V7tUgybe4eLflQkSskWY65e7TkXOk6CvCQGetQufPOO2PmzJlxxBFHxM9+9rMX9bkcKiSZNnePlnyoyBWSLMfcPVpyrnQdBXjIpA6V+++/P3bbbbeYN29e/OAHP3jRn8uhQpJpc/doyYeKXCHJcszdoyXnStdRgIfE1VdfHUuXLo1zzz03qqqKU089NZYuXRpLly6NH/3oR/GTn/wk9t5775g0aVL85V/+ZaxcuXKEX/nKVwb+nA4Vkkybu0dLPFTkCkmWZ+4eLTFX+oICPCT23Xffrf40zXXr1sW6deu2+s+rqop3vetdA39OhwpJps3doyUeKnKFJMszd4+WmCt9QQHuMA4Vkkybu0cdKjVyhSTT5u5RudIcCnCHcaiQZNrcPepQqZErJJk2d4/KleZQgDuMQ4Uk0+buUYdKjVwhybS5e1SuNIcC3GEcKiSZNnePOlRq5ApJps3do3KlORTgDuNQIcm0uXvUoVIjV0gybe4elSvNoQB3GIcKSabN3aMOlRq5QpJpc/eoXGkOBbjDOFRIMm3uHnWo1MgVkkybu0flSnMowB3GoUKSaXP3qEOlRq6QZNrcPSpXmkMB7jAOFZJMm7tHHSo1coUk0+buUbnSHApwh3GokGTa3D3qUKmRKySZNnePypXmUIA7jEOFJNPm7lGHSo1cIcm0uXtUrjSHAtxhHCokmTZ3jzpUauQKSabN3aNypTkU4A7jUCHJtLl71KFSI1dIMm3uHpUrzaEAdxiHCkmmzd2jDpUauUKSaXP3qFxpDgW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAe4wDhWSTJu7Rx0qNXKFJNPm7lG50hwKcIdxqJBk2tw96lCpkSskmTZ3j8qV5lCAO4xDhSTT5u5Rh0qNXCHJtLl7VK40hwLcYRwqJJk2d486VGrkCkmmzd2jcqU5FOAO41AhybS5e9ShUiNXSDJt7h6VK82hAHcYhwpJps3dow6VGrlCkmlz96hcaQ4FuMM4VEgybe4edajUyBWSTJu7R+VKcyjAHWaQQ+X5J+eMq00vn7ZrHuVoFmU53vPI3aMOlZpBcmXfq5aPqwvmnj+uNv3v8rB91ZXLx1XzKGgWB1wwrjb9+9P2eeTuUbnSHApwh1GA26t5lKNZlKUC3CwKcHtVgMtRAS5LBbh/KMAdRgFur+ZRjmZRlgpwsyjA7VUBLkcFuCwV4P6hAHcYBbi9mkc5mkVZKsDNogC3VwW4HBXgslSA+4cCPASefvrpuPDCC2PBggUxa9asqKoqrr/++i0+9/77748FCxbEzJkzY9asWXHWWWfFU089tU2fVwFur+ZRjmZRlgpwTRtyRQEuSwW4HBXgslSA+4cCPATWrVsXVVXFPvvsE0cdddRWD5XHH388XvrSl8Z+++0XV155ZSxbtixmzZoVr33ta2PTpk0Df14FuL2aRzmaRVkqwDVtyBUFuCwV4HJUgMtSAe4fCvAQ2LhxYzz55JMREXH33Xdv9VA599xzY/r06fHoo49ufuy2226LqqpixYoVA39eBbi9mkc5mkVZKsA1bcgVBbgsFeByVIDLUgHuHwrwkEkdKrvvvnssXLhw1ONz586NY445ZuDPpQC3V/MoR7MoSwV4NKXmigJclgpwOSrAZakA9w8FeMhs7VD59re/HVVVxeWXXz7qY84666zYddddB/5cCnB7NY9yNIuyVIBHU2quKMBlqQCXowJclgpw/1CAh8zWDpUXHv/Upz416mMWL14cVVXFxo0bt/q669evj7Vr145w1apVCnBLNY9yNIuyVIBHU2quKMBlqQCXowJclgpw/1CAh8zWDpX/+I//iKqq4jOf+cyoj/nzP//zqKoqfvjDH271dZcsWRJVVW1RBbh9mkc5mkVZKsCjKTVXFOCyVIDLUQEuSwW4fyjAQ6bUP6l35JeleZSjWZSlAjyaUnNFAS5LBbgcFeCyVID7hwI8ZEr9Xi1HflmaRzmaRVkqwKMpNVcU4LJUgMtRAS5LBbh/KMBDJvXTOl/2spdt9ad1Hn300QN/LgW4vZpHOZpFWSrAoyk1VxTgslSAy1EBLksFuH8owEMmdaicc845MX369Hjsscc2P/bFL34xqqqKa665ZuDPpQC3V/MoR7MoSwV4NKXmigJclgpwOSrAZakA9w8FeEhcffXVsXTp0jj33HOjqqo49dRTY+nSpbF06dL40Y9+FBERjz32WOy2226x3377xVVXXRWXXnppzJo1Kw466KDk92ltDQW4vZpHOZpFWSrA/0fpuaIAl6UCXI4KcFkqwP1DAR4S++6771Z/mua6des2P2/t2rVx/PHHx4wZM2KXXXaJM888M7773e9u0+dUgNureZSjWZSlAvx/lJ4rCnBZKsDlqACXpQLcPxTgDqMAt1fzKEezKEsFuFkU4PaqAJejAlyWCnD/UIA7jALcXs2jHM2iLBXgZlGA26sCXI4KcFkqwP1DAe4wCnB7NY9yNIuyVICbRQFurwpwOSrAZakA9w8FuMMMcqiQZB/N3aMOlZpBcuWwdywfV4879C/G1ab/3Ru289++fFw97rCLxtWmf3/Mop+zOPYlb435ZywfV3P3qFxpDgW4wyjAJJk2d486VGoU4PaqdJWjWZSlAtw/FOAOowCTZNrcPepQqVGA26vSVY5mUZYKcP9QgDuMAkySaXP3qEOlRgFur0pXOZpFWSrA/UMB7jAKMEmmzd2jDpUaBbi9Kl3laBZlqQD3DwW4wyjAJJk2d486VGoU4PaqdJWjWZSlAtw/FOAOowCTZNrcPepQqVGA26vSVY5mUZYKcP9QgDuMAkySaXP3qEOlRgFur0pXOZpFWSrA/UMB7jAKMEmmzd2jDpUaBbi9Kl3laBZlqQD3DwW4wyjAJJk2d486VGoU4PaqdJWjWZSlAtw/FOAOowCTZNrcPepQqVGA26vSVY5mUZYKcP9QgDuMAkySaXP3qEOlRgFur0pXOZpFWSrA/UMB7jAKMEmmzd2jDpUaBbi9Kl3laBZlqQD3DwW4wyjAJJk2d486VGoU4PaqdJWjWZSlAtw/FOAOowCTZNrcPepQqVGA26vSVY5mUZYKcP9QgDuMAkySaXP3qEOlRgFur0pXOZpFWSrA/UMB7jAKMEmmzd2jDpUaBbi9Kl3laBZlqQD3DwW4wyjAJJk2d486VGoU4PaqdJWjWZSlAtw/FOAOowCTZNrcPepQqVGA26vSVY5mUZYKcP9QgDuMAkySaXP3qEOlRgFur0pXOZpFWSrA/UMB7jAKMEmmzd2jDpUauUKSaXP3qFxpDgW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAS6Mhx56KE4//fR4xSteEdOnT48DDjggLrroovjpT3868Gs5VEgybe4ebfOhIldIcnjm7tE250rbUYAL4rHHHotddtkl9t1337jssstixYoV8e53vzuqqopTTjll4NdzqJBk2tw92tZDRa6Q5HDN3aNtzZUuoAAXxLJly7b4hnjnO98ZVVXFD37wg4Fez6FCkmlz92hbDxW5QpLDNXePtjVXuoACXBAXXHBBVFUV3/ve90Y9PmnSpHjmmWcGej2HCkmmzd2jbT1U5ApJDtfcPdrWXOkCCnBB3HzzzZv/s7Svf/3r8dhjj8UNN9wQO+20U3zwgx8c+PUcKiSZNnePtvVQkSskOVxz92hbc6ULKMCFsXTp0pg+fXpUVbXZD3/4w2N+3Pr162Pt2rUjXLVqlUOFJBOORRcOFblCksNzLLqQK21HAS6MlStXxoIFC+Laa6+Nz3/+8/Ge97wnXvKSl8TVV1+d/LglS5aMOG5+WYcKSW7ZsejCoSJXSHJ4jkUXcqXtKMAF8U//9E8xffr0ePzxx0c8/u53vztmzJgR3//+97f6sf6kniQHdyzafqjIFZIcrmPR9lzpAgpwQRx55JFxxBFHjHr8xhtvjKqq4rbbbhvo9XyvFkmmzd2jbT1U5ApJDtfcPdrWXOkCCnBBzJ07N+bPnz/q8c985jNRVVXcfPPNA72eQ4Uk0+bu0bYeKnKFJIdr7h5ta650AQW4IE466aSYNm1aPPjggyMe/53f+Z2YNGlSPPHEEwO9nkOFJNPm7tG2HipyhSSHa+4ebWuudAEFuCBWr14dkydPjt133z0uvvji+PjHPx4nnHBCVFUVZ5999sCv51AhybS5e7Sth4pcIcnhmrtH25orXUABLoy77rorTjjhhNhzzz1j6tSpMXfu3Fi2bFk8++yzA7+WQ4Uk0+bu0TYfKnKFJIdn7h5tc660HQW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAe4wDhWSTJu7Rx0qNXKFJNPm7lG50hwKcIdxqJBk2tw96lCpkSskmTZ3j8qV5lCAO4xDhSTT5u5Rh0qNXCHJtLl7VK40hwLcYRwqJJk2d486VGrkCkmmzd2jcqU5FOAO41AhybS5e9ShUiNXSDJt7h6VK82hAHcYhwpJps3dow6VGrlCkmlz96hcaQ4FuMM4VEgybe4edajUyBWSTJu7R+VKcyjAHcahQpJpc/eoQ6VGrpBk2tw9KleaQwHuMA4Vkkybu0cdKjVyhSTT5u5RudIcCnCHcaiQZNrcPepQqZErJJk2d4/KleZQgDuMQ4Uk0+buUYdKjVwhybS5e1SuNIcC3GEcKiSZNnePOlRq5ApJps3do3KlORTgDuNQIcm0uXvUoVIjV0gybe4elSvNoQB3GIcKSabN3aMOlRq5QpJpc/eoXGkOBbjDOFRIMm3uHnWo1MgVkkybu0flSnMowB3GoUKSaXP3qEOlRq6QZNrcPSpXmkMB7jAOFZJMm7tHHSo1coUk0+buUbnSHApwh3GokGTa3D3qUKmRKySZNnePypXmUIA7jEOFJNPm7lGHSo1cIcm0uXtUrjSHAtxhHCokmTZ3jzpUauQKSabN3aNypTkU4A7jUCHJtLl71KFSI1dIMm3uHpUrzaEAdxiHCkmmzd2jDpUauUKSaXP3qFxpDgW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAe4wDhWSTJu7Rx0qNXKFJNPm7lG50hwKcIGsWbMmTj755Jg1a1ZMnz495s2bF1deeeXAr+NQIcm0uXu07YeKXCHJ4Zi7R9ueK21GAS6Mf/u3f4tp06bF/Pnz46Mf/Whce+21ccEFF8TixYsHfi2HCkmmzd2jbT5U5ApJDs/cPdrmXGk7CnBB/PjHP4499tgj3vKWt8Tzzz//ol/PoUKSaXP3aFsPFblCksM1d4+2NVe6gAJcENdcc01UVRX3339/REQ888wzL+pgcaiQZNrcPdrWQ0WukORwzd2jbc2VLqAAF8Rpp50WO+20U9x2220xd+7cqKoqZs6cGeecc05s2LBh4NdzqJBk2tw92tZDRa6Q5HDN3aNtzZUuoAAXxMEHHxwzZsyIGTNmxPve9774/Oc/H+973/uHxIk6AAAeKElEQVSiqqo444wzkh+7fv36WLt27QhXrVrlUCHJhGPR9kNFrpDkcB2LtudKF1CAC2L27NlRVVWcc845Ix5/73vfG1VVxUMPPbTVj12yZElUVbVFHSokuWXHou2HilwhyeE6Fm3PlS6gABfEvHnzoqqqWL169YjHV69eHVVVxd///d9v9WP9ST1JDu5YtP1QkSskOVzHou250gUU4II47rjjoqqq+OY3vzni8QceeCCqqoqPfexjA72e79UiybS5e7Sth4pcIcnhmrtH25orXUABLog//dM/jaqq4ktf+tKIx7/0pS9FVVXxj//4jwO9nkOFJNPm7tG2HipyhSSHa+4ebWuudAEFuCC+9rWvRVVV8Y53vGPE429/+9tjypQp8cQTTwz0eg4Vkkybu0fbeqjIFZIcrrl7tK250gUU4MJ4z3veE1VVxdve9rb4+Mc/HgsXLoyqquJDH/rQwK/lUCHJtLl7tM2HilwhyeGZu0fbnCttRwEujJ///OfxF3/xF7HvvvvG1KlTY86cOXHFFVds02s5VEgybe4ebfOhIldIcnjm7tE250rbUYA7jEOFJNPm7lGHSo1cIcm0uXtUrjSHAtxhHCokmTZ3jzpUauQKSabN3aNypTkU4A7jUCHJtLl71KFSI1dIMm3uHpUrzaEAd5hBDpXnn5wzrja9fNqueZSjWZTleM8jd486VGoGyZVXXbl8XF0w9/xxtel/l4eteZSjWZTlqz62fFzN3aNypTkU4A6jALdX8yhHsyhLBbhZFOD2ah7laBZlqQD3DwW4wyjA7dU8ytEsylIBbhYFuL2aRzmaRVkqwP1DAe4wCnB7NY9yNIuyVICbRQFur+ZRjmZRlgpw/1CAO4wC3F7NoxzNoiwV4GZRgNureZSjWZSlAtw/FOAOowC3V/MoR7MoSwW4WRTg9moe5WgWZakA9w8FuMMowO3VPMrRLMpSAW4WBbi9mkc5mkVZKsD9QwHuMApwezWPcjSLslSAm0UBbq/mUY5mUZYKcP9QgDuMAtxezaMczaIsFeBmUYDbq3mUo1mUpQLcPxTgDqMAt1fzKEezKEsFuFkU4PZqHuVoFmWpAPcPBbjDKMDt1TzK0SzKUgFuFgW4vZpHOZpFWSrA/UMB7jAKcHs1j3I0i7JUgJtFAW6v5lGOZlGWCnD/UIA7jALcXs2jHM2iLBXgZlGA26t5lKNZlKUC3D8U4A6jALdX8yhHsyhLBbhZFOD2ah7laBZlqQD3DwW4wyjA7dU8ytEsylIBbhYFuL2aRzmaRVkqwP1DAe4wCnB7NY9yNIuyVICbRQFur+ZRjmZRlgpw/1CAO4wC3F7NoxzNoiwV4GZRgNureZSjWZSlAtw/FOAOowC3V/MoR7MoSwW4WRTg9moe5WgWZakA9w8FuMMowO3VPMrRLMpSAW4WBbi9mkc5mkVZKsD9QwHuMApwezWPcjSLslSAm0UBbq/mUY5mUZYKcP9QgDvMIIcKSfbR3D3qUKkZJFcOe8fycfW4wy4aV5v+d2/Ymkc5zn/78nH12PkXja8F/B61eR65e1SuNIcC3GEUYJJMm7tHHSo1CnB7NY9yVIDLUgHuHwpwh1GASTJt7h51qNQowO3VPMpRAS5LBbh/KMAdRgEmybS5e9ShUqMAt1fzKEcFuCwV4P6hABfOJZdcElVVxbx58wb+WAWYJNPm7tGuHCovJlMiFOA2ax7lqACXpQLcPxTggnn88cdjxowZMXPmTAWYJCfA3D3ahUPlxWZKhALcZs2jHBXgslSA+4cCXDCnn356HH300fGGN7xBASbJCTB3j3bhUHmxmRKhALdZ8yhHBbgsFeD+oQAXyurVq2Py5Mlxzz33KMAkOUHm7tG2HyrjkSkRCnCbNY9yVIDLUgHuHwpwgTz33HNx8MEHx3vf+96ICAWYJCfI3D3a5kNlvDIlQgFus+ZRjgpwWSrA/UMBLpC//uu/jp133jmeeuqpiMg7VtavXx9r164d4apVqxRgkkw4Fl04VLYlUyJefK4oXGVpHuWoAJelAtw/FODC+P73vx+77rprLF/+f2+gnGNlyZIlUVXVFlWASXLLjkXbD5VtzZSIF58rCldZmkc5KsBlqQD3DwW4MM4555yYM2dObNq0afNj/h9gkpwYx6Lth8q2ZkqE/we4a5pHOSrAZakA9w8FuCAeeuihmDRpUlx11VWxbt26zc6fPz/mzp0b69ati//93//Nfj3fA0ySaXP3aBsPlfHOlAjfA9xmzaMcFeCyVID7hwJcEF/+8pe3+p+bveAHPvCB7NdTgEkybe4ebeOhMt6ZEqEAt1nzKEcFuCwV4P6hABfE9773vbjppptGOW/evNhnn33ipptuinvuuSf79RRgkkybu0fbeKiMd6ZEKMBt1jzKUQEuSwW4fyjALcBfg0SSE2PuHu3SoeKvQepf4TKPslSAy1IB7h8KcAtQgElyYszdo106VBTg/hUu8yhLBbgsFeD+oQB3GAWYJNPm7lGHSo0C3F7NoxwV4LJUgPuHAtxhFGCSTJu7Rx0qNQpwezWPclSAy1IB7h8KcIdRgEkybe4edajUKMDt1TzKUQEuSwW4fyjAHUYBJsm0uXvUoVIjV0gybe4elSvNoQB3GIcKSabN3aMOlRq5QpJpc/eoXGkOBbjDOFRIMm3uHnWo1MgVkkybu0flSnMowB3GoUKSaXP3qEOlRq6QZNrcPSpXmkMB7jAOFZJMm7tHHSo1coUk0+buUbnSHApwh3GokGTa3D3qUKmRKySZNnePypXmUIA7jEOFJNPm7lGHSo1cIcm0uXtUrjSHAtxhHCokmTZ3jzpUauQKSabN3aNypTkU4A7jUCHJtLl71KFSI1dIMm3uHpUrzaEAdxiHCkmmzd2jDpUauUKSaXP3qFxpDgW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAe4wDhWSTJu7Rx0qNXKFJNPm7lG50hwKcIdxqJBk2tw96lCpkSskmTZ3j8qV5lCAO4xDhSTT5u5Rh0qNXCHJtLl7VK40hwLcYRwqJJk2d486VGrkCkmmzd2jcqU5FOAO41AhybS5e9ShUiNXSDJt7h6VK82hAHcYhwpJps3dow6VGrlCkmlz96hcaQ4FuMM4VEgybe4edajUyBWSTJu7R+VKcyjAHcahQpJpc/eoQ6VGrpBk2tw9KleaQwHuMA4Vkkybu0cdKjVyhSTT5u5RudIcCnCHcaiQZNrcPepQqZErJJk2d4/KleZQgDuMQ4Uk0+buUYdKjVwhybS5e1SuNIcC3GEcKiSZNnePOlRq5ApJps3do3KlORTggviv//qvOO+88+I1r3lNzJgxI/bee+9YuHBhPPjgg9v0eg4Vkkybu0fbeqjIFZIcrrl7tK250gUU4II47bTTYs8994z3ve998Td/8zexdOnS2GOPPWLmzJlx7733Dvx6DhWSTJu7R9t6qMgVkhyuuXu0rbnSBRTggrjjjjti06ZNIx576KGHYrvttoszzzxz4NdzqJBk2tw92tZDRa6Q5HDN3aNtzZUuoAC3gEMOOSQOOeSQgT/OoUKSaXP3aNcOFblCkhNj7h7tWq60CQW4cH7xi1/EK17xijj++OMH/liHCkmmzd2jXTpU5ApJTpy5e7RLudI2FODCWblyZVRVFdddd13yeevXr4+1a9eOcNWqVQ4Vkkw4Fl08VOQKSU6cY9HFXGkbCnDBPPDAA7HTTjvF4YcfHs8991zyuUuWLImqqraoQ4Ukt+xYdO1QkSskObGORddypY0owIXy5JNPxuzZs2PvvfeOJ554Yszn+5N6khzcsejSoSJXSHLiHYsu5UpbUYAL5Ec/+lH8xm/8Ruy6665x3333bfPr+F4tkkybu0fbfqjIFZIcjrl7tO250mYU4MLYsGFDHHnkkTFjxoz4yle+8qJey6FCkmlz92ibDxW5QpLDM3ePtjlX2o4CXBDPPfdcnHLKKTFlypT413/91xf9eg4Vkkybu0fbeqjIFZIcrrl7tK250gUU4IL4wAc+EFVVxcknnxwrV64c5aA4VEgybe4ebeuhIldIcrjm7tG25koXUIAL4g1veMNWf+JmVQ0+KocKSabN3aNtPVTkCkkO19w92tZc6QIKcIdxqJBk2tw96lCpkSskmTZ3j8qV5lCAO4xDhSTT5u5Rh0qNXCHJtLl7VK40hwLcYRwqJJk2d486VGrkCkmmzd2jcqU5FOAO41AhybS5e9ShUiNXSDJt7h6VK82hAHcYhwpJps3dow6VGrlCkmlz96hcaQ4FuMM4VEgybe4edajUyBWSTJu7R+VKcyjAHcahQpJpc/eoQ6VGrpBk2tw9KleaQwHuMA4Vkkybu0cdKjVyhSTT5u5RudIcCnCHcaiQZNrcPepQqZErJJk2d4/KleZQgDuMQ4Uk0+buUYdKjVwhybS5e1SuNIcC3GEcKiSZNnePOlRq5ApJps3do3KlORTgDuNQIcm0uXvUoVIjV0gybe4elSvNoQB3GIcKSabN3aMOlRq5QpJpc/eoXGkOBbjDOFRIMm3uHnWo1MgVkkybu0flSnMowB3GoUKSaXP3qEOlRq6QZNrcPSpXmkMB7jAOFZJMm7tHHSo1coUk0+buUbnSHApwh3GokGTa3D3qUKmRKySZNnePypXmUIA7jEOFJNPm7lGHSo1cIcm0uXtUrjSHAtxhHCokmTZ3jzpUauQKSabN3aNypTkU4A7jUCHJtLl71KFSI1dIMm3uHpUrzaEAdxiHCkmmzd2jDpUauUKSaXP3qFxpDgW4wzhUSDJt7h51qNTIFZJMm7tH5UpzKMAdxqFCkmlz96hDpUaukGTa3D0qV5pDAe4wgxwqzz85Z1xtevm0XfMoR7Moy/GeR+4edajUDJIrr7py+bi6YP/F42rT/y4P21d9bPm4umDu+eNq078/ZtHPWUzEPHL3qFxpDgW4wyjA7dU8ytEsylIBbhYFuL0qXeVoFmWpAPcPBbjDKMDt1TzK0SzKUgFuFgW4vSpd5WgWZakA9w8FuMMowO3VPMrRLMpSAW4WBbi9Kl3laBZlqQD3DwW4MDZu3Bjnn39+7LXXXrH99tvHYYcdFrfeeus2vZYC3F7NoxzNoiwV4MFpKlcU4LJUusrRLMpSAe4fCnBhnHHGGTFlypRYtGhRrFixIg4//PCYMmVK3H777QO/lgLcXs2jHM2iLBXgwWkqVxTgslS6ytEsylIB7h8KcEHcddddUVVVfOQjH9n82IYNG2K//faLww8/fODXU4Dbq3mUo1mUpQI8GE3migJclkpXOZpFWSrA/UMBLojFixfH5MmT48c//vGIxy+99NKoqioee+yxgV5PAW6v5lGOZlGWCvBgNJkrCnBZKl3laBZlqQD3DwW4II499tg48MADRz3+xS9+Maqqii984QsDvZ4C3F7NoxzNoiwV4MFoMlcU4LJUusrRLMpSAe4fCnBBzJs3L44++uhRj993331RVVV88pOf3OrHrl+/PtauXTvCz372s1FVVRxcHR6vq45Les//23tcHevz0TzaolmU5XjP41f35q+6atWqqKoq1qxZM5Hrf8JoMlde/qeLxtXX7/N742rT/y4P25dfsGhcff2+7xlXm/79MYt+zmIi5tH1XOkCCnBBzJ49O0444YRRjz/yyCNRVVVcccUVW/3YJUuWRFVVJMkJcNWqVRO5/icMuUKSZdrWXOkCCnBBjPef1H/lK1+Jv/u7v4s1a9aM+adROb7wJ1arVq0al9ejWXRF8yjH8Z7FmjVrYtWqVfHDH/5wItf/hCFXaBbt1DzKUa50DwW4IMb7e7XGm7Vrfc9CKZhFWZhHOZjFSOQKcjGLsjCPcjCL7qEAF8SiRYu2+NM6ly1bFlU1+E/rHG8sgHIwi7Iwj3Iwi5HIFeRiFmVhHuVgFt1DAS6IO++8M6pq5N/XuHHjxpgzZ07Mnz+/wV9ZjQVQDmZRFuZRDmYxErmCXMyiLMyjHMyieyjAhbFw4cKYMmVKLF68OFasWBFHHHFETJkyJVavXt30L80CKAizKAvzKAezGI1cQQ5mURbmUQ5m0T0U4MLYsGFDLFq0KPbcc8/Ybrvt4tBDD41bbrml6V9WRNQ/EGXJkiWxfv36pn8pvccsysI8ysEsRiNXkINZlIV5lINZdA8FGAAAAADQCxRgAAAAAEAvUIABAAAAAL1AAQYAAAAA9AIFGAAAAADQCxTgnvLVr341FixYEDvuuGPssMMOcdxxx8XXv/71LT5306ZNsWzZsjjggANiu+22i9133z1OPPHEePzxxzc/58tf/nJUVbVF//M//3NYX1ZryZnHunXrtvp7XFVVnH322SOev3Hjxjj//PNjr732iu233z4OO+ywuPXWW4f5ZbWS8Z6F98aLI3dXPf/883HNNdfEa1/72pg5c2bsvvvu8aY3vSnuuOOOUc/13pgY5Eo5yJSykCvlIFMQoQD3kjVr1sT2228f+++/fyxfvjz+6q/+Kl71qlfFTjvtFN/85jdHPPfnP/95HHvssTFjxoz4wAc+ENddd10sX748Fi5cOOLvQ3thGb///e+PlStXjvB73/vesL/EVpE7j2eeeWbU7+3KlSvjzDPPjKqq4rOf/eyI1z3jjDNiypQpsWjRolixYkUcfvjhMWXKlLj99tuH/SW2homYhffGtjPIrvqTP/mTqKoqzjrrrFixYkVcfvnlMXv27JgyZUrcddddI57rvTH+yJVykCllIVfKQabgBRTgHnLiiSfGrFmz4vvf//7mx77zne/EDjvsEKeeeuqI515++eUxderUUW/2X+WFZfy5z31uQn7NXWaQeWyJY445JnbaaafYsGHD5sfuuuuuqKoqPvKRj2x+bMOGDbHffvvF4YcfPr5fQIeYiFl4b2w7ufN49tlnY/r06fHWt751xMf/z//8z+Yj8QW8NyYGuVIOMqUs5Eo5yBS8gALcQ3bcccdYuHDhqMd/+7d/O6ZNmxZPP/10RNT/+cfLX/7yeNvb3hYR9UL46U9/usXX/OVl/JOf/CSeffbZifsCOkbuPLbEd77znZg0aVK8+93vHvH44sWLY/LkyfHjH/94xOOXXnppVFUVjz322Pj84jvGRMzCe2PbyZ3Hz372s6iqKs4777wRz3vmmWdi0qRJccEFF2x+zHtjYpAr5SBTykKulINMwQsowD1k2rRp8c53vnPU4wsXLhzx/SP33ntvVFUVl1xySfzBH/xBTJs2LaqqioMOOij+/d//fcTHvrCMd9hhh6iqKiZPnhxHHXVU3H333UP5mtpM7jy2xEc/+tGoqipuu+22EY8fe+yxceCBB456/he/+MWoqiq+8IUvvPhfeAeZiFl4b2w7g8xj/vz5MXPmzPiHf/iHePTRR+Mb3/hGvPWtb43ddtstHnnkkc3P896YGORKOciUspAr5SBT8AIKcA856KCDYu7cufHcc89tfmzTpk2xzz77RFVV8c///M8REXHjjTdGVVWx2267xf777x/XX399XH/99bH//vvHtGnT4hvf+Mbmj7/jjjvitNNOi+uuuy7+5V/+JS677LLYbbfdYvvtt4+vfe1rQ/8a20TuPLbEb/7mb8Zee+0Vzz///IjH582bF0cfffSo5993331RVVV88pOfHL8voENMxCy8N7adQebx8MMPxyGHHDLih8HMnj171Pd1eW9MDHKlHGRKWciVcpApeAEFuIdcc801UVVVvOtd74r77rsv7r333jj99NNj6tSpUVVVrFy5MiIiPvWpT0VVVTFt2rQR/wnHo48+GlOnTo0zzzwz+XkefvjhmD59eixYsGBCv562kzuPX+XBBx+Mqqrij//4j0f9s9mzZ8cJJ5ww6vFHHnkkqqqKK664Yty/ji4wEbPYEt4beQwyj+9+97vxu7/7u3HeeefFjTfeGJ/4xCdin332iVe/+tUjfiiM98bEIFfKQaaUhVwpB5mCF1CAe8qf/dmfbX7DV1UVv/VbvxUf/vCHo6qquOmmmyIi4nOf+1xUVRVvfOMbR338G9/4xvi1X/u1MT/PGWecEdOmTRvxp20YTc48fpULL7wwqqqKr371q6P+mT+R3HbGexZbw3sjj5x5PPvss/Hrv/7r8Ud/9EcjPvahhx6KqVOnxvnnn7/5Me+NiUOulINMKQu5Ug4yBREKcK/5wQ9+ELfffnvcc889ERHxoQ99KKqqivvuuy8i6v/EpqqqOOOMM0Z97Omnnx677LLLmJ9j8eLFUVXVqB8OgNGMNY9fZc6cOXHAAQds8Z/5npQXx3jOYmt4b+Qz1jy+9KUvRVVVW/x7Fw8++OB4/etfv/l/e29MLHKlHGRKWciVcpApUICxmUMPPTRe+cpXbv5ek5/85CcxderUOPLII0c998gjj4z9999/zNc87bTTYvvttx/1/SsYm1+dxy9z5513RlVVcfHFF2/xYxctWrTFn0q4bNmyqCo/lXBQXswstob3xrbzq/P49Kc/HVVVxc033zzquQceeGDMnz9/8//23hgucqUcZEpZyJVykCn9QwFGRETccMMNUVVVLF++fMTjb37zm2Py5MnxwAMPbH7s/vvvj8mTJ8cf/uEfbn7sqaeeGvWa//3f/x1Tp06NU045ZeJ+4R1la/N4gfe///1RVVV861vf2uI/fyE8f/nvpdu4cWPMmTNnxOLG2LzYWXhvjC9bmsdXv/rVzd/X9cusWbMmJk2aFOecc87mx7w3hodcKQeZUhZypRxkSj9RgHvI6tWr45hjjonLL788/vZv/zbOPvvsmDx5crzpTW8a9XfJ3XfffbHDDjvEXnvtFZdddllcdtllsddee8XLXvay+Pa3v735eW984xvjxBNPjEsuuSSuvfba+OAHPxgzZsyInXfeOe6///5hf4mtYpB5REQ899xzsccee8TrXve65OsuXLgwpkyZEosXL44VK1bEEUccEVOmTInVq1dP1JfSeiZiFt4b284g8zjuuOOiqqp4y1veEtdcc01ceOGFMWvWrJg5c+aon9rpvTH+yJVykCllIVfKQabgBRTgHvKtb30rjj/++HjpS18a2223Xbz61a+Oyy67LDZt2rTF569ZsyaOPfbYmDlzZuy4447x5je/OR566KERz7nyyivjsMMOi1133TWmTJkSe+21V5x11lnx8MMPD+NLajWDzuOWW26JqqriqquuSr7uhg0bYtGiRbHnnnvGdtttF4ceemjccsstE/EldIaJmIX3xrYzyDx+9rOfxcUXXxyvec1rYvr06bHzzjvHSSedFF//+tdHPdd7Y/yRK+UgU8pCrpSDTMELKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF6gAAMAAAAAeoECDAAAAADoBQowAAAAAKAXKMAAAAAAgF7w/wEoGAMpegE9pgAAAABJRU5ErkJggg==\" width=\"800\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f9108532320>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dummy_image = numpy.zeros(mask.shape, dtype=\"float32\")\n",
    "dummy_image[::5,::5] = 1\n",
    "#dummy_image[mask] = -1\n",
    "csr = csr_matrix(pre_csr)\n",
    "dummy_blurred = csr.T.dot(dummy_image.ravel()).reshape(mask.shape)\n",
    "fix, ax = subplots(2,2, figsize=(8,8))\n",
    "ax[0,0].imshow(dummy_image)\n",
    "ax[0,1].imshow(csr.dot(dummy_image.ravel()).reshape(mask.shape))\n",
    "ax[1,1].imshow(csr.T.dot(dummy_image.ravel()).reshape(mask.shape))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 16)"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ax[0,0].set_xlim(964,981)\n",
    "ax[0,0].set_ylim(0,16)\n",
    "ax[0,1].set_xlim(964,981)\n",
    "ax[0,1].set_ylim(0,16)\n",
    "ax[1,1].set_xlim(964,981)\n",
    "ax[1,1].set_ylim(0,16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 3 s, sys: 5.56 s, total: 8.56 s\n",
      "Wall time: 723 ms\n",
      "(1, 31, 0.00036844599621312167, 4.0083979131992555e-05, 2.1620353817016356, 4.873728452382846, 195.4916852921513)\n"
     ]
    }
   ],
   "source": [
    "blured = csr.T.dot(dummy_image.ravel())\n",
    "\n",
    "# Invert this matrix: see https://arxiv.org/abs/1006.0758\n",
    "\n",
    "%time res = linalg.lsmr(csr.T, blured)\n",
    "\n",
    "restored = res[0].reshape(mask.shape)\n",
    "ax[1,0].imshow(restored)\n",
    "ax[1,0].set_xlim(964,981)\n",
    "ax[1,0].set_ylim(0,16)\n",
    "\n",
    "print(res[1:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion of the raytracing part:\n",
    "\n",
    "We are able to simulate the path and the absorption of the photon in the thickness of the detector. \n",
    "Numba helped substentially to make the raytracing calculation much faster. \n",
    "The signal of each pixel is indeed spread on the neighboors, depending on the position of the PONI and this effect can be inverted using sparse-matrix pseudo-inversion.\n",
    "\n",
    "We will now save this sparse matrix to file in order to be able to re-use it in next notebook. But before saving it, it makes sense to spend some time in generating a high quality sparse matrix in throwing thousand rays per pixel in a grid of 32x32."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 5min 36s, sys: 16.6 s, total: 5min 52s\n",
      "Wall time: 5min 57s\n"
     ]
    }
   ],
   "source": [
    "%time pre_csr = thick.calc_csr(32)\n",
    "hq_csr = csr_matrix(pre_csr)\n",
    "from scipy.sparse import save_npz\n",
    "save_npz(\"csr.npz\",hq_csr)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.7 local venv",
   "language": "python",
   "name": "python3.7"
  },
  "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.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
