class SquareImplant(ProsthesisSystem):
    def __init__(self,
                 e_num_side,
                 e_radius,
                 spacing=None,
                 side_size=None,
                 stim=None,
                 eye='RE',
                 name=None):
        """
            e_num_side : the number of electrodes in the squares implant's side
            e_radius   : the radius of each electrode in the square implant [microns]
            spacing    : the spacing between to electrodes in the squares implant's [microns]
            side_size  : the size of the squares implant's side in [microns]
            stim       : stimuli signal for each electrode [one dimensional array]
            eye        : the eye where it is implanted ['RE', 'LE']
            name       : name of the implant [string]
        """

        # if there is no name assigned the derive it from the desired characteristics of the implant
        if name is None:
            self.name = f"e_num_side={e_num_side}-e_radius={e_radius}-spacing={spacing}-side_size={side_size}"
        else:
            self.name = name

        # if side size is set the derive the spacing between each electrod from it
        if side_size is not None:
            spacing = (side_size - 2 * e_radius) / (e_num_side - 1)
        elif spacing is None:
            raise Exception(
                "Provide a 'spacing' or 'side_size' parameter in microns")

        if spacing < 2 * e_radius:
            warnings.warn(
                'Either the electrode radius (e_radius) is too big or the side size (side_size) '
                + 'is too small and there is electrode overlap',
                stacklevel=2)

        self.earray = ElectrodeGrid((e_num_side, e_num_side),
                                    x=0,
                                    y=0,
                                    z=0,
                                    rot=0,
                                    r=e_radius,
                                    spacing=spacing,
                                    etype=DiskElectrode,
                                    names=('A', '1'))
        self.stim = stim
        self.eye = eye

    # plot implant on an axon map
    def plot_on_axon_map(self,
                         annotate_implant=False,
                         annotate_quadrants=True,
                         ax=None):
        if ax is None:
            _, ax = plt.subplots(figsize=(10, 10))
        AxonMapModel().plot(annotate=annotate_quadrants, ax=ax)
        self.earray.plot(annotate=annotate_implant, ax=ax)
    def __init__(self,
                 e_num_side,
                 e_radius,
                 spacing=None,
                 side_size=None,
                 stim=None,
                 eye='RE',
                 name=None):
        """
            e_num_side : the number of electrodes in the squares implant's side
            e_radius   : the radius of each electrode in the square implant [microns]
            spacing    : the spacing between to electrodes in the squares implant's [microns]
            side_size  : the size of the squares implant's side in [microns]
            stim       : stimuli signal for each electrode [one dimensional array]
            eye        : the eye where it is implanted ['RE', 'LE']
            name       : name of the implant [string]
        """

        # if there is no name assigned the derive it from the desired characteristics of the implant
        if name is None:
            self.name = f"e_num_side={e_num_side}-e_radius={e_radius}-spacing={spacing}-side_size={side_size}"
        else:
            self.name = name

        # if side size is set the derive the spacing between each electrod from it
        if side_size is not None:
            spacing = (side_size - 2 * e_radius) / (e_num_side - 1)
        elif spacing is None:
            raise Exception(
                "Provide a 'spacing' or 'side_size' parameter in microns")

        if spacing < 2 * e_radius:
            warnings.warn(
                'Either the electrode radius (e_radius) is too big or the side size (side_size) '
                + 'is too small and there is electrode overlap',
                stacklevel=2)

        self.earray = ElectrodeGrid((e_num_side, e_num_side),
                                    x=0,
                                    y=0,
                                    z=0,
                                    rot=0,
                                    r=e_radius,
                                    spacing=spacing,
                                    etype=DiskElectrode,
                                    names=('A', '1'))
        self.stim = stim
        self.eye = eye
Exemple #3
0
def test_ProsthesisSystem_reshape_stim(rot, gtype, n_frames):
    implant = ProsthesisSystem(ElectrodeGrid((10, 10), 30, rot=rot,
                                             type=gtype))
    # Smoke test the automatic reshaping:
    n_px = 21
    implant.stim = ImageStimulus(np.ones((n_px, n_px, n_frames)).squeeze())
    npt.assert_equal(implant.stim.data.shape, (implant.n_electrodes, 1))
    npt.assert_equal(implant.stim.time, None)
    implant.stim = VideoStimulus(np.ones((n_px, n_px, 3 * n_frames)),
                                 time=2 * np.arange(3 * n_frames))
    npt.assert_equal(implant.stim.data.shape,
                     (implant.n_electrodes, 3 * n_frames))
    npt.assert_equal(implant.stim.time, 2 * np.arange(3 * n_frames))

    # Verify that a horizontal stimulus will always appear horizontally, even if
    # the device is rotated:
    data = np.zeros((50, 50))
    data[20:-20, 10:-10] = 1
    implant.stim = ImageStimulus(data)
    model = ScoreboardModel(xrange=(-1, 1),
                            yrange=(-1, 1),
                            rho=30,
                            xystep=0.02)
    model.build()
    percept = label(model.predict_percept(implant).data.squeeze().T > 0.2)
    npt.assert_almost_equal(regionprops(percept)[0].orientation, 0, decimal=1)
Exemple #4
0
def test_ProsthesisSystem_deactivate():
    implant = ProsthesisSystem(ElectrodeGrid((10, 10), 30))
    implant.stim = np.ones(implant.n_electrodes)
    electrode = 'A3'
    npt.assert_equal(electrode in implant.stim.electrodes, True)
    implant.deactivate(electrode)
    npt.assert_equal(implant[electrode].activated, False)
    npt.assert_equal(electrode in implant.stim.electrodes, False)
def test_ElectrodeGrid___get_item__():
    grid = ElectrodeGrid((2, 4), names=('C', '3'))
    npt.assert_equal(grid[0], grid['C3'])
    npt.assert_equal(grid[0, 0], grid['C3'])
    npt.assert_equal(grid[1], grid['C4'])
    npt.assert_equal(grid[0, 1], grid['C4'])
    npt.assert_equal(grid[['C3', 1, (0, 2)]],
                     [grid['C3'], grid['C4'], grid['C5']])
def test_ElectrodeGrid___get_item__(gtype):
    grid = ElectrodeGrid((2, 4), 20, names=('A', '1'), type=gtype,
                         etype=DiskElectrode, r=20)
    npt.assert_equal(grid[0], grid['A1'])
    npt.assert_equal(grid[0, 0], grid['A1'])
    npt.assert_equal(grid[1], grid['A2'])
    npt.assert_equal(grid[0, 1], grid['A2'])
    npt.assert_equal(grid[['A1', 1, (0, 2)]],
                     [grid['A1'], grid['A2'], grid['A3']])
Exemple #7
0
def test_ProsthesisSystem_stim():
    implant = ProsthesisSystem(ElectrodeGrid((13, 13), 20))
    stim = Stimulus(np.ones((13 * 13 + 1, 5)))
    with pytest.raises(ValueError):
        implant.stim = stim

    # Deactivated electrodes cannot receive stimuli:
    implant.deactivate('H4')
    npt.assert_equal(implant['H4'].activated, False)
    implant.stim = {'H4': 1}
    npt.assert_equal('H4' in implant.stim.electrodes, False)

    implant.deactivate('all')
    npt.assert_equal(not implant.stim.data, True)
    implant.activate('all')
    implant.stim = {'H4': 1}
    npt.assert_equal('H4' in implant.stim.electrodes, True)
Exemple #8
0
def test_ProsthesisSystem_stim():
    implant = ProsthesisSystem(ElectrodeGrid((13, 13), 20))
    stim = Stimulus(np.ones((13 * 13 + 1, 5)))
    with pytest.raises(ValueError):
        implant.stim = stim

    # color mapping
    stim = np.zeros((13 * 13, 5))
    stim[84, 0] = 1
    stim[98, 2] = 2
    implant.stim = stim
    plt.cla()
    ax = implant.plot(stim_cmap='hsv')
    plt.colorbar()
    npt.assert_equal(len(ax.collections), 1)
    npt.assert_equal(ax.collections[0].colorbar.vmax, 2)
    npt.assert_equal(ax.collections[0].cmap(ax.collections[0].norm(1)),
                     (0.0, 1.0, 0.9647031631761764, 1))
    # make sure default behaviour unchanged
    plt.cla()
    ax = implant.plot()
    plt.colorbar()
    npt.assert_equal(len(ax.collections), 1)
    npt.assert_equal(ax.collections[0].colorbar.vmax, 1)
    npt.assert_equal(ax.collections[0].cmap(ax.collections[0].norm(1)),
                     (0.993248, 0.906157, 0.143936, 1))

    # Deactivated electrodes cannot receive stimuli:
    implant.deactivate('H4')
    npt.assert_equal(implant['H4'].activated, False)
    implant.stim = {'H4': 1}
    npt.assert_equal('H4' in implant.stim.electrodes, False)

    implant.deactivate('all')
    npt.assert_equal(not implant.stim.data, True)
    implant.activate('all')
    implant.stim = {'H4': 1}
    npt.assert_equal('H4' in implant.stim.electrodes, True)
-  An electrode type ``etype``, which must be a subclass of
   :py:class:`~pulse2percept.implants.Electrode`. By default,
   :py:class:`~pulse2percept.implants.PointSource` is chosen.
-  Any additional parameters that should be passed to the
   :py:class:`~pulse2percept.implants.Electrode` constructor, such as a radius
   ``r`` for :py:class:`~pulse2percept.implants.DiskElectrode`.

Let's say we want to create a 2x3 rectangular grid of
:py:class:`~pulse2percept.implants.PointSource` objects, each electrode spaced
500 microns apart, and the whole grid should be centered over the fovea:

"""
# sphinx_gallery_thumbnail_number = 3
from pulse2percept.implants import ElectrodeGrid

grid = ElectrodeGrid((2, 3), 500)

##############################################################################
# We can access individual electrodes by indexing into ``grid``:
#
# The first electrode:

grid[0]

##############################################################################
# The first electrode by name:

grid['A1']

##############################################################################
# Accessing the x-coordinate of the first electrode:
def test_ElectrodeGrid_get_params(gtype):
    # When the electrode_type is 'DiskElectrode'
    # test the default value
    egrid = ElectrodeGrid((2, 3), 40, type=gtype, etype=DiskElectrode, r=20)
    npt.assert_equal(egrid.shape, (2, 3))
    npt.assert_equal(egrid.type, gtype)
def test_ElectrodeGrid(gtype):
    # Must pass in tuple/list of (rows, cols) for grid shape:
    with pytest.raises(TypeError):
        ElectrodeGrid("badinstantiation")
    with pytest.raises(TypeError):
        ElectrodeGrid(OrderedDict({'badinstantiation': 0}))
    with pytest.raises(ValueError):
        ElectrodeGrid([0], 10)
    with pytest.raises(ValueError):
        ElectrodeGrid([1, 2, 3], 10)
    with pytest.raises(TypeError):
        ElectrodeGrid({'1': 2}, 10)

    # Must pass in valid Electrode type:
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, etype=ElectrodeArray)
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, etype="foo")

    # Must pass in valid Orientation value:
    with pytest.raises(ValueError):
        ElectrodeGrid((2, 3), 10, type=gtype, orientation="foo")
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, orientation=False)

    # Must pass in radius `r` for grid of DiskElectrode objects:
    gshape = (4, 5)
    spacing = 100
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=13)
    for e in grid.values():
        npt.assert_almost_equal(e.r, 13)
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=np.arange(1, np.prod(gshape) + 1))
    for i, e in enumerate(grid.values()):
        npt.assert_almost_equal(e.r, i + 1)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=10)
    # Number of radii must match number of electrodes
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=[2, 13, 14])
    # Only DiskElectrode needs r, not PointSource:
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, type=gtype, r=10)

    # Must pass in radius `r` for grid of DiskElectrode objects:
    gshape = (4, 5)
    spacing = 100
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=10)
    # Number of radii must match number of electrodes
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=[2, 13])

    # Must pass in valid grid type:
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, type=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type='unknown')

    # Verify spacing is correct:
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=30)
    npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['A2'].x) ** 2 +
                                    (grid['A1'].y - grid['A2'].y) ** 2),
                            spacing)
    npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['B1'].x) ** 2 +
                                    (grid['A1'].y - grid['B1'].y) ** 2),
                            spacing)
    grid = ElectrodeGrid(gshape, spacing, type=gtype, orientation='vertical',
                         etype=DiskElectrode, r=30)
    npt.assert_almost_equal(np.sqrt((grid['B1'].x - grid['B2'].x) ** 2 +
                                    (grid['B1'].y - grid['B2'].y) ** 2),
                            spacing)
    npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['B1'].x) ** 2 +
                                    (grid['A1'].y - grid['B1'].y) ** 2),
                            spacing)

    # A valid 2x5 grid centered at (0, 500):
    x, y = 0, 500
    radius = 30
    egrid = ElectrodeGrid(gshape, spacing, x=x, y=y, type='rect',
                          etype=DiskElectrode, r=radius)
    npt.assert_equal(egrid.shape, gshape)
    npt.assert_equal(egrid.n_electrodes, np.prod(gshape))
    # Make sure different electrodes have different coordinates:
    npt.assert_equal(len(np.unique([e.x for e in egrid.values()])), gshape[1])
    npt.assert_equal(len(np.unique([e.y for e in egrid.values()])), gshape[0])
    # Make sure the average of all x-coordinates == x:
    # (Note: egrid has all electrodes in a dictionary, with (name, object)
    # as (key, value) pairs. You can get the electrode names by iterating over
    # egrid.keys(). You can get the electrode objects by iterating over
    # egrid.values().)
    npt.assert_almost_equal(np.mean([e.x for e in egrid.values()]), x)
    # Same for y:
    npt.assert_almost_equal(np.mean([e.y for e in egrid.values()]), y)

    # Test whether egrid.z is set correctly, when z is a constant:
    z = 12
    egrid = ElectrodeGrid(gshape, spacing, z=z, type=gtype,
                          etype=DiskElectrode, r=radius)
    for i in egrid.values():
        npt.assert_equal(i.z, z)

    # and when every electrode has a different z:
    z = np.arange(np.prod(gshape))
    egrid = ElectrodeGrid(gshape, spacing, z=z, type=gtype,
                          etype=DiskElectrode, r=radius)
    x = -1
    for i in egrid.values():
        npt.assert_equal(i.z, x + 1)
        x = i.z

    # TODO display a warning when rot > 2pi (meaning it might be in degrees).
    # I think we did this somewhere in the old Argus code

    # TODO test rotation, making sure positive angles rotate CCW
    egrid1 = ElectrodeGrid((2, 2), spacing, type=gtype, etype=DiskElectrode,
                           r=radius)
    egrid2 = ElectrodeGrid((2, 2), spacing, rot=np.deg2rad(10), type=gtype,
                           etype=DiskElectrode, r=radius)
    npt.assert_equal(egrid1["A1"].x < egrid2["A1"].x, True)
    npt.assert_equal(egrid1["A1"].y > egrid2["A1"].y, True)
    npt.assert_equal(egrid1["B2"].x > egrid2["B2"].x, True)
    npt.assert_equal(egrid1["B2"].y < egrid2["B2"].y, True)

    # Smallest possible grid:
    egrid = ElectrodeGrid((1, 1), spacing, type=gtype, etype=DiskElectrode,
                          r=radius)
    npt.assert_equal(egrid.shape, (1, 1))
    npt.assert_equal(egrid.n_electrodes, 1)

    # Grid has same size as 'names':
    egrid = ElectrodeGrid((1, 2), spacing, type=gtype, names=('C1', '4'))
    npt.assert_equal(egrid[0, 0], egrid['C1'])
    npt.assert_equal(egrid[0, 1], egrid['4'])

    # Can't have a zero-sized grid:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((0, 0), spacing, type=gtype)
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((5, 0), spacing, type=gtype)

    # Invalid naming conventions:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=[1])
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=[])
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names={1})
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names={})
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names={'1': 2})
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('A', '1', 'A'))
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names=(1, 'A'))
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names=('A', 1))
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('A', '~'))
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('~', 'A'))

    # Test all naming conventions:
    gshape = (2, 3)
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('1', 'A'))
    # print([e for e in egrid.keys()])
    # egrid = ElectrodeGrid(shape, names=('A', '1'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'B1', 'C1', 'A2', 'B2', 'C2'])

    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('1', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['11', '12', '13', '21', '22', '23'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', 'A'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['AA', 'AB', 'AC', 'BA', 'BB', 'BC'])

    # Still starts at A:
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('B', '1'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', '2'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])

    # test unique names
    egrid = ElectrodeGrid(gshape, spacing, type=gtype,
                          names=['53', '18', '00', '81', '11', '12'])
    npt.assert_equal([e for e in egrid.keys()],
                     ['53', '18', '00', '81', '11', '12'])

    # Slots:
    npt.assert_equal(hasattr(egrid, '__slots__'), True)
    npt.assert_equal(hasattr(egrid, '__dict__'), False)
Exemple #12
0
-  Any additional parameters that should be passed to the
   :py:class:`~pulse2percept.implants.Electrode` constructor, such as a radius
   ``r`` for :py:class:`~pulse2percept.implants.DiskElectrode`.

Let's say we want to create a 2x3 rectangular grid of
:py:class:`~pulse2percept.implants.PointSource` objects, each electrode spaced
500 microns apart, and the whole grid should be centered over the fovea:

"""
# sphinx_gallery_thumbnail_number = 3
from pulse2percept.models import AxonMapModel
from numpy import pi
from pulse2percept.implants import DiskElectrode
from pulse2percept.implants import ElectrodeGrid

grid = ElectrodeGrid((2, 3), 500)

##############################################################################
# We can access individual electrodes by indexing into ``grid``:
#
# The first electrode:

grid[0]

##############################################################################
# The first electrode by name:

grid['A1']

##############################################################################
# Accessing the x-coordinate of the first electrode:
Exemple #13
0
def test_ElectrodeGrid():
    # Must pass in tuple/list of (rows, cols) for grid shape:
    with pytest.raises(TypeError):
        ElectrodeGrid("badinstantiation")
    with pytest.raises(TypeError):
        ElectrodeGrid(coll.OrderedDict({'badinstantiation': 0}))
    with pytest.raises(ValueError):
        ElectrodeGrid([0])
    with pytest.raises(ValueError):
        ElectrodeGrid([1, 2, 3])
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), etype=ElectrodeArray)
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), etype="foo")

    # A valid 2x5 grid centered at (0, 500):
    shape = (2, 3)
    x, y = 0, 500
    spacing = 100
    egrid = ElectrodeGrid(shape, x=x, y=y, spacing=spacing)
    npt.assert_equal(egrid.shape, shape)
    npt.assert_equal(egrid.n_electrodes, np.prod(shape))
    npt.assert_equal(egrid.x, x)
    npt.assert_equal(egrid.y, y)
    npt.assert_almost_equal(egrid.spacing, spacing)
    # Make sure different electrodes have different coordinates:
    npt.assert_equal(len(np.unique([e.x for e in egrid.values()])), shape[1])
    npt.assert_equal(len(np.unique([e.y for e in egrid.values()])), shape[0])
    # Make sure the average of all x-coordinates == x:
    # (Note: egrid has all electrodes in a dictionary, with (name, object)
    # as (key, value) pairs. You can get the electrode names by iterating over
    # egrid.keys(). You can get the electrode objects by iterating over
    # egrid.values().)
    npt.assert_almost_equal(np.mean([e.x for e in egrid.values()]), x)
    # Same for y:
    npt.assert_almost_equal(np.mean([e.y for e in egrid.values()]), y)

    # Test whether egrid.z is set correctly, when z is a constant:
    z = 12
    egrid = ElectrodeGrid(shape, z=z)
    npt.assert_equal(egrid.z, z)
    for i in egrid.values():
        npt.assert_equal(i.z, z)

    # and when every electrode has a different z:
    z = np.arange(np.prod(shape))
    egrid = ElectrodeGrid(shape, z=z)
    npt.assert_equal(egrid.z, z)
    x = -1
    for i in egrid.values():
        npt.assert_equal(i.z, x + 1)
        x = i.z

    # TODO display a warning when rot > 2pi (meaning it might be in degrees).
    # I think we did this somewhere in the old Argus code

    # TODO test rotation, making sure positive angles rotate CCW
    egrid1 = ElectrodeGrid(shape=(2, 2))
    egrid2 = ElectrodeGrid(shape=(2, 2), rot=np.deg2rad(10))
    npt.assert_equal(egrid1["A1"].x < egrid2["A1"].x, True)
    npt.assert_equal(egrid1["A1"].y > egrid2["A1"].y, True)
    npt.assert_equal(egrid1["B2"].x > egrid2["B2"].x, True)
    npt.assert_equal(egrid1["B2"].y < egrid2["B2"].y, True)

    # Smallest possible grid:
    egrid = ElectrodeGrid((1, 1))
    npt.assert_equal(egrid.shape, (1, 1))
    npt.assert_equal(egrid.n_electrodes, 1)

    # Can't have a zero-sized grid:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((0, 0))
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((5, 0))

    # Invalid naming conventions:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(shape, names=[1])
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(shape, names=[])
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(shape, names={1})
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(shape, names={})

    # Test all naming conventions:
    egrid = ElectrodeGrid(shape, names=('A', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    egrid = ElectrodeGrid(shape, names=('1', 'A'))
    # print([e for e in egrid.keys()])
    # egrid = ElectrodeGrid(shape, names=('A', '1'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['A1', 'B1', 'C1', 'A2', 'B2', 'C2'])

    # egrid = ElectrodeGrid(shape, names=('A', '1'))
    # npt.assert_equal([e for e in egrid.keys()],
    #                  ['A1', 'A1', 'C1', 'A2', 'B2', 'C2'])
    egrid = ElectrodeGrid(shape, names=('1', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['11', '12', '13', '21', '22', '23'])
    egrid = ElectrodeGrid(shape, names=('A', 'A'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.keys()],
                     ['AA', 'AB', 'AC', 'BA', 'BB', 'BC'])

    # rows and columns start at values other than A or 1
    egrid = ElectrodeGrid(shape, names=('B', '1'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['B1', 'B2', 'B3', 'C1', 'C2', 'C3'])
    egrid = ElectrodeGrid(shape, names=('A', '2'))
    npt.assert_equal([e for e in egrid.keys()],
                     ['A2', 'A3', 'A4', 'B2', 'B3', 'B4'])

    # test unique names
    egrid = ElectrodeGrid(shape, names=['53', '18', '00', '81', '11', '12'])
    npt.assert_equal([e for e in egrid.keys()],
                     ['53', '18', '00', '81', '11', '12'])
Exemple #14
0
def test_ElectrodeGrid_get_params():
    # When the electrode_type is 'DiskElectrode'
    # test the default value
    egrid = ElectrodeGrid((2, 3), 40, etype=DiskElectrode, r=20)
    params = {
        'shape': (2, 3),
        'x': 0,
        'y': 0,
        'z': 0,
        'etype': DiskElectrode,
        'rot': 0,
        'spacing': 40,
        'name_cols': '1',
        'name_rows': 'A',
        'r': 20
    }
    npt.assert_equal(egrid.get_params(), params)
    # test the nondefault value for all the parameters
    egrid = ElectrodeGrid((2, 3),
                          30,
                          x=10,
                          y=20,
                          z=30,
                          rot=10,
                          names=('A', '1'),
                          etype=DiskElectrode,
                          r=20)
    params = {
        'shape': (2, 3),
        'x': 10,
        'y': 20,
        'z': 30,
        'rot': 10,
        'spacing': 30,
        'name_cols': '1',
        'name_rows': 'A',
        'etype': DiskElectrode,
        'r': 20
    }
    npt.assert_equal(egrid.get_params(), params)
    # When the electrode_type is 'PointSource'
    # test the default value
    egrid = ElectrodeGrid((2, 3), 20, etype=PointSource)
    params = {
        'shape': (2, 3),
        'x': 0,
        'y': 0,
        'z': 0,
        'etype': PointSource,
        'rot': 0,
        'spacing': 20,
        'name_cols': '1',
        'name_rows': 'A'
    }
    npt.assert_equal(egrid.get_params(), params)
    # test the nondefault value for all the parameters
    egrid = ElectrodeGrid((2, 3),
                          30,
                          x=10,
                          y=20,
                          z=30,
                          rot=10,
                          names=('A', '1'),
                          etype=PointSource)
    params = {
        'shape': (2, 3),
        'x': 10,
        'y': 20,
        'z': 30,
        'rot': 10,
        'spacing': 30,
        'etype': PointSource,
        'name_cols': '1',
        'name_rows': 'A'
    }
    npt.assert_equal(egrid.get_params(), params)
-  An electrode type ``etype``, which must be a subclass of
   :py:class:`~pulse2percept.implants.Electrode`. By default,
   :py:class:`~pulse2percept.implants.PointSource` is chosen.
-  Any additional parameters that should be passed to the
   :py:class:`~pulse2percept.implants.Electrode` constructor, such as a radius
   ``r`` for :py:class:`~pulse2percept.implants.DiskElectrode`.

Let's say we want to create a 2x3 rectangular grid of
:py:class:`~pulse2percept.implants.PointSource` objects, each electrode spaced
500 microns apart, and the whole grid should be centered over the fovea:

"""
# sphinx_gallery_thumbnail_number = 3
from pulse2percept.implants import ElectrodeGrid

grid = ElectrodeGrid((2, 3), 500)

##############################################################################
# We can access individual electrodes by indexing into ``grid``:
#
# The first electrode:

grid[0]

##############################################################################
# The first electrode by name:

grid['A1']

##############################################################################
# Accessing the x-coordinate of the first electrode:
def test_ElectrodeGrid__make_grid(gtype, orientation):
    # A valid 2x5 grid centered at (0, 500):
    x, y = 0, 500
    radius = 30
    gshape = (4, 6)
    spacing = 100
    egrid = ElectrodeGrid(gshape, spacing, x=x, y=y, type='rect', r=radius,
                          etype=DiskElectrode, orientation=orientation)
    npt.assert_equal(egrid.shape, gshape)
    npt.assert_equal(egrid.n_electrodes, np.prod(gshape))
    # Make sure different electrodes have different coordinates:
    npt.assert_equal(len(np.unique([e.x for e in egrid.electrode_objects])),
                     gshape[1])
    npt.assert_equal(len(np.unique([e.y for e in egrid.electrode_objects])),
                     gshape[0])
    # Make sure the average of all x-coordinates == x:
    # (Note: egrid has all electrodes in a dictionary, with (name, object)
    # as (key, value) pairs. You can get the electrode names by iterating over
    # egrid.keys(). You can get the electrode objects by iterating over
    # egrid.values().)
    npt.assert_almost_equal(np.mean([e.x for e in egrid.electrode_objects]), x)
    # Same for y:
    npt.assert_almost_equal(np.mean([e.y for e in egrid.electrode_objects]), y)

    # Test whether egrid.z is set correctly, when z is a constant:
    z = 12
    egrid = ElectrodeGrid(gshape, spacing, z=z, type=gtype, r=radius,
                          etype=DiskElectrode, orientation=orientation)
    for i in egrid.electrode_objects:
        npt.assert_equal(i.z, z)

    # and when every electrode has a different z:
    z = np.arange(np.prod(gshape))
    egrid = ElectrodeGrid(gshape, spacing, z=z, type=gtype, r=radius,
                          etype=DiskElectrode, orientation=orientation)
    x = -1
    for i in egrid.electrode_objects:
        npt.assert_equal(i.z, x + 1)
        x = i.z

    # TODO test rotation, making sure positive angles rotate CCW
    egrid1 = ElectrodeGrid((2, 2), spacing, type=gtype, etype=DiskElectrode,
                           r=radius, orientation=orientation)
    egrid2 = ElectrodeGrid((2, 2), spacing, rot=10, type=gtype, r=radius,
                           etype=DiskElectrode, orientation=orientation)
    npt.assert_equal(egrid1["A1"].x < egrid2["A1"].x, True)
    npt.assert_equal(egrid1["A1"].y > egrid2["A1"].y, True)
    npt.assert_equal(egrid1["B2"].x > egrid2["B2"].x, True)
    npt.assert_equal(egrid1["B2"].y < egrid2["B2"].y, True)

    # Smallest possible grid:
    egrid = ElectrodeGrid((1, 1), spacing, type=gtype, etype=DiskElectrode,
                          r=radius, orientation=orientation)
    npt.assert_equal(egrid.shape, (1, 1))
    npt.assert_equal(egrid.n_electrodes, 1)

    # Can't have a zero-sized grid:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((0, 0), spacing, type=gtype)
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid((5, 0), spacing, type=gtype)

    # Verify spacing is correct:
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=30, orientation=orientation)
    npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['B1'].x) ** 2 +
                                    (grid['A1'].y - grid['B1'].y) ** 2),
                            spacing)
    npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['A2'].x) ** 2 +
                                    (grid['A1'].y - grid['A2'].y) ** 2),
                            spacing)
    if gtype == 'hex':
        npt.assert_almost_equal(np.sqrt((grid['A1'].x - grid['B2'].x) ** 2 +
                                        (grid['A1'].y - grid['B2'].y) ** 2),
                                spacing)

    # Different spacing in x and y:
    x_spc, y_spc = 50, 100
    grid = ElectrodeGrid(gshape, (x_spc, y_spc), type=gtype, r=30,
                         etype=DiskElectrode, orientation=orientation)
    print(gtype, orientation)
    npt.assert_almost_equal(grid['A2'].x - grid['A1'].x, x_spc)
    npt.assert_almost_equal(grid['B2'].y - grid['A2'].y, y_spc)

    # Grid has same size as 'names':
    egrid = ElectrodeGrid((1, 2), spacing, type=gtype, names=('C1', '4'))
    npt.assert_equal(egrid[0, 0], egrid['C1'])
    npt.assert_equal(egrid[0, 1], egrid['4'])

    # Invalid naming conventions:
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=[1])
    with pytest.raises(ValueError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=[])
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names={1})
    with pytest.raises(TypeError):
        egrid = ElectrodeGrid(gshape, spacing, type=gtype, names={})
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names={'1': 2})
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('A', '1', 'A'))
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names=(1, 'A'))
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, names=('A', 1))
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('A', '~'))
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, names=('~', 'A'))

    # Test all naming conventions:
    gshape = (2, 3)
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('1', 'A'))
    # print([e for e in egrid.keys()])
    # egrid = ElectrodeGrid(shape, names=('A', '1'))
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['A1', 'B1', 'C1', 'A2', 'B2', 'C2'])

    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('1', '1'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['11', '12', '13', '21', '22', '23'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', 'A'))
    # print([e for e in egrid.keys()])
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['AA', 'AB', 'AC', 'BA', 'BB', 'BC'])

    # Still starts at A:
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('B', '1'))
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', '2'))
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['A1', 'A2', 'A3', 'B1', 'B2', 'B3'])
    # Reversal:
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('-A', '1'))
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['B1', 'B2', 'B3', 'A1', 'A2', 'A3'])
    egrid = ElectrodeGrid(gshape, spacing, type=gtype, names=('A', '-1'))
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['A3', 'A2', 'A1', 'B3', 'B2', 'B1'])

    # test unique names
    egrid = ElectrodeGrid(gshape, spacing, type=gtype,
                          names=['53', '18', '00', '81', '11', '12'])
    npt.assert_equal([e for e in egrid.electrode_names],
                     ['53', '18', '00', '81', '11', '12'])
def test_ElectrodeGrid(gtype):
    # Must pass in tuple/list of (rows, cols) for grid shape:
    with pytest.raises(TypeError):
        ElectrodeGrid("badinstantiation")
    with pytest.raises(TypeError):
        ElectrodeGrid(OrderedDict({'badinstantiation': 0}))
    with pytest.raises(ValueError):
        ElectrodeGrid([0], 10)
    with pytest.raises(ValueError):
        ElectrodeGrid([1, 2, 3], 10)
    with pytest.raises(TypeError):
        ElectrodeGrid({'1': 2}, 10)

    # Must pass in valid Electrode type:
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, etype=ElectrodeArray)
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, etype="foo")

    # Must pass in valid Orientation value:
    with pytest.raises(ValueError):
        ElectrodeGrid((2, 3), 10, type=gtype, orientation="foo")
    with pytest.raises(TypeError):
        ElectrodeGrid((2, 3), 10, type=gtype, orientation=False)

    # Must pass in radius `r` for grid of DiskElectrode objects:
    gshape = (4, 5)
    spacing = 100
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=13)
    for (_, e) in grid.electrodes.items():
        npt.assert_almost_equal(e.r, 13)
    grid = ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                         r=np.arange(1, np.prod(gshape) + 1))
    for i, (_, e) in enumerate(grid.electrodes.items()):
        npt.assert_almost_equal(e.r, i + 1)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=10)
    # Number of radii must match number of electrodes
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=[2, 13, 14])
    # Only DiskElectrode needs r, not PointSource:
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, type=gtype, r=10)

    # Must pass in radius `r` for grid of DiskElectrode objects:
    gshape = (4, 5)
    spacing = 100
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=10)
    # Number of radii must match number of electrodes
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type=gtype, etype=DiskElectrode,
                      radius=[2, 13])

    # Must pass in valid grid type:
    with pytest.raises(TypeError):
        ElectrodeGrid(gshape, spacing, type=DiskElectrode)
    with pytest.raises(ValueError):
        ElectrodeGrid(gshape, spacing, type='unknown')

    # Slots:
    npt.assert_equal(hasattr(grid, '__slots__'), True)
    npt.assert_equal(hasattr(grid, '__dict__'), False)