-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ENH] Add ICVP Cortical Implant + Test (#542)
* [MNT] Update requirements.txt (#507) * [DOC] Fix gallery thumbnail images (#510) * [FIX] Add check for empty stimulus (#522) * add check for empty stimulus in stim setter for implants * add check for empty np.ndarray * [FIX] Fix electrode numbering annotation in implant.plot() (#523) * fix implant annotation * used zorder * [ENH][REF] Modify Grid2D and VisualFieldMap to support multiple visual areas (#509) * grid interface changes * changed valueerror to runtime error in percept save test * Fix unique time point bug * temp commit to store grid stuff * update requirements * update base.py for new grid class * Grid class now supports multiple layers * refactor layer to be region * Add region_mappings, RetinalMap * Fixed overwriting static attributes * Base class for cortical models * [MNT] Update requirements.txt (#507) * Add tests, made inv transforms optional to overwrite * update with named tuple coordinate grids, and ret_to_dva etc " * refactor everything to ret_to_dva * Update static ret2dva references * removed backwards compatibility for ret2dva * update doc * [REF] Add topography, implants.cortex, and models.cortex submodules (#518) * [MNT] Update requirements.txt (#507) * [DOC] Fix gallery thumbnail images (#510) * add topography module * fix imports * refactor tests for topography module * doc * add epmty submodules for implants.cortex and models.cortex * add orion implant and test * update cortex __init__.py * add orion implant and test * add icvp model and tests, fix implant plotting --------- Co-authored-by: Jacob Granley <jgranley@ucsb.edu> Co-authored-by: isaac hoffman <trsileneh@gmail.com>
- Loading branch information
1 parent
d2f0346
commit c6185f6
Showing
4 changed files
with
158 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
"""`ICVP`""" | ||
import numpy as np | ||
|
||
from ..base import ProsthesisSystem | ||
from ..electrodes import DiskElectrode | ||
from ..electrode_arrays import ElectrodeGrid | ||
|
||
|
||
class ICVP(ProsthesisSystem): | ||
"""Create an ICVP array. | ||
This function creates a ICVP array and places it on the visual cortex | ||
such that the center of the base of the array is at 3D location (x,y,z) given | ||
in microns, and the array is rotated by angle ``rot``, given in degrees. | ||
ICVP (Intracortical Visual Prosthesis Project) is an electrode array containing | ||
16 Parylene-insulated (and 2 uninsulated reference and counter) iridium shaft | ||
electrodes in a 4 column array with 400 um spacing. The electrodes have | ||
a diameter of 15 um at the laser cut. They are inserted either 650 um | ||
or 850 um into the cortex. | ||
""" | ||
# Frozen class: User cannot add more class attributes | ||
__slots__ = ('shape',) | ||
|
||
# 100um diameter at base | ||
# (https://iopscience.iop.org/article/10.1088/1741-2552/abb9bf/pdf) | ||
|
||
# 400um spacing, 4x4 + reference (R) and count (C) | ||
# (https://iopscience.iop.org/article/10.1088/1741-2552/ac2bb8) | ||
|
||
# depth of shanks: 650 or 850 um | ||
# (https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9175335) | ||
|
||
def __init__(self, x=15000, y=0, z=0, rot=0, stim=None, | ||
preprocess=False, safe_mode=False): | ||
if not np.isclose(z, 0): | ||
raise NotImplementedError | ||
self.preprocess = preprocess | ||
self.safe_mode = safe_mode | ||
self.shape = (5, 4) | ||
spacing = 400 | ||
names = np.array( | ||
[ | ||
[i for i in range(1, 5)] + ['R'], | ||
[i for i in range(5, 9)] + ['t1'], | ||
[i for i in range(9, 14)], | ||
['C'] + [i for i in range(14, 17)] + ['t2'] | ||
] | ||
) | ||
names = np.rot90(names).flatten() | ||
|
||
if not isinstance(z, (list, np.ndarray)): | ||
z = np.full(20, z, dtype=float) | ||
|
||
# These electrodes have a shaft length of 650 microns, the rest are 650 microns | ||
length_650 = {'9', '2', '6', '11', '15', '4', '8', '13'} | ||
|
||
# account for depth of shanks | ||
z_offset = [650 if name in length_650 else 850 for name in names] | ||
z -= z_offset | ||
|
||
self.earray = ElectrodeGrid( | ||
self.shape, spacing, x=x, y=y, z=z, rot=rot, names=names, | ||
type='hex', orientation='vertical', r=50, etype=DiskElectrode | ||
) | ||
for e in ['t1', 't2']: | ||
self.earray.remove_electrode(e) | ||
|
||
self.earray.deactivate(['R', 'C']) | ||
|
||
# Beware of race condition: Stim must be set last, because it requires | ||
# indexing into self.electrodes: | ||
self.stim = stim | ||
|
||
def _pprint_params(self): | ||
"""Return dict of class attributes to pretty-print""" | ||
params = super()._pprint_params() | ||
params.update({'shape': self.shape, 'safe_mode': self.safe_mode, | ||
'preprocess': self.preprocess}) | ||
return params |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import numpy as np | ||
import pytest | ||
import numpy.testing as npt | ||
|
||
from pulse2percept.implants.cortex.icvp import ICVP | ||
|
||
@pytest.mark.parametrize('x', (-100, 200)) | ||
@pytest.mark.parametrize('y', (-200, 400)) | ||
@pytest.mark.parametrize('rot', (-45, 60)) | ||
def test_icvp(x, y, rot): | ||
icvp = ICVP(x, y, rot=rot) | ||
non_rot_icvp = ICVP(0) | ||
|
||
n_elec = 18 | ||
spacing = 400 | ||
radius = 50 | ||
length_650 = {'9', '2', '6', '11', '15', '4', '8', '13'} | ||
deactivated_electrodes = {'R', 'C'} | ||
|
||
# Slots: | ||
npt.assert_equal(hasattr(icvp, '__slots__'), True) | ||
npt.assert_equal(hasattr(icvp, '__dict__'), False) | ||
|
||
# Make sure number of electrodes is correct | ||
npt.assert_equal(icvp.n_electrodes, n_elec) | ||
npt.assert_equal(len(icvp.earray.electrodes), n_elec) | ||
|
||
# Coordinates of 11 when device is not rotated: | ||
xy = np.array([non_rot_icvp['11'].x, non_rot_icvp['11'].y]) | ||
# Rotate | ||
rot_rad = np.deg2rad(rot) | ||
R = np.array([np.cos(rot_rad), -np.sin(rot_rad), | ||
np.sin(rot_rad), np.cos(rot_rad)]).reshape((2, 2)) | ||
xy = R @ xy | ||
# Then off-set: Make sure first electrode is placed | ||
# correctly | ||
npt.assert_almost_equal(icvp['11'].x, xy[0] + x, decimal=2) | ||
npt.assert_almost_equal(icvp['11'].y, xy[1] + y, decimal=2) | ||
|
||
for electrode in icvp.earray.electrode_objects: | ||
npt.assert_almost_equal(electrode.r, radius) | ||
|
||
if electrode.name in deactivated_electrodes: | ||
npt.assert_equal(electrode.activated, False) | ||
else: | ||
npt.assert_equal(electrode.activated, True) | ||
|
||
if electrode.name in length_650: | ||
npt.assert_equal(electrode.z, -650) | ||
else: | ||
npt.assert_equal(electrode.z, -850) | ||
|
||
# Make sure center to center spacing is correct | ||
npt.assert_almost_equal(np.sqrt( | ||
(icvp['11'].x - icvp['7'].x) ** 2 + | ||
(icvp['11'].y - icvp['7'].y) ** 2), | ||
spacing | ||
) | ||
npt.assert_almost_equal(np.sqrt( | ||
(icvp['11'].x - icvp['10'].x) ** 2 + | ||
(icvp['11'].y - icvp['10'].y) ** 2), | ||
spacing | ||
) | ||
npt.assert_almost_equal(np.sqrt( | ||
(icvp['11'].x - icvp['15'].x) ** 2 + | ||
(icvp['11'].y - icvp['15'].y) ** 2), | ||
spacing | ||
) |