{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Retinotopy: Predicting the perceptual effects of different visual field maps\n\nEvery computational model needs to assume a mapping between retinal and visual\nfield coordinates. A number of these visual field maps are provided in the\n:py:mod:`~pulse2percept.utils.geometry` module of the utilities subpackage:\n\n*  :py:class:`~pulse2percept.utils.Curcio1990Map`: The [Curcio1990]_ model\n   simply assumes that one degree of visual angle (dva) is equal to 280 um on\n   the retina.\n*  :py:class:`~pulse2percept.utils.Watson2014Map`: The [Watson2014]_ model\n   extends [Curcio1990]_ by recognizing that the transformation between dva and\n   retinal eccentricity is not linear (see Eq. A5 in [Watson2014]_). However,\n   within 40 degrees of eccentricity, the transform is virtually\n   indistuingishable from [Curcio1990]_.\n*  :py:class:`~pulse2percept.utils.Watson2014DisplaceMap`: [Watson2014]_ also\n   describes the retinal ganglion cell (RGC) density at different retinal\n   eccentricities. In specific, there is a central retinal zone where RGC bodies\n   are displaced centrifugally some distance from the inner segments of the\n   cones to which they are connected through the bipolar cells, and thus from\n   their receptive field (see Eq. 5 [Watson2014]_).\n\nAll of these visual field maps follow the\n:py:class:`~pulse2percept.utils.VisualFieldMap` template.\nThis means that they have to specify a ``dva2ret`` method, which transforms\nvisual field coordinates into retinal coordinates, and a complementary \n``ret2dva`` method.\n\n## Visual field maps\n\nTo appreciate the difference between the available visual field maps, let us\nlook at a rectangular grid in visual field coordinates:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pulse2percept as p2p\nimport matplotlib.pyplot as plt\n\ngrid = p2p.utils.Grid2D((-50, 50), (-50, 50), step=5)\ngrid.plot(style='scatter')\nplt.xlabel('x (degrees of visual angle)')\nplt.ylabel('y (degrees of visual angle)')\nplt.axis('square')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Such a grid is typically created during a model's ``build`` process and\ndefines at which (x,y) locations the percept is to be evaluated.\n\nHowever, these visual field coordinates are mapped onto different retinal\ncoordinates under the three visual field maps:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "transforms = [p2p.utils.Curcio1990Map,\n              p2p.utils.Watson2014Map,\n              p2p.utils.Watson2014DisplaceMap]\nfig, axes = plt.subplots(ncols=3, sharey=True, figsize=(13, 4))\nfor ax, transform in zip(axes, transforms):\n    grid.plot(transform=transform().dva2ret, style='cell', ax=ax)\n    ax.set_title(transform().__class__.__name__)\n    ax.set_xlabel('x (microns)')\n    ax.set_ylabel('y (microns)')\n    ax.axis('equal')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Whereas the [Curcio1990]_ map applies a simple scaling factor to the visual\nfield coordinates, [Watson2014]_ uses a nonlinear transform.\nOne thing to note is the RGC displacement zone in the third panel, which might\nlead to distortions in the fovea.\n\n## Perceptual distortions\n\nThe perceptual consequences of these visual field maps become apparent when\nused in combination with an implant.\n\nFor this purpose, let us create an :py:class:`~pulse2percept.models.AlphaAMS`\ndevice on the fovea and feed it a suitable stimulus:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "implant = p2p.implants.AlphaAMS(stim=p2p.stimuli.LogoUCSB())\nimplant.stim"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We can easily switch out the visual field maps by passing a ``retinotopy``\nattribute to :py:class:`~pulse2percept.models.ScoreboardModel` (by default,\nthe scoreboard model will use [Curcio1990]_):\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "fig, axes = plt.subplots(ncols=3, sharey=True, figsize=(13, 4))\nfor ax, transform in zip(axes, transforms):\n    model = p2p.models.ScoreboardModel(xrange=(-6, 6), yrange=(-6, 6),\n                                       retinotopy=transform())\n    model.build()\n    model.predict_percept(implant).plot(ax=ax)\n    ax.set_title(transform().__class__.__name__)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Whereas the left and center panel look virtually identical, the rightmost\npanel predicts a rather striking perceptual effect of the RGC displacement\nzone.\n\n## Creating your own visual field map\n\nTo create your own visual field map, you need to subclass the\n:py:class:`~pulse2percept.utils.VisualFieldMap` template and provide your own\n``dva2ret`` and ``ret2dva`` methods.\nFor example, the following class would (wrongly) assume that retinal\ncoordinates are identical to visual field coordinates:\n\n```python\nclass MyVisualFieldMap(p2p.utils.VisualFieldMap):\n\n    def dva2ret(self, xdva, ydva):\n        return xdva, ydva\n\n    def ret2dva(self, xret, yret):\n        return xret, yret\n```\nTo use it with a model, you need to pass ``retinotopy=MyVisualFieldMap()``\nto the model's constructor.\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}