{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

### Quickstart

\n", "To run the code below:\n", "
\n", "
1. Click on the cell to select it.
2. \n", "
3. Press SHIFT+ENTER on your keyboard or press the play button\n", " () in the toolbar above
4. \n", "
\n", "Feel free to create new cells using the plus button\n", "(), or pressing SHIFT+ENTER while this cell\n", "is selected.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this article we demonstrate how Brian can be used to simulate non-neural aspects of the model. This is an idealized model of the smooth pursuit reflex, including two ocular muscles, a moving visual stimulus and spiking neural control.\n", "\n", "This article is adapted from our eLife paper [(Stimberg et al. 2019)](https://elifesciences.org/articles/47314), which includes an interactive version that you can play with [here](https://github.com/brian-team/brian2_paper_examples).\n", "" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from brian2 import *\n", "%matplotlib notebook\n", "seed(79620)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each of the two antagonistic ocular muscles is modelled as a spring of elasticity $k$ and some friction. To simplify, we consider that the eye moves laterally, rather than rotate. If $x$ is the position of the eye with 0 being the center, then the lengths of the springs are $L+x$ and $L-x$. The dynamics of the eye is then given by a second-order differential equation:\n", "$$m\\frac{d^2x}{dt^2} = - k\\left(\\left(L+x\\right)-x_L\\right) + k\\left(\\left(L-x\\right)-x_R\\right) - f\\frac{dx}{dt}$$\n", "or:\n", "$$m\\frac{d^2x}{dt^2} = k(x_L-x_R-2x) - f\\frac{dx}{dt}$$\n", "\n", "We see that $x_0 = \\frac{1}{2}(x_L-x_R)$ is the equilibrium position of the eye. Here we have assumed that spring elasticities are identical. We can rewrite this equation with just two parameters $\\alpha$ and $\\beta$:\n", "$$\\frac{d^2x}{dt^2} = \\alpha(x_0 - x) - \\beta\\frac{dx}{dt}$$\n", "\n", "We will assume that eye position can move between -1 and 1.\n", "\n", "We consider that the resting length is the variable on which motoneurons act. Each spike from a motoneuron produces a waveform of contraction (\"twitch\"), i.e., a change in resting length. We consider that contractions add linearly, and resting lengths relax exponentially. By linearity it follows that we can simply express the action of motoneurons on the equilibrium position of the eye $x_0$, which relaxes exponentially to the center position 0.\n", "\n", "Finally, we consider a visual object that performs a random walk according to an Ornstein–Uhlenbeck with 0 as the central location." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "alpha = (1/(50*ms))**2 # characteristic relaxation time is 50 ms\n", "beta = 1/(50*ms) # friction parameter\n", "tau_muscle = 20*ms # relaxation time of muscle contraction\n", "tau_object = 500*ms # time constant of object movement\n", "\n", "eqs_eye = '''\n", "dx/dt = velocity : 1\n", "dvelocity/dt = alpha*(x0-x)-beta*velocity : 1/second\n", "dx0/dt = -x0/tau_muscle : 1\n", "dx_object/dt = (noise - x_object)/tau_object: 1\n", "dnoise/dt = -noise/tau_object + tau_object**-0.5*xi : 1\n", "'''\n", "eye = NeuronGroup(1, model=eqs_eye, method='euler')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now define two motoneurons, one for each muscle:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "taum = 20*ms\n", "motoneurons = NeuronGroup(2, model= 'dv/dt = -v/taum : 1', threshold = 'v>1',\n", " reset = 'v=0', refractory = 5*ms, method='exact')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The motoneurons project to the eye, and each spike produces a small contraction." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "motosynapses = Synapses(motoneurons, eye, model = 'w : 1', on_pre = 'x0+=w')\n", "motosynapses.connect() # connects all motoneurons to the eye\n", "motosynapses.w = [-0.5,0.5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now implement the sensory neurons, which we simplify by considering spiking neurons which directly respond to light, i.e they represent both photoreceptors and retinal ganglion cells. We model this by giving each neuron a 1d position $x_{neuron}$, the eye a position $x_{eye}$ and object position $x_{object}$ and setting the input current to each neuron as proportional to $\\exp(-((x_{object}-x_{eye}-x_{neuron})/w)^2)$, where $w$ is a receptive field width parameter." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "N = 20\n", "width = 2./N # width of receptive field\n", "gain = 4.\n", "eqs_retina = '''\n", "I = gain*exp(-((x_object-x_eye-x_neuron)/width)**2) : 1\n", "x_neuron : 1 (constant)\n", "x_object : 1 (linked) # position of the object\n", "x_eye : 1 (linked) # position of the eye\n", "dv/dt = (I-(1+gs)*v)/taum : 1\n", "gs : 1 # total synaptic conductance\n", "'''\n", "retina = NeuronGroup(N, model = eqs_retina, threshold = 'v>1', reset = 'v=0', method='exact')\n", "retina.v = 'rand()'\n", "retina.x_eye = linked_var(eye, 'x')\n", "retina.x_object = linked_var(eye, 'x_object')\n", "retina.x_neuron = '-1.0 + 2.0*i/(N-1)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally we connect sensory neurons to motoneurons. Sensory neurons on each hemifield connects to the corresponding motoneuron, with a strength that scales with eccentricity:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "sensorimotor_synapses = Synapses(retina, motoneurons, model = 'w : 1 (constant)', on_pre = 'v+=w')\n", "sensorimotor_synapses.connect(j = 'int(x_neuron_pre > 0)')\n", "sensorimotor_synapses.w = '20*abs(x_neuron_pre)/N_pre'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We record the position of the eye, of the object, and spikes produced by the retina and motoneurons:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "M = StateMonitor(eye, ('x', 'x0', 'x_object'), record = True)\n", "S_retina = SpikeMonitor(retina)\n", "S_motoneurons = SpikeMonitor(motoneurons)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now can run the simulation:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "run(10*second)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we plot the results" ] }, { "cell_type": "code", "execution_count": 9, "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 = $(' ');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", "$(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " fig.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " ' ');\n", " var titletext =$(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext;\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $(' ');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas =$('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas;\n", " this.context = canvas.getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband;\n", " this.rubberband_context = rubberband.getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", "$(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $(' ');\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind];\n", " var tooltip = mpl.toolbar_items[toolbar_ind];\n", " var image = mpl.toolbar_items[toolbar_ind];\n", " var method_name = mpl.toolbar_items[toolbar_ind];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button =$('