{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 7: Modeling the visual field (with FilterNet)\n",
    "\n",
    "FilterNet is a part of the BMTK that simulates the effects of visual input onto cells in the LGN. It uses LGNModel as a backend, which uses neural-filters to simulate firing rates and spike-trains one may expect given a stimulus on (especially mouse) visual field. FilterNet supports a number of visual stimuli including static-graitings, moving-graiting, full-field flashes, static images and even movies.\n",
    "\n",
    "Filternet uses a [__linear-nonlinear-Poisson (lnp) model__](https://en.wikipedia.org/wiki/Linear-nonlinear-Poisson_cascade_model), using a spatial-temporal linear filter to convert a movie into a series of spike trains.\n",
    "\n",
    "![FilterNet LNP](_static/_tutorial_images/bmtk_lnp.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A simple example\n",
    "\n",
    "#### 1. Building the network\n",
    "\n",
    "FilterNet is modeled on the mammalian LGN and expects a two-dimensional network of cells that can be mapped onto the receptive field. The default parameters of the filters are based on experimental recordings from the mouse LGN [Durant et. al, 2016, Billeh et. al, 2019] As our starting case we will have a simple network of 20 ON cells and 20 OFF cells with coordinates that correspond to the 240x120 movie we will be playing against it.\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from bmtk.builder import NetworkBuilder\n",
    "\n",
    "\n",
    "net = NetworkBuilder('lgn')  # Initialize network called 'lgn'\n",
    "\n",
    "net.add_nodes(  # add 10 simple OFF cells\n",
    "    N=20, \n",
    "    model_type='virtual',\n",
    "    model_template='lgnmodel:tOFF_TF15',\n",
    "    x=np.random.uniform(0.0, 240.0, 20),\n",
    "    y=np.random.uniform(0.0, 120.0, 20),\n",
    "    spatial_size=1.0,\n",
    "    dynamics_params='tOFF_TF15.json'\n",
    ")\n",
    "\n",
    "net.add_nodes(  # add 10 simple ON cells\n",
    "    N=20, \n",
    "    model_type='virtual',\n",
    "    model_template='lgnmodel:tON',\n",
    "    x=np.random.uniform(0.0, 240.0, 20),\n",
    "    y=np.random.uniform(0.0, 120.0, 20),\n",
    "    spatial_size=1.0,\n",
    "    dynamics_params='tON_TF8.json'\n",
    ")\n",
    "\n",
    "# Create and save the network\n",
    "net.build()\n",
    "net.save_nodes(output_dir='sim_ch07/network')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2. Setting up the simulation enviornment\n",
    "\n",
    "The following will create the necessarcy python and configuration files to run a simulation, all placed in the __sim_ch07__ directory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:root:Configuration file /home/ping/bmtk_change/bmtk/docs/tutorial/sim_ch07/config.json already exists, skipping. Please delete existing file, use a different name, or use overwrite_config=True.\n",
      "WARNING:root:Configuration file /home/ping/bmtk_change/bmtk/docs/tutorial/sim_ch07/circuit_config.json already exists, skipping. Please delete existing file, use a different name, or use overwrite_config=True.\n",
      "WARNING:root:Configuration file /home/ping/bmtk_change/bmtk/docs/tutorial/sim_ch07/simulation_config.json already exists, skipping. Please delete existing file, use a different name, or use overwrite_config=True.\n"
     ]
    }
   ],
   "source": [
    "from bmtk.utils.sim_setup import build_env_filternet\n",
    "\n",
    "build_env_filternet(\n",
    "    base_dir='sim_ch07', \n",
    "    network_dir='sim_ch07/network', \n",
    "    tstop=3000.0,                   # run the simulation for 3 seconds \n",
    "    include_examples=True)          # includes example model files which we'll use in this tutorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The most important file that we will want to change is the __simulation_config.json__ which contains information about the simulation. \n",
    "\n",
    "By default the config file does not contain any stimulus so the firing rate will simply be at the baseline firing rate. To change this and play a certain type of movie/stimulus, which here we'll use a movie with a bright flash full-screen for an extend period of time. To do so open up simulation_config.json and add the following inputs section:\n",
    "\n",
    "```json\n",
    "{\n",
    "    \"inputs\": {\n",
    "        \"full_field_flash\": {\n",
    "            \"input_type\": \"movie\",\n",
    "            \"module\": \"full_field_flash\",\n",
    "            \"row_size\": 120,\n",
    "            \"col_size\": 240,\n",
    "            \"t_on\": 1000.0,\n",
    "            \"t_off\": 2000.0,\n",
    "            \"max_intensity\": 20.0,\n",
    "            \"frame_rate\": 1000.0\n",
    "        }\n",
    "    }\n",
    "}\n",
    "```\n",
    "\n",
    "This will create a flash on the screen from 1 to 2 seconds. Make sure the frame rate is sufficient for the temporal fluctuations in the LGN filters (e.g., 1000.0)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3 Running the simulation\n",
    "\n",
    "You can run the simulation from the command line by going to the __sim_ch07__ directory and running:\n",
    "```bash\n",
    "  $ python run_filternet.py simulation_config.json\n",
    "```\n",
    "\n",
    "And if you have MPI installed with access to a cluster you can parallize much of the process\n",
    "\n",
    "```bash\n",
    " $ mpirun -np $NCORES python run_filternet.py simulation_config.json\n",
    "```\n",
    "\n",
    "or we can run it directly"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-02-10 13:38:44,402 [WARNING] Directory /home/ping/bmtk_change/bmtk/docs/tutorial/sim_ch07/output already exists.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:FilterNetIOUtils:Directory /home/ping/bmtk_change/bmtk/docs/tutorial/sim_ch07/output already exists.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-02-10 13:38:44,403 [INFO] Created log file\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:FilterNetIOUtils:Created log file\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-02-10 13:39:03,776 [INFO] Building cells.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:FilterNetIOUtils:Building cells.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-02-10 13:39:04,649 [INFO] Evaluating rates.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:FilterNetIOUtils:Evaluating rates.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-02-10 13:39:11,702 [INFO] Done.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:FilterNetIOUtils:Done.\n"
     ]
    }
   ],
   "source": [
    "from bmtk.simulator import filternet\n",
    "\n",
    "config = filternet.Config.from_json('sim_ch07/config.json')\n",
    "config.build_env()\n",
    "net = filternet.FilterNetwork.from_config(config)\n",
    "sim = filternet.FilterSimulator.from_config(config, net)\n",
    "sim.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will generate a number of files in the __output__ directory, including __rates.csv__ that contains the firing rate of each cell over the duration of the simulation, and __spikes.h5__ in which the rates have been converted to spike times using a nonhomogeneous Poisson process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": " ",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from bmtk.analyzer.spike_trains import plot_raster\n",
    "\n",
    "_ = plot_raster(config_file='sim_ch07/config.json', group_by='model_template')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As expected, the flash from 1 to 2 seconds caused the ON cells to temporary increase firing while also supressing the OFF cells."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running different types of Stimuli\n",
    "\n",
    "Currently FilterNet allows for a number of different types of custom and pre-alligned type of stimuli. To change the type of stimuli requires updating the _inputs_ section in the __simulation_config.json__ file like above. \n",
    "\n",
    "\n",
    "#### movie\n",
    "\n",
    "Allows playing a custom movie file in the form of a three-dimension matrix saved in a [npy file](https://docs.scipy.org/doc/numpy/reference/generated/numpy.save.html). \n",
    "\n",
    "_example_\n",
    "```json\n",
    "{\n",
    "  \"inputs\": {\n",
    "    \"movie_input\": {\n",
    "      \"input_type\": \"movie\",\n",
    "      \"module\": \"movie\",\n",
    "      \"data_file\": \"/path/to/my/movie.npy\",\n",
    "      \"frame_rate\": 1000.0\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```\n",
    "\n",
    "_parameters_\n",
    "* __movie__ - Link to a 3-dimensional (time, x, y) matrix representing a movie\n",
    "* __frame_rate__ - frames per second\n",
    "\n",
    "\n",
    "#### grating\n",
    "\n",
    "Plays a drifting grating across the screen\n",
    "\n",
    "_example_\n",
    "```json\n",
    "{\n",
    "  \"inputs\": {\n",
    "    \"LGN_spikes\": {\n",
    "      \"input_type\": \"movie\",\n",
    "      \"module\": \"graiting\",\n",
    "      \"row_size\": 120,\n",
    "      \"col_size\": 240,\n",
    "      \"gray_screen_dur\": 0.5,\n",
    "      \"cpd\": 0.04,\n",
    "      \"temporal_f\": 4.0,\n",
    "      \"contrast\": 0.8,\n",
    "      \"theta\": 45.0,\n",
    "      \"phase\": 0.0,\n",
    "      \"frame_rate\": 1000.0\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```\n",
    "\n",
    "_parameters_\n",
    "* __row_size__, __col_size__ - width and heigth dimensions of screen in pixels.\n",
    "* __grapy_screen_dur__ - displays an optional gray screen for a number of seconds before the grating starts. (default: 0)\n",
    "* __cpd__ - spatial frequncy represented as cycles per degree. (default: 0.05)\n",
    "* __temporal_f__ - temporal frequency in Hz. (default: 4.0)\n",
    "* __theta__: orientation angle, in degrees (default: 45.0)\n",
    "* __phase__: temporal phase, in degrees (default: 0.0)\n",
    "* __contrast__: the maximum constrast, must be between 0 and 1.0 (default: 1.0)\n",
    "\n",
    "\n",
    "#### full field flash\n",
    "\n",
    "Creates a bright (or dark) flash on a gray screen for a limited number of seconds\n",
    "\n",
    "_example_\n",
    "```json\n",
    "{\n",
    "    \"inputs\": {\n",
    "        \"full_field_flash\": {\n",
    "            \"input_type\": \"movie\",\n",
    "            \"module\": \"full_field_flash\",\n",
    "            \"row_size\": 120,\n",
    "            \"col_size\": 240,\n",
    "            \"t_on\": 1000.0,\n",
    "            \"t_off\": 2000.0,\n",
    "            \"max_intensity\": 20.0\n",
    "            \"frame_rate\": 1000.0\n",
    "        }\n",
    "    }\n",
    "}\n",
    "```\n",
    "\n",
    "_parameters_\n",
    "* __row_size__, __col_size__ - width and heigth dimensions of screen in pixels.\n",
    "* __t_on__ - time (ms) from the beginning on when to start the flash\n",
    "* __t_off__ - length (ms) of flash\n",
    "* __max_intensity__ - intensity of screen during flash (>0.0 is brighter, <0.0 is darker) compared to gray screen.\n",
    "\n",
    "\n",
    "\n",
    "#### looming\n",
    "\n",
    "Creates a spreading black field originating from the center.\n",
    "\n",
    "_example_\n",
    "\n",
    "\n",
    "```json\n",
    "{\n",
    "  \"inputs\": {\n",
    "    \"LGN_spikes\": {\n",
    "      \"input_type\": \"movie\",\n",
    "      \"module\": \"looming\",\n",
    "      \"row_size\": 120,\n",
    "      \"col_size\": 240,\n",
    "      \"frame_rate\": 1000.0,\n",
    "      \"gray_screen_dur\": 0.5,\n",
    "      \"t_looming\": 1.0\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```\n",
    "\n",
    "_parameters_\n",
    "* __row_size__, __col_size__ - width and heigth dimensions of screen in pixels.\n",
    "* __frame_rate__ - frames per second\n",
    "* __gray_screen_dur__ - durating of initial grey screen (seconds)\n",
    "* __t_looming__ - time of looming movie (seconds).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Units\n",
    "\n",
    "#### simple ON/OFF cells\n",
    "These are cells that respond by either increasing or decreasing firing rates to brightness changes in their receptive fields. They are also the basis for more advanced cell types. When building a network you can set a cell or set of cells with ```model_template='lgnmodel:tON'``` or ```model_template='lgnmodel:tOFF'``` during the call to ```add_nodes(...)```. There are also a number of special types (```tON_TF8, sON_TF1, sON_TF2, sON_TF4, sON_TF8, sON_TF15, tOFF_TF1, tOFF_TF2, tOFF_TF4, tOFF_TF8, tOFF_TF15, sOFF_TF1, sOFF_TF2, sOFF_TF4, sOFF_TF8, sOFF_TF15```) which have properties that have been pre-calculated using electrophysiological recordings from the mouse LGN (Durant et. al 2016, Billeh et. al 2019).\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A combination of a Gaussian spatial filter and double cosine temporal filter are used to filter the receptive field.\n",
    "\n",
    "
\n",
    "  
\n",
    "  \n",
    "  
\n",
    "  
\n",
    "  \n",
    "  
\n",
    "
"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### parameters\n",
    "\n",
    "The parameters for the cell are by default set in the .json file pointed to by the ```dynamic_params``` parameter are calculated from extracellular recordings from the mouse LGN (Durand et. al 2016, Billeh et. al, 2019). These parameters may be over ridden to be unique to each cell by passing in a list of values in the call to ```add_nodes```. For example to given every cell a randomized spontaneous firing rate using the spont_fr variable (see below) you would just add the following:\n",
    "```python\n",
    "net.add_nodes(  # add 10 simple ON cells\n",
    "    N=20, \n",
    "    model_template='lgnmodel:tON',\n",
    "    spont_fr=np.random.uniform(1.0, 2.0, 20) # create a rand list of 20 firing rates between 1 to 2 Hz\n",
    "    ....\n",
    ")\n",
    "```\n",
    "\n",
    "__*spatial filter parameters*__\n",
    "* *spatial_size*: (float, float) - the row and column spread of the gaussian filter, default: (1.0, 1.0)\n",
    "* *spatial_rotation*: float - the gaussian rotation in degrees, default: 0.0\n",
    "\n",
    "![](_static/_tutorial_images/filternet_sfilt_params.png)\n",
    "\n",
    "__*temporal filter parameters*__\n",
    "* *weights*: (float, float) - used to control amplitude of the the two bumps in cosine filter. The first value is used to set the major bump and must have weights[0] > weights[1]\n",
    "* *kpeaks*: (float, float) - used to control the spread of the two bumps, the first value for the initial larger peak and the second for the smaller peak. The second peak must have a greater spread than the first.\n",
    "* *delays*: (float, float) - controls the delays of the first and second bumps\n",
    "\n",
    "![](_static/_tutorial_images/filternet_tfilt_params.png)\n",
    "\n",
    "__*non-linear filter parameters*__\n",
    "* *spont_fr*: float - The spontaneous/resting firing rate of the cell. ON/OFF cells use the function $Heaviside(s+spont\\_fr)*(s+spont\\_fr)$ to filter the firing rate following the spatialtemporal filter. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Spatial Mixture cells\n",
    "These types of units combine two spatial filters to create a [Mexican hat](https://en.wikipedia.org/w/index.php?title=Mexican_Hat_Function) filter so the spatial filter responds to a bright area surrounded by darkness (or vice-versa). To use set ```model_template='lgnmodel:LGNOnOFFCell'```\n",
    "\n",
    "__*parameters*__\n",
    "* *sigma_on*: (float, float) - spread of the ON region of the spatial filter.\n",
    "* *sigma_off*: (float, float) - spread of the OFF region of the spatial filter.\n",
    "\n",
    "The temporal filter is set using the same parameters as a simple cell. \n",
    "\n",
    "\n",
    "### Dual filter cells (sONtOFF, sONsOFF)\n",
    "\n",
    "These units combine two simple linear filters into one, and can be set using either ```model_template='lgnmodel:sONsOFF'``` or ```model_template='lgnmodel:sONtOFF'```, both with a non-dominate spatial-off filter.\n",
    "\n",
    "__*parameters*__\n",
    "\n",
    "The parameters for the first filter (tOFF or sOFF subunit) is set using the same parameters as a simple cell. You can set non-dominate secondary subunit parameters in two ways:\n",
    "1. Adding parameter *non_dom_params* that points to a second dynamics_params .json file. (eg. ```non_dom_params='sON_TF8.json'```)\n",
    "2. add the postfix *_non_dom* to the specified params (eg. weights_non_dom, kpeaks_non_dom, delays_non_dom)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
