From e4ee1650dcbae1385e7fa64d5af872280e23445f Mon Sep 17 00:00:00 2001 From: Michael Beyeler Date: Tue, 20 Feb 2018 11:59:43 -0800 Subject: [PATCH] remove deprecated files; use relative imports in tests; test only Python 3.6 fix variable name clashes in tests fix flake errors --- .travis.yml | 2 - README.md | 3 +- pulse2percept/files.py | 102 --------------- pulse2percept/stimuli.py | 55 --------- pulse2percept/tests/test_api.py | 25 ++-- pulse2percept/tests/test_files.py | 51 +------- pulse2percept/tests/test_implants.py | 90 +++++++------- pulse2percept/tests/test_retina.py | 178 ++++++++++++--------------- pulse2percept/tests/test_stimuli.py | 35 +----- pulse2percept/tests/test_utils.py | 16 +-- pulse2percept/utils.py | 52 -------- 11 files changed, 144 insertions(+), 465 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e0f3cc6..ae65bf19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,6 @@ addons: - ffmpeg python: - - '2.7' - - '3.5' - '3.6' before_install: diff --git a/README.md b/README.md index 1763addd..8c2e7a88 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ corresponds to the predicted perceptual experience of a patient: If you use pulse2percept in a scholary publication, please cite as: > M Beyeler, GM Boynton, I Fine, A Rokem (2017). pulse2percept: A Python-based > simulation framework for bionic vision. Proceedings of the 16th Python in -> Science Conference, 81-88. doi: 10.25080/shinma-7f4c6e7-00c. +> Science Conference, p.81-88, +> doi:[10.25080/shinma-7f4c6e7-00c](https://doi.org/10.25080/shinma-7f4c6e7-00c). Or use the following BibTex: ``` diff --git a/pulse2percept/files.py b/pulse2percept/files.py index baa75e72..911548c8 100644 --- a/pulse2percept/files.py +++ b/pulse2percept/files.py @@ -1,7 +1,4 @@ -import subprocess import numpy as np -import scipy.io as sio -import os import logging from pulse2percept import utils @@ -467,105 +464,6 @@ def save_video_sidebyside(videofile, percept, savefile, fps=30, libav_path=libav_path) -@utils.deprecated(alt_func='p2p.files.save_video', deprecated_version='0.2', - removed_version='0.3') -def savemoviefiles(filestr, data, path='savedImages/'): - """Saves a brightness movie to .npy, .mat, and .avi format - - Parameters - ---------- - filestr : str - Name of the resulting files without file type (suffix .npy, .mat - or .avi will be appended) - data : array - A 3-D NumPy array containing the data, such as the `data` field of - a utils.TimeSeries object - path : str, optional - Path to directory where files should be saved. - Default: savedImages/ - """ - np.save(path + filestr, data) # save as npy - sio.savemat(path + filestr + '.mat', dict(mov=data)) # save as matfile - npy2movie(path + filestr + '.avi', data) # save as avi - - -@utils.deprecated(alt_func='p2p.files.save_video', deprecated_version='0.2', - removed_version='0.3') -def npy2movie(filename, movie, rate=30): - """Saves a NumPy array to .avi on Windows - - Creates avi files will work on a 64x Windows as well as a PC provided - that the ffmpeg folder is included in the right location. - - Most uptodate version of ffmpeg can be found here: - https://ffmpeg.zeranoe.com/builds/win64/static/ - - Used instructions from here with modifications: - http://adaptivesamples.com/how-to-install-ffmpeg-on-windows - - Parameters - ---------- - filename : str - File name of .avi movie to be produced - movie : array - A 3-D NumPy array containing the data, such as the `data` field of - a utils.TimeSeries object - rate : float, optional, default: 30 - Frame rate. - """ - if os.name != 'nt': - raise OSError("npy2movie only works on Windows.") - - try: - from PIL import Image - except ImportError: - raise ImportError("You do not have PIL installed.") - - cmdstring = ('ffmpeg.exe', - '-y', - '-r', '%d' % rate, - '-f', 'image2pipe', - '-vcodec', 'mjpeg', - '-i', 'pipe:', - '-vcodec', 'libxvid', - filename - ) - logging.getLogger(__name__).info(filename) - p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False) - - for i in range(movie.shape[-1]): - im = Image.fromarray(np.uint8(scale(movie[:, :, i], 0, 255))) - p.stdin.write(im.tobytes('jpeg', 'L')) - - p.stdin.close() - - -@utils.deprecated(alt_func='p2p.stimuli.image2pulsetrain', - deprecated_version='0.2', removed_version='0.3') -def scale(inarray, newmin=0.0, newmax=1.0): - """Scales an image such that its lowest value attains newmin and - it's highest value attains newmax. - - written by Ione Fine, based on code from Rick Anthony - - Parameters - ---------- - inarray : array - The input array - newmin : float, optional, default: 0.0 - The desired lower bound of values in `inarray`. - newmax : float, optional, default: 1.0 - The desired upper bound of values in `inarray`. - """ - - oldmin = inarray.min() - oldmax = inarray.max() - - delta = (newmax - newmin) / (oldmax - oldmin) - outarray = delta * (inarray - oldmin) + newmin - return outarray - - def find_files_like(datapath, pattern): """Finds files in a folder whose name matches a pattern diff --git a/pulse2percept/stimuli.py b/pulse2percept/stimuli.py index da6844ab..743155eb 100644 --- a/pulse2percept/stimuli.py +++ b/pulse2percept/stimuli.py @@ -5,7 +5,6 @@ """ import numpy as np -from scipy import interpolate as spi import six import copy import logging @@ -465,48 +464,6 @@ def video2pulsetrain(filename, implant, framerate=20, return video -@utils.deprecated('p2p.stimuli.video2pulsetrain', removed_version='0.3') -class Movie2Pulsetrain(utils.TimeSeries): - """Is used to create pulse-train stimulus based on luminance over time from - a movie""" - - def __init__(self, rflum, tsample, fps=30.0, amp_transform='linear', - amp_max=60, freq=20, pulse_dur=.5 / 1000., - interphase_dur=.5 / 1000., - pulsetype='cathodicfirst', stimtype='pulsetrain'): - """ - Parameters - ---------- - rflum : 1D array - Values between 0 and 1 - tsample : suggest TemporalModel.tsample - """ - if tsample <= 0: - raise ValueError("tsample must be a non-negative float.") - - # set up the individual pulses - pulse = BiphasicPulse(pulsetype, pulse_dur, tsample, - interphase_dur) - # set up the sequence - dur = rflum.shape[-1] / fps - - if stimtype == 'pulsetrain': - interpulsegap = np.zeros(int(round((1.0 / freq) / tsample)) - - len(pulse.data)) - ppt = [] - for j in range(0, int(np.ceil(dur * freq))): - ppt = np.concatenate((ppt, interpulsegap), axis=0) - ppt = np.concatenate((ppt, pulse.data), axis=0) - - ppt = ppt[0:int(round(dur / tsample))] - intfunc = spi.interp1d(np.linspace(0, len(rflum), len(rflum)), - rflum) - - amp = intfunc(np.linspace(0, len(rflum), len(ppt))) - data = amp * ppt * amp_max - utils.TimeSeries.__init__(self, tsample, data) - - def parse_pulse_trains(stim, implant): """Parse input stimulus and convert to list of pulse trains @@ -567,15 +524,3 @@ def parse_pulse_trains(stim, implant): pt = copy.deepcopy(stim) return pt - - -@utils.deprecated(deprecated_version='0.2', removed_version='0.3') -def retinalmovie2electrodtimeseries(rf, movie): - """Calculates the luminance over time for each electrodes receptive field. - """ - rflum = np.zeros(movie.shape[-1]) - for f in range(movie.shape[-1]): - tmp = rf * movie[:, :, f] - rflum[f] = np.mean(tmp) - - return rflum diff --git a/pulse2percept/tests/test_api.py b/pulse2percept/tests/test_api.py index c2ed41d9..5aa70c4b 100644 --- a/pulse2percept/tests/test_api.py +++ b/pulse2percept/tests/test_api.py @@ -2,36 +2,39 @@ import numpy.testing as npt import pytest -import pulse2percept as p2p +from .. import api as p2p +from .. import implants +from .. import stimuli +from .. import utils def test_Simulation___init__(): - implant = p2p.implants.Electrode("epiretinal", 10, 0, 0, 0) + implant = implants.Electrode("epiretinal", 10, 0, 0, 0) with pytest.raises(TypeError): p2p.Simulation(implant) def test_Simulation_pulse2percept(): - implant = p2p.implants.ElectrodeArray("epiretinal", 10, 0, 0, 0) + implant = implants.ElectrodeArray("epiretinal", 10, 0, 0, 0) sim = p2p.Simulation(implant, engine='serial') sim.set_optic_fiber_layer(x_range=[0, 0], y_range=[0, 0]) - pt = p2p.stimuli.BiphasicPulse('cathodicfirst', 0.45 / 1000, 0.005 / 1000) + pt = stimuli.BiphasicPulse('cathodicfirst', 0.45 / 1000, 0.005 / 1000) sim.pulse2percept(pt) sim.pulse2percept(pt, layers=['GCL']) sim.pulse2percept(pt, layers=['INL']) # PulseTrain must have the same tsample as (implicitly set up) GCL - pt = p2p.stimuli.BiphasicPulse("cathodicfirst", 0.1, 0.001) + pt = stimuli.BiphasicPulse("cathodicfirst", 0.1, 0.001) with pytest.raises(ValueError): sim.pulse2percept(pt) - pt = p2p.stimuli.BiphasicPulse("cathodicfirst", 0.1, 0.005 / 1000) + pt = stimuli.BiphasicPulse("cathodicfirst", 0.1, 0.005 / 1000) with pytest.raises(ValueError): sim.pulse2percept(pt, layers=['GCL', 'invalid']) def test_Simulation_set_optic_fiber_layer(): - sim = p2p.Simulation(p2p.implants.ArgusI(), engine='serial') + sim = p2p.Simulation(implants.ArgusI(), engine='serial') # Invalid grid ranges with pytest.raises(ValueError): @@ -59,7 +62,7 @@ def test_Simulation_set_optic_fiber_layer(): npt.assert_equal(sim.ofl.y_range, y_range) # Smoke test - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0, 0) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0, 0) sim = p2p.Simulation(implant, engine='serial') sim.set_optic_fiber_layer(x_range=0, y_range=0) sim.set_optic_fiber_layer(x_range=[0, 0], y_range=[0, 0]) @@ -74,7 +77,7 @@ def model_cascade(self, inval): return inval # Smoke test custom model - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0, 0) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0, 0) sim = p2p.Simulation(implant, engine='serial') sim.set_optic_fiber_layer(x_range=0, y_range=0) @@ -108,7 +111,7 @@ def model_cascade(self, inval): with pytest.raises(ValueError): sim.set_ganglion_cell_layer('unknown-model') with pytest.raises(ValueError): - sim.set_ganglion_cell_layer(p2p.implants.ArgusII()) + sim.set_ganglion_cell_layer(implants.ArgusII()) def test_get_brightest_frame(): @@ -121,7 +124,7 @@ def test_get_brightest_frame(): tsdata[1, 1, idx] = 2.0 # Make sure function returns the right frame - ts = p2p.utils.TimeSeries(1, tsdata) + ts = utils.TimeSeries(1, tsdata) brightest = p2p.get_brightest_frame(ts) npt.assert_equal(brightest.data.max(), tsdata.max()) npt.assert_equal(brightest.data, tsdata[:, :, idx]) diff --git a/pulse2percept/tests/test_files.py b/pulse2percept/tests/test_files.py index 9e93f959..638ca78d 100644 --- a/pulse2percept/tests/test_files.py +++ b/pulse2percept/tests/test_files.py @@ -1,7 +1,6 @@ import numpy as np import numpy.testing as npt import pytest -import os try: # Python 3 from unittest import mock @@ -15,8 +14,8 @@ except ImportError: pass -from pulse2percept import files -from pulse2percept import utils +from .. import files +from .. import utils def test_set_skvideo_path(): @@ -195,52 +194,6 @@ def test_save_video_sidebyside(): 'invalid.avi') -def test_savemoviefiles(): - # This function is deprecated - - if os.name != 'nt': - # If not on Windows, this should break - with pytest.raises(OSError): - files.savemoviefiles('invalid.avi', np.zeros(10), path='./') - else: - # Trigger an import error - with mock.patch.dict("sys.modules", {"PIL": {}}): - with pytest.raises(ImportError): - files.savemoviefiles('invalid.avi', np.zeros(10), path='./') - - # smoke test - with pytest.warns(UserWarning): - files.savemoviefiles('invalid.avi', np.zeros(10), path='./') - - -def test_npy2movie(): - # This function is deprecated - - if os.name != 'nt': - # If not on Windows, this should break - with pytest.raises(OSError): - files.npy2movie("invalid.avi", np.zeros(10), rate=30) - else: - # Trigger an import error - with mock.patch.dict("sys.modules", {"PIL": {}}): - with pytest.raises(ImportError): - files.npy2movie("invalid.avi", np.zeros(10), rate=30) - - # smoke test - with pytest.raises(UserWarning): - files.npy2movie("invalid.avi", np.zeros(10), rate=30) - - -def test_scale(): - # This function is deprecated - inarray = np.random.rand(100) - for newmin in [0.0, -0.5]: - for newmax in [1.0, 10.0]: - scaled = files.scale(inarray, newmin=newmin, newmax=newmax) - npt.assert_almost_equal(scaled.min(), newmin) - npt.assert_almost_equal(scaled.max(), newmax) - - def test_find_files_like(): # Not sure how to check a valid pattern match, because we # don't know in which directory the test suite is diff --git a/pulse2percept/tests/test_implants.py b/pulse2percept/tests/test_implants.py index 4b5e69e7..f494cfae 100644 --- a/pulse2percept/tests/test_implants.py +++ b/pulse2percept/tests/test_implants.py @@ -2,7 +2,7 @@ import numpy.testing as npt import pytest -import pulse2percept as p2p +from .. import implants def test_Electrode(): @@ -15,7 +15,7 @@ def test_Electrode(): n = ["some name"] * num_pts for rr, xx, yy, hh, tt, nn in zip(r, x, y, h, t, n): - e = p2p.implants.Electrode(tt, rr, xx, yy, hh, nn) + e = implants.Electrode(tt, rr, xx, yy, hh, nn) npt.assert_equal(e.radius, rr) npt.assert_equal(e.x_center, xx) npt.assert_equal(e.y_center, yy) @@ -40,10 +40,10 @@ def test_Electrode(): # Invalid type with pytest.raises(ValueError): - p2p.implants.Electrode('suprachoroidal', 10, 0, 0, 0) + implants.Electrode('suprachoroidal', 10, 0, 0, 0) # Invalid layer - e = p2p.implants.Electrode('epiretinal', 10, 0, 0, 0) + e = implants.Electrode('epiretinal', 10, 0, 0, 0) with pytest.raises(ValueError): e.current_spread(0, 0, 'RGC') @@ -54,20 +54,20 @@ def test_Electrode(): def test_ElectrodeArray(): - implant = p2p.implants.ElectrodeArray('subretinal', 10, 0, 0) + implant = implants.ElectrodeArray('subretinal', 10, 0, 0) npt.assert_equal(implant.num_electrodes, 1) # Make sure ElectrodeArray can accept ints, floats, lists, np.arrays - implants = [None] * 4 - implants[0] = p2p.implants.ElectrodeArray('epiretinal', [0], [1], [2], - hs=[3]) - implants[1] = p2p.implants.ElectrodeArray('epiretinal', 0, 1, 2, hs=3) - implants[2] = p2p.implants.ElectrodeArray('epiretinal', .0, [1], 2.0, - hs=[3]) - implants[3] = p2p.implants.ElectrodeArray('epiretinal', np.array([0]), [1], - [2], hs=[[3]]) - - for arr in implants: + arrays = [None] * 4 + arrays[0] = implants.ElectrodeArray('epiretinal', [0], [1], [2], + hs=[3]) + arrays[1] = implants.ElectrodeArray('epiretinal', 0, 1, 2, hs=3) + arrays[2] = implants.ElectrodeArray('epiretinal', .0, [1], 2.0, + hs=[3]) + arrays[3] = implants.ElectrodeArray('epiretinal', np.array([0]), [1], + [2], hs=[[3]]) + + for arr in arrays: npt.assert_equal(arr.num_electrodes, 1) npt.assert_equal(arr.electrodes[0].radius, 0) npt.assert_equal(arr.electrodes[0].x_center, 1) @@ -77,8 +77,8 @@ def test_ElectrodeArray(): # Make sure electrodes can be addressed by index vals = range(5) - implant = p2p.implants.ElectrodeArray('subretinal', vals, vals, vals, - hs=vals) + implant = implants.ElectrodeArray('subretinal', vals, vals, vals, + hs=vals) npt.assert_equal(implant.num_electrodes, len(vals)) for v in vals: el = implant[v] @@ -90,36 +90,36 @@ def test_ElectrodeArray(): # Test left/right eye for validstr in ['left', 'LEFT', 'l', 'LE']: - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0, - eye=validstr) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0, + eye=validstr) npt.assert_equal(implant.eye, 'LE') for validstr in ['right', 'Right', 'r', 'RE']: - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0, - eye=validstr) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0, + eye=validstr) npt.assert_equal(implant.eye, 'RE') for invalidstr in ['both', 'lefteye', 'invalid']: with pytest.raises(ValueError): - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0, - eye=invalidstr) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0, + eye=invalidstr) def test_ElectrodeArray_add_electrode(): - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0) with pytest.raises(TypeError): implant.add_electrode(implant) with pytest.raises(ValueError): - implant.add_electrode(p2p.implants.Electrode('subretinal', 10, 0, 0)) + implant.add_electrode(implants.Electrode('subretinal', 10, 0, 0)) # Make sure electrode count is correct for j in range(5): - implant.add_electrode(p2p.implants.Electrode('epiretinal', 10, 10, 10)) + implant.add_electrode(implants.Electrode('epiretinal', 10, 10, 10)) npt.assert_equal(implant.num_electrodes, j + 2) def test_ElectrodeArray_add_electrodes(): for j in range(5): - implant = p2p.implants.ElectrodeArray('epiretinal', 10, 0, 0) + implant = implants.ElectrodeArray('epiretinal', 10, 0, 0) implant.add_electrodes(range(1, j + 1), range(j), range(j)) npt.assert_equal(implant.num_electrodes, j + 1) @@ -142,7 +142,7 @@ def test_ArgusI(): # Convert rotation angle to rad rot = r * np.pi / 180 - argus = p2p.implants.ArgusI(x, y, h=h, rot=rot) + argus = implants.ArgusI(x, y, h=h, rot=rot) # Coordinates of first electrode xy = np.array([-1200, -1200]).T @@ -179,13 +179,13 @@ def test_ArgusI(): # `h` must have the right dimensions with pytest.raises(ValueError): - p2p.implants.ArgusI(-100, 10, h=np.zeros(5)) + implants.ArgusI(-100, 10, h=np.zeros(5)) with pytest.raises(ValueError): - p2p.implants.ArgusI(-100, 10, h=[1, 2, 3]) + implants.ArgusI(-100, 10, h=[1, 2, 3]) for use_legacy_names in [False, True]: # Indexing must work for both integers and electrode names - argus = p2p.implants.ArgusI(use_legacy_names=use_legacy_names) + argus = implants.ArgusI(use_legacy_names=use_legacy_names) for idx, electrode in enumerate(argus): name = electrode.name npt.assert_equal(electrode, argus[idx]) @@ -208,14 +208,14 @@ def test_ArgusI(): # Right-eye implant: xc, yc = 500, -500 - argus_re = p2p.implants.ArgusI(eye='RE', x_center=xc, y_center=yc) + argus_re = implants.ArgusI(eye='RE', x_center=xc, y_center=yc) npt.assert_equal(argus_re['D1'].x_center > argus_re['A1'].x_center, True) npt.assert_almost_equal(argus_re['D1'].y_center, argus_re['A1'].y_center) npt.assert_equal(argus_re.tack[0] < argus_re['D1'].x_center, True) npt.assert_almost_equal(argus_re.tack[1], yc) # Left-eye implant: - argus_le = p2p.implants.ArgusI(eye='LE', x_center=xc, y_center=yc) + argus_le = implants.ArgusI(eye='LE', x_center=xc, y_center=yc) npt.assert_equal(argus_le['A1'].x_center > argus_le['D1'].x_center, True) npt.assert_almost_equal(argus_le['D1'].y_center, argus_le['A1'].y_center) npt.assert_equal(argus_le.tack[0] > argus_le['A1'].x_center, True) @@ -224,12 +224,12 @@ def test_ArgusI(): # In both left and right eyes, rotation with positive angle should be # counter-clock-wise (CCW): for (x>0,y>0), decreasing x and increasing y for eye, el in zip(['LE', 'RE'], ['A1', 'D4']): - before = p2p.implants.ArgusI(eye=eye) - after = p2p.implants.ArgusI(eye=eye, rot=np.deg2rad(10)) + before = implants.ArgusI(eye=eye) + after = implants.ArgusI(eye=eye, rot=np.deg2rad(10)) npt.assert_equal(after[el].x_center < before[el].x_center, True) npt.assert_equal(after[el].y_center > before[el].y_center, True) - argus = p2p.implants.ArgusI() + argus = implants.ArgusI() # Old to new npt.assert_equal(argus.get_new_name('M1'), 'D4') npt.assert_equal(argus.get_new_name('M6'), 'C3') @@ -252,7 +252,7 @@ def test_ArgusII(): # Convert rotation angle to rad rot = np.deg2rad(r) - argus = p2p.implants.ArgusII(x, y, h=h, rot=rot) + argus = implants.ArgusII(x, y, h=h, rot=rot) # Coordinates of first electrode xy = np.array([-2362.5, -1312.5]).T @@ -283,12 +283,12 @@ def test_ArgusII(): # `h` must have the right dimensions with pytest.raises(ValueError): - p2p.implants.ArgusII(-100, 10, h=np.zeros(5)) + implants.ArgusII(-100, 10, h=np.zeros(5)) with pytest.raises(ValueError): - p2p.implants.ArgusII(-100, 100, h=[1, 2, 3]) + implants.ArgusII(-100, 100, h=[1, 2, 3]) # Indexing must work for both integers and electrode names - argus = p2p.implants.ArgusII() + argus = implants.ArgusII() for idx, electrode in enumerate(argus): name = electrode.name npt.assert_equal(electrode, argus[idx]) @@ -304,14 +304,14 @@ def test_ArgusII(): # Right-eye implant: xc, yc = 500, -500 - argus_re = p2p.implants.ArgusII(eye='RE', x_center=xc, y_center=yc) + argus_re = implants.ArgusII(eye='RE', x_center=xc, y_center=yc) npt.assert_equal(argus_re['A10'].x_center > argus_re['A1'].x_center, True) npt.assert_almost_equal(argus_re['A10'].y_center, argus_re['A1'].y_center) npt.assert_equal(argus_re.tack[0] < argus_re['A1'].x_center, True) npt.assert_almost_equal(argus_re.tack[1], yc) # Left-eye implant: - argus_le = p2p.implants.ArgusII(eye='LE', x_center=xc, y_center=yc) + argus_le = implants.ArgusII(eye='LE', x_center=xc, y_center=yc) npt.assert_equal(argus_le['A1'].x_center > argus_le['A10'].x_center, True) npt.assert_almost_equal(argus_le['A10'].y_center, argus_le['A1'].y_center) npt.assert_equal(argus_le.tack[0] > argus_le['A10'].x_center, True) @@ -320,14 +320,14 @@ def test_ArgusII(): # In both left and right eyes, rotation with positive angle should be # counter-clock-wise (CCW): for (x>0,y>0), decreasing x and increasing y for eye, el in zip(['LE', 'RE'], ['F1', 'F10']): - before = p2p.implants.ArgusII(eye=eye) - after = p2p.implants.ArgusII(eye=eye, rot=np.deg2rad(10)) + before = implants.ArgusII(eye=eye) + after = implants.ArgusII(eye=eye, rot=np.deg2rad(10)) npt.assert_equal(after[el].x_center < before[el].x_center, True) npt.assert_equal(after[el].y_center > before[el].y_center, True) def test_Electrode_receptive_field(): - electrode = p2p.implants.Electrode('epiretinal', 100, 0, 0, 0) + electrode = implants.Electrode('epiretinal', 100, 0, 0, 0) with pytest.raises(ValueError): electrode.receptive_field(0, 0, rftype='invalid') diff --git a/pulse2percept/tests/test_retina.py b/pulse2percept/tests/test_retina.py index 5e470376..f288794e 100644 --- a/pulse2percept/tests/test_retina.py +++ b/pulse2percept/tests/test_retina.py @@ -4,36 +4,39 @@ import pytest import logging -import pulse2percept as p2p +from .. import retina +from .. import implants +from .. import stimuli +from .. import utils def test_Grid(): # Invalid calls with pytest.raises(ValueError): - grid = p2p.retina.Grid(x_range=1, n_axons=1) + grid = retina.Grid(x_range=1, n_axons=1) with pytest.raises(ValueError): - grid = p2p.retina.Grid(x_range=(1.0, 0.0), n_axons=1) + grid = retina.Grid(x_range=(1.0, 0.0), n_axons=1) with pytest.raises(ValueError): - grid = p2p.retina.Grid(y_range=1, n_axons=1) + grid = retina.Grid(y_range=1, n_axons=1) with pytest.raises(ValueError): - grid = p2p.retina.Grid(y_range=(1.0, 0.0), n_axons=1) + grid = retina.Grid(y_range=(1.0, 0.0), n_axons=1) for n_axons in [-1, 0]: with pytest.raises(ValueError): - grid = p2p.retina.Grid(n_axons=n_axons) + grid = retina.Grid(n_axons=n_axons) for n_rho in [-1, 0]: with pytest.raises(ValueError): - p2p.retina.Grid(n_rho=n_rho) + retina.Grid(n_rho=n_rho) for lophi in [-200.0, 90.0, 200.0]: with pytest.raises(ValueError): - p2p.retina.Grid(phi_range=(lophi, 85.0)) + retina.Grid(phi_range=(lophi, 85.0)) for hiphi in [-200.0, 90.0, 200.0]: with pytest.raises(ValueError): - p2p.retina.Grid(phi_range=(95.0, hiphi)) + retina.Grid(phi_range=(95.0, hiphi)) # Verify size of axon bundles for n_axons in [3, 5, 10]: - grid = p2p.retina.Grid(x_range=(0, 0), y_range=(0, 0), n_axons=n_axons, - save_data=False) + grid = retina.Grid(x_range=(0, 0), y_range=(0, 0), n_axons=n_axons, + save_data=False) # Number of axon bundles given by `n_axons` npt.assert_equal(len(grid.axon_bundles), n_axons) # Number of axons given by number of pixels on grid @@ -45,16 +48,16 @@ def test_Grid(): def test_BaseModel(): # Cannot instantiate abstract class with pytest.raises(TypeError): - tm = p2p.retina.BaseModel(0.01) + tm = retina.BaseModel(0.01) # Child class must provide `model_cascade()` - class Incomplete(p2p.retina.BaseModel): + class Incomplete(retina.BaseModel): pass with pytest.raises(TypeError): tm = Incomplete() # A complete class - class Complete(p2p.retina.BaseModel): + class Complete(retina.BaseModel): def model_cascade(self, inval): return inval @@ -68,7 +71,7 @@ def test_TemporalModel(): tsample = 0.01 / 1000 # Assume 4 electrodes, each getting some stimulation - pts = [p2p.stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 + pts = [stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 ptrain_data = [pt.data for pt in pts] # For each of these 4 electrodes, we have two effective current values: @@ -88,7 +91,7 @@ def test_TemporalModel(): ecm_by_hand[1, :] += curr * pt # And that should be the same as `calc_layer_current`: - tm = p2p.retina.TemporalModel(tsample=tsample) + tm = retina.TemporalModel(tsample=tsample) ecm = tm.calc_layer_current(ecs_item, ptrain_data, layers) npt.assert_almost_equal(ecm, ecm_by_hand) tm.model_cascade(ecs_item, ptrain_data, layers, False) @@ -101,10 +104,10 @@ def test_TemporalModel(): def test_Nanduri2012(): tsample = 0.01 / 1000 - tm = p2p.retina.Nanduri2012(tsample=tsample) + tm = retina.Nanduri2012(tsample=tsample) # Assume 4 electrodes, each getting some stimulation - pts = [p2p.stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 + pts = [stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 ptrain_data = [pt.data for pt in pts] # For each of these 4 electrodes, we have two effective current values: @@ -133,10 +136,10 @@ def test_Nanduri2012(): def test_Horsager2009_model_cascade(): tsample = 0.01 / 1000 - tm = p2p.retina.Horsager2009(tsample=tsample) + tm = retina.Horsager2009(tsample=tsample) # Assume 4 electrodes, each getting some stimulation - pts = [p2p.stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 + pts = [stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 ptrain_data = [pt.data for pt in pts] # For each of these 4 electrodes, we have two effective current values: @@ -153,10 +156,10 @@ def test_Horsager2009_model_cascade(): def test_Horsager2009_calc_layer_current(): tsample = 0.01 / 1000 - tm = p2p.retina.Horsager2009(tsample=tsample) + tm = retina.Horsager2009(tsample=tsample) # Assume 4 electrodes, each getting some stimulation - pts = [p2p.stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 + pts = [stimuli.PulseTrain(tsample=tsample, dur=0.1)] * 4 ptrain_data = [pt.data for pt in pts] # For each of these 4 electrodes, we have two effective current values: @@ -192,10 +195,10 @@ def forward_pass(model, pdurs, amps): amps = np.array([amps]).ravel() for pdur, amp in zip(pdurs, amps): in_arr = np.ones((2, 1)) - pt = p2p.stimuli.PulseTrain(model.tsample, amp=amp, freq=0.1, - pulsetype='cathodicfirst', - pulse_dur=pdur / 1000.0, - interphase_dur=pdur / 1000.0) + pt = stimuli.PulseTrain(model.tsample, amp=amp, freq=0.1, + pulsetype='cathodicfirst', + pulse_dur=pdur / 1000.0, + interphase_dur=pdur / 1000.0) percept = model.model_cascade(in_arr, [pt.data], 'GCL', False) yield percept.data.max() @@ -222,9 +225,9 @@ def yield_fits(model, pdurs, amps): # Make sure our implementation comes close to ground-truth `amps`: # - Do the forward pass - model = p2p.retina.Horsager2009(tsample=0.01 / 1000, tau1=0.42 / 1000, - tau2=45.25 / 1000, tau3=26.25 / 1000, - beta=3.43, epsilon=2.25, theta=110.3) + model = retina.Horsager2009(tsample=0.01 / 1000, tau1=0.42 / 1000, + tau2=45.25 / 1000, tau3=26.25 / 1000, + beta=3.43, epsilon=2.25, theta=110.3) amps_predicted = np.array(list(yield_fits(model, pdurs, amps_true))) # - Make sure predicted values still the same @@ -236,33 +239,32 @@ def test_Retina_Electrodes(): ssample = 1 x_range = (-2, 2) y_range = (-3, 3) - retina = p2p.retina.Grid(x_range=x_range, y_range=y_range, - sampling=ssample, - save_data=False) - npt.assert_equal(retina.gridx.shape, (int(np.diff(y_range) / ssample + 1), - int(np.diff(x_range) / ssample + 1))) - npt.assert_equal(retina.x_range, x_range) - npt.assert_equal(retina.y_range, y_range) + ret = retina.Grid(x_range=x_range, y_range=y_range, sampling=ssample, + save_data=False) + npt.assert_equal(ret.gridx.shape, (int(np.diff(y_range) / ssample + 1), + int(np.diff(x_range) / ssample + 1))) + npt.assert_equal(ret.x_range, x_range) + npt.assert_equal(ret.y_range, y_range) - electrode1 = p2p.implants.Electrode('epiretinal', 1, 0, 0, 0) + electrode1 = implants.Electrode('epiretinal', 1, 0, 0, 0) # Calculate current spread for all retinal layers retinal_layers = ['INL', 'OFL'] cs = dict() ecs = dict() for layer in retinal_layers: - cs[layer] = electrode1.current_spread(retina.gridx, retina.gridy, + cs[layer] = electrode1.current_spread(ret.gridx, ret.gridy, layer=layer) - ecs[layer] = retina.current2effectivecurrent(cs[layer]) + ecs[layer] = ret.current2effectivecurrent(cs[layer]) - electrode_array = p2p.implants.ElectrodeArray('epiretinal', [1, 1], [0, 1], - [0, 1], [0, 1]) + electrode_array = implants.ElectrodeArray('epiretinal', [1, 1], [0, 1], + [0, 1], [0, 1]) npt.assert_equal(electrode1.x_center, electrode_array.electrodes[0].x_center) npt.assert_equal(electrode1.y_center, electrode_array.electrodes[0].y_center) npt.assert_equal(electrode1.radius, electrode_array.electrodes[0].radius) - ecs_list, cs_list = retina.electrode_ecs(electrode_array) + ecs_list, cs_list = ret.electrode_ecs(electrode_array) # Make sure cs_list has an entry for every layer npt.assert_equal(cs_list.shape[-2], len(retinal_layers)) @@ -274,51 +276,25 @@ def test_Retina_Electrodes(): npt.assert_equal(cs[e], cs_list[..., i, 0]) -def test_brightness_movie(): - logging.getLogger(__name__).info("test_brightness_movie()") - - tsample = 0.075 / 1000 - tsample_out = 1.0 / 30.0 - s1 = p2p.stimuli.PulseTrain(freq=20, dur=0.5, - pulse_dur=.075 / 1000., - interphase_dur=.075 / 1000., delay=0., - tsample=tsample, amp=20, - pulsetype='cathodicfirst') - - implant = p2p.implants.ElectrodeArray('epiretinal', [1, 1], [0, 1], [0, 1], - [0, 1]) - - # Smoke testing, feed the same stimulus through both electrodes: - sim = p2p.Simulation(implant, engine='serial') - - sim.set_optic_fiber_layer(x_range=[-2, 2], y_range=[-3, 3], sampling=1, - save_data=False) - - sim.set_ganglion_cell_layer('latest', tsample=tsample) - - logging.getLogger(__name__).info(" - PulseTrain") - sim.pulse2percept([s1, s1], t_percept=tsample_out) - - def test_ret2dva(): # Below 15mm eccentricity, relationship is linear with slope 3.731 - npt.assert_equal(p2p.retina.ret2dva(0.0), 0.0) + npt.assert_equal(retina.ret2dva(0.0), 0.0) for sign in [-1, 1]: for exp in [2, 3, 4]: ret = sign * 10 ** exp # mm dva = 3.731 * sign * 10 ** (exp - 3) # dva - npt.assert_almost_equal(p2p.retina.ret2dva(ret), dva, + npt.assert_almost_equal(retina.ret2dva(ret), dva, decimal=3 - exp) # adjust precision def test_dva2ret(): # Below 50deg eccentricity, relationship is linear with slope 0.268 - npt.assert_equal(p2p.retina.dva2ret(0.0), 0.0) + npt.assert_equal(retina.dva2ret(0.0), 0.0) for sign in [-1, 1]: for exp in [-2, -1, 0]: dva = sign * 10 ** exp # deg ret = 0.268 * sign * 10 ** (exp + 3) # mm - npt.assert_almost_equal(p2p.retina.dva2ret(dva), ret, + npt.assert_almost_equal(retina.dva2ret(dva), ret, decimal=-exp) # adjust precision @@ -327,60 +303,60 @@ def test_jansonius2009(): # center for phi0 in [-135.0, 66.0, 128.0]: for loc_od in [(15.0, 2.0), (-15.0, 2.0), (-4.2, -6.66)]: - ax_pos = p2p.retina.jansonius2009(phi0, n_rho=100, - rho_range=(0.0, 45.0), - loc_od=loc_od) + ax_pos = retina.jansonius2009(phi0, n_rho=100, + rho_range=(0.0, 45.0), + loc_od=loc_od) npt.assert_almost_equal(ax_pos[0, 0], loc_od[0]) npt.assert_almost_equal(ax_pos[0, 1], loc_od[1]) # These axons should all end at the meridian for sign in [-1.0, 1.0]: for phi0 in [110.0, 135.0, 160.0]: - ax_pos = p2p.retina.jansonius2009(sign * phi0, n_rho=801, - loc_od=(15, 2), - rho_range=(0.0, 45.0)) + ax_pos = retina.jansonius2009(sign * phi0, n_rho=801, + loc_od=(15, 2), + rho_range=(0.0, 45.0)) print(ax_pos[-1, :]) npt.assert_almost_equal(ax_pos[-1, 1], 0.0, decimal=1) # `phi0` must be within [-180, 180] for phi0 in [-200.0, 181.0]: with pytest.raises(ValueError): - p2p.retina.jansonius2009(phi0) + retina.jansonius2009(phi0) # `n_rho` must be >= 1 for n_rho in [-1, 0]: with pytest.raises(ValueError): - p2p.retina.jansonius2009(0.0, n_rho=n_rho) + retina.jansonius2009(0.0, n_rho=n_rho) # `rho_range` must have min <= max for lorho in [-200.0, 90.0]: with pytest.raises(ValueError): - p2p.retina.jansonius2009(0.0, rho_range=(lorho, 45.0)) + retina.jansonius2009(0.0, rho_range=(lorho, 45.0)) for hirho in [-200.0, 40.0]: with pytest.raises(ValueError): - p2p.retina.jansonius2009(0.0, rho_range=(45.0, hirho)) + retina.jansonius2009(0.0, rho_range=(45.0, hirho)) # `eye` must be left or right for eye in ['L', 'r', 'left', 'right']: with pytest.raises(ValueError): - p2p.retina.jansonius2009(0.0, eye=eye) + retina.jansonius2009(0.0, eye=eye) # A single axon fiber with `phi0`=0 should return a single pixel location # that corresponds to the optic disc for eye in ['LE', 'RE']: for loc_od in [(15.5, 1.5), (7.0, 3.0), (-2.0, -2.0)]: - single_fiber = p2p.retina.jansonius2009(0, n_rho=1, loc_od=loc_od, - rho_range=(0, 0)) + single_fiber = retina.jansonius2009(0, n_rho=1, loc_od=loc_od, + rho_range=(0, 0)) npt.assert_equal(len(single_fiber), 1) npt.assert_almost_equal(single_fiber[0], loc_od) def test_find_closest_axon(): phi = np.linspace(-180.0, 180.0, 10) - axon_bundles = p2p.utils.parfor(p2p.retina.jansonius2009, phi) + axon_bundles = utils.parfor(retina.jansonius2009, phi) for idx, ax in enumerate(axon_bundles): # Each axon bundle should be closest to itself - closest = p2p.retina.find_closest_axon(ax[-1, :], axon_bundles) + closest = retina.find_closest_axon(ax[-1, :], axon_bundles) npt.assert_almost_equal(closest, ax[-1:0:-1, :]) @@ -392,19 +368,19 @@ def test_axon_dist_from_soma(): # have zero distance to the soma: for x_soma in [-1.0, -0.2, 0.51]: axon = np.array([[i, i] for i in np.linspace(x_soma, x_soma + 0.01)]) - _, dist = p2p.retina.axon_dist_from_soma(axon, xg, yg) + _, dist = retina.axon_dist_from_soma(axon, xg, yg) npt.assert_almost_equal(dist, 0.0) # On this simple grid, a diagonal axon should have dist [0, sqrt(2), 2]: for sign in [-1.0, 1.0]: for num in [10, 20, 50]: axon = np.array([[i, i] for i in np.linspace(sign, -sign, num)]) - _, dist = p2p.retina.axon_dist_from_soma(axon, xg, yg) + _, dist = retina.axon_dist_from_soma(axon, xg, yg) npt.assert_almost_equal(dist, np.array([0.0, np.sqrt(2), 2.0])) # An axon that does not live near the grid should return infinite distance axon = np.array([[i, i] for i in np.linspace(1000.0, 1500.0)]) - _, dist = p2p.retina.axon_dist_from_soma(axon, xg, yg) + _, dist = retina.axon_dist_from_soma(axon, xg, yg) npt.assert_equal(np.isinf(dist), True) @@ -412,18 +388,18 @@ def test_axon_contribution(): # Invalid calls for lmbda in [-1, 0]: with pytest.raises(ValueError): - p2p.retina.axon_contribution([0], [0], decay_const=lmbda) + retina.axon_contribution([0], [0], decay_const=lmbda) for p in [-1, 0]: with pytest.raises(ValueError): - p2p.retina.axon_contribution([0], [0], contribution_rule='mean', - powermean_exp=p) + retina.axon_contribution([0], [0], contribution_rule='mean', + powermean_exp=p) with pytest.raises(ValueError): - p2p.retina.axon_contribution([0], [0], contribution_rule='max', - powermean_exp=1) + retina.axon_contribution([0], [0], contribution_rule='max', + powermean_exp=1) with pytest.raises(ValueError): - p2p.retina.axon_contribution([0], [0], sensitivity_rule='unknown') + retina.axon_contribution([0], [0], sensitivity_rule='unknown') with pytest.raises(ValueError): - p2p.retina.axon_contribution([0], [0], contribution_rule='unknown') + retina.axon_contribution([0], [0], contribution_rule='unknown') # Make sure numbers are right for a simple setup: dist = np.arange(10) @@ -436,7 +412,7 @@ def test_axon_contribution(): powermean_exp = None # If current spread == 1 everywhere, contribution given by `dist` - _, contrib = p2p.retina.axon_contribution( + _, contrib = retina.axon_contribution( dist2, np.ones_like(dist), sensitivity_rule='decay', decay_const=decay_const, contribution_rule=c_rule, powermean_exp=powermean_exp @@ -453,7 +429,7 @@ def test_axon_contribution(): powermean_exp = p else: powermean_exp = None - _, contrib = p2p.retina.axon_contribution( + _, contrib = retina.axon_contribution( dist2, np.zeros_like(dist), sensitivity_rule=s_rule, contribution_rule=c_rule, powermean_exp=powermean_exp ) @@ -462,7 +438,7 @@ def test_axon_contribution(): # deprecated def test_make_axon_map(): - jan_x, jan_y = p2p.retina.jansonius(num_cells=10, num_samples=100) + jan_x, jan_y = retina.jansonius(num_cells=10, num_samples=100) xg, yg = np.meshgrid(np.linspace(-100, 100, 21), np.linspace(-100, 100, 21), indexing='xy') - ax_id, ax_wt = p2p.retina.make_axon_map(xg, yg, jan_x, jan_y) + ax_id, ax_wt = retina.make_axon_map(xg, yg, jan_x, jan_y) diff --git a/pulse2percept/tests/test_stimuli.py b/pulse2percept/tests/test_stimuli.py index ab987933..7df59bd3 100644 --- a/pulse2percept/tests/test_stimuli.py +++ b/pulse2percept/tests/test_stimuli.py @@ -14,9 +14,9 @@ except ImportError: pass -from pulse2percept import stimuli -from pulse2percept import implants -from pulse2percept import utils +from .. import stimuli +from .. import implants +from .. import utils def test_MonophasicPulse(): @@ -311,35 +311,6 @@ def test_video2pulsetrain(): stimuli.video2pulsetrain(datasets.bikes(), implant) -def test_Movie2Pulsetrain(): - fps = 30.0 - amplitude_transform = 'linear' - amp_max = 90 - freq = 20 - pulse_dur = .075 / 1000. - interphase_dur = .075 / 1000. - tsample = .005 / 1000. - pulsetype = 'cathodicfirst' - stimtype = 'pulsetrain' - rflum = np.zeros(100) - rflum[50] = 1 - m2pt = stimuli.Movie2Pulsetrain(rflum, - fps=fps, - amp_transform=amplitude_transform, - amp_max=amp_max, - freq=freq, - pulse_dur=pulse_dur, - interphase_dur=interphase_dur, - tsample=tsample, - pulsetype=pulsetype, - stimtype=stimtype) - npt.assert_equal(m2pt.shape[0], round((rflum.shape[-1] / fps) / tsample)) - npt.assert_(m2pt.data.max() < amp_max) - - with pytest.raises(ValueError): - stimuli.Movie2Pulsetrain(np.zeros(10), -0.1) - - def test_parse_pulse_trains(): # Specify pulse trains in a number of different ways and make sure they # are all identical after parsing diff --git a/pulse2percept/tests/test_utils.py b/pulse2percept/tests/test_utils.py index fdbab644..1f433dde 100644 --- a/pulse2percept/tests/test_utils.py +++ b/pulse2percept/tests/test_utils.py @@ -3,7 +3,7 @@ import pytest import copy -from pulse2percept import utils +from .. import utils try: # Python 3 @@ -29,15 +29,6 @@ def test_deprecated(): raise_deprecated() -def test_Parameters(): - my_params = utils.Parameters(foo='bar', list=[1, 2, 3]) - assert my_params.foo == 'bar' - assert my_params.list == [1, 2, 3] - assert str(my_params) == 'foo : bar\nlist : [1, 2, 3]' - my_params.tuple = (1, 2, 3) - assert my_params.tuple == (1, 2, 3) - - def test_TimeSeries(): max_val = 2.0 max_idx = 156 @@ -284,8 +275,3 @@ def test_traverse_randomly(): # Make sure every element was visited once npt.assert_equal(shuffled_idx, np.arange(len(sequence)).tolist()) npt.assert_equal(shuffled_val.sort(), sequence.sort()) - - -def test_mov2npy(): - with pytest.raises(ImportError): - utils.mov2npy(np.array([]), "invalid.npy") diff --git a/pulse2percept/utils.py b/pulse2percept/utils.py index 3455db26..d236ae4a 100644 --- a/pulse2percept/utils.py +++ b/pulse2percept/utils.py @@ -96,26 +96,6 @@ def wrapped(*args, **kwargs): return wrapped -@deprecated(deprecated_version='0.2', removed_version='0.3') -class Parameters(object): - """Container to wrap a MATLAB array in a Python dict""" - - def __init__(self, **params): - for k, v in params.items(): - self.__dict__[k] = v - - def __repr__(self): - my_list = [] - for k, v in self.__dict__.items(): - my_list.append("%s : %s" % (k, v)) - my_list.sort() - my_str = "\n".join(my_list) - return my_str - - def __setattr(self, name, values): - self.__dict__[name] = values - - class TimeSeries(object): def __init__(self, tsample, data): @@ -506,38 +486,6 @@ def newfunc(in_arg): return results -@deprecated(deprecated_version='0.2', removed_version='0.3') -def mov2npy(movie_file, out_file): - """Converts a movie file to a NumPy array - - Parameters - ---------- - movie_file : array - The movie file to be read - out_file: str - Name of .npy file to be created. - """ - # Don't import cv at module level. Instead we'll use this on python 2 - # sometimes... - try: - import cv - except ImportError: - e_s = "You do not have opencv installed. " - e_s += "You probably want to run this in Python 2" - raise ImportError(e_s) - - capture = cv.CaptureFromFile(movie_file) - frames = [] - img = cv.QueryFrame(capture) - while img is not None: - tmp = cv.CreateImage(cv.GetSize(img), 8, 3) - cv.CvtColor(img, tmp, cv.CV_BGR2RGB) - frames.append(np.asarray(cv.GetMat(tmp))) - img = cv.QueryFrame(capture) - frames = np.fliplr(np.rot90(np.mean(frames, -1).T, -1)) - np.save(out_file, frames) - - def gamma(n, tau, tsample, tol=0.01): """Returns the impulse response of `n` cascaded leaky integrators