-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
REVIEW: mb/usability #53
Conversation
…map user-accessible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this is still WIP. For now, just a couple of small comments.
I think that we might want to think about a future uniform API where one object or kind of object (maybe the ArgusI
object and similar) serves as an interface and does all the work through a uniform set of functions. For example, have every one of our ElectrodeArray sub-types have a predict
method (or some-such) that generates the percepts. Just a thought.
""" | ||
Initialize an electrode object | ||
|
||
Parameters | ||
---------- | ||
radius : float | ||
r : float |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to add documentation for the ptype
parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
@@ -182,24 +187,81 @@ class ElectrodeArray(object): | |||
""" | |||
Represent a retina and array of electrodes | |||
""" | |||
import operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look like this parameter gets used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
axis=0) | ||
|
||
# For now, all electrodes have the same height | ||
h_arr = np.ones_like(r_arr) * h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can test to see whether this is an array or a scalar and then do this only if this is a scalar (asserting that the shape is correct otherwise)?
@@ -2,7 +2,7 @@ sudo: false | |||
|
|||
env: | |||
global: | |||
- CONDA_DEPS="pip flake8 numpy scipy numba joblib dask" PIP_DEPS="pytest coveralls pytest-cov" | |||
- CONDA_DEPS="pip flake8 numpy scipy numba joblib dask nose" PIP_DEPS="pytest coveralls pytest-cov" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is nose required? We are using pytest for testing.
(the reason for that is that nose is probably going away some time in the future: http://nose.readthedocs.io/en/latest/#note-to-users)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha - makes sense. We might need to eventually shift to using the pytest error assertions. Something like this, I believe: http://stackoverflow.com/questions/23337471/how-to-properly-assert-that-exception-raises-in-pytest
Next, to fix the broken docstring tests...
I think that the remaining failure is a real bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great. The new API looks pleasant to use and the addition of tests/examples is excellent. This is definitely going in the right direction. I had a few small comments.
We also still need to deal with the small issue that I identified in mbeyeler#1
(btw - you might want to turn on Travis on your own fork, so that it tests my PRs on top of your branches)
(p.p.s What does the mb/branch_name
nomenclature mean, and how do you use it?)
lweight=0.636, epsilon=8.617, | ||
asymptote=1.0, slope=3.0, shift=15.0, | ||
scale_slow=521.0): | ||
lweight=0.636, scale_slow=1150.0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring is now out of sync with the parameter names. Docstring has tau1
, and doesn't have tau_nfl
and tau_inl
scale_charge : float | ||
Scaling factor applied to charge accumulation (used to be called | ||
epsilon). Default: 42.1. | ||
tol : float |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short explanation on each one of these?
for p in range(len(ptrain)): | ||
ca = tm.tsample * np.cumsum(np.maximum(0, -ptrain[p].data)) | ||
# Apply charge accumulation | ||
for i, p in enumerate(pt_list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More pythonic every day!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks much better, doesn't it. :)
There are several ways to specify an input stimulus: | ||
* For a single-electrode array, pass a single pulse train; i.e., a | ||
single utils.TimeSeries object. | ||
* For a multi-electrode array, pass a list of pulse trains; i.e., one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where every pulse train is a TimeSeries
object?
center=np.array([15, 2]), | ||
rot=0 * np.pi / 180, scale=1, bs=-1.9, bi=.5, r0=4, | ||
maxR=45, angRange=60): | ||
def makeAxonMap(xg, yg, jan_x, jan_y, axon_lambda=1, min_weight=.001): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combination of setting nCells
and nR
as key-word arguments to jansonius
and removing them as inputs here is a bit like hard-coding these values. Because the API doesn't let you change these values. Maybe not a big deal, if there is a compelling argument for this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal here was to give a user access to the axon map (jan_x
and jan_y
) for visual investigation and pretty-plotting, which is the output of jansonius
. In the old code, the Retina
object called makeAxonMap
, which called jansionius
under the hood, therefore not exposing jan_x
and jan_y
to theuser. Now Retina
calls both these functions, and stores jan_x
and jan_y
in the .npz file.
Funny enough, nCells
and nR
were never available to the Retina
API, so I hardcoded them. I don't think they are supposed to change, but I can always bring them back if we need them.
@@ -137,10 +134,6 @@ def makeAxonMap(xg, yg, axon_lambda=1, nCells=500, nR=801, min_weight=.001, | |||
space | |||
|
|||
""" | |||
axon_x, axon_y = jansonius(nCells, nR, center=center, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I might be missing something (re: key-word arguments), given that you removed the call to jansonius
. What happened here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above
sampling = 1 | ||
xlo = -2 | ||
xhi = 2 | ||
ylo = -3 | ||
yhi = 3 | ||
retina = e2cm.Retina(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, | ||
sampling=sampling, axon_map=retina_file) | ||
sampling=sampling, loadpath='') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will create a file wherever users are running their tests, wouldn't it? Better to create a temp-file to avoid strange things happening when this is run in a location where a file already exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could try to argue about how unlikely that is with the name filename generator, or start messing with tempdirs... :) But a much easier solution seemed to be the introduction of an optional input argument save_data
that is set to True by default but gets set to False
in all tests. Hence no new files will be generated in tests.
A few small testing fixes...
Looks good to me! Thanks for the additional explanation re: jansonius/makeAxonMap. I like the This should be ready to go. Since I am going to see you in a couple of hours, I'll wait to touch base with you before hitting the merge button. |
Oh dear. I am still getting the following failure when running the tests locally (including doctests):
|
On the bright side - 88% coverage! |
Although now all tests pass locally, somehow |
A number of features that are meant to improve the usability of pulse2percept:
There should be a Jupyter notebook that showcases the current, up-to-date usage of the model:
examples/notebook/0.0-example-usage.ipynb
. This will be useful as we continue to change the functionality and syntax of the model.joblib
anddask
are now optional.It's now super-easy to create common electrode arrays, such as an Argus I with a given center location
(x_center, y_center)
, a heighth
(either a list or a scalar), and a rotation anglerot
:Instead of stuff like this:
ArgusI
is an instance of typeElectrodeArray
. It can automatically figure out the size and retinal location of each electrode in the array. No more math by hand.The same is true for
e2cm.ArgusII
.ElectrodeArray
is now indexable. Electrodes can be addressed either by index or by name:ElectrodeArray
no longer requires all arguments to be lists. Now supports ints, floats, lists, and numpy arrays as inputs. Electrode type is mandatory and comes first (to avoid confusion between epiretinal and subretinal arrays). For example, all the following are now possible:The
ec2b.pulse2percept
call now accepts an input stimulusstim
, an electrode arrayimplant
, a temporal modeltm
, and a retinaretina
. If not specified, a temporal model with default parameters is used. If not specified, a retina large enough to fit the electrode array is used.dolayer
is no longer specified, since it is redundant (depends on the electrode type ofimplant
). Order of arguments has changed, too:Input arguments are type checked in order to make it easier to spot examples with outdated API calls.
Input stimuli specification is much more flexible. Single-electrode arrays take just a single pulse train. For multi-electrode arrays, the user can specify a list of pulse trains, or specify all electrodes that should receive non-zero pulse trains by name:
The function
ec2b.get_brightest_frame
finds and returns the frame in a brightness movie that contains the brightest pixel.The retina object now automatically generates a descriptive filename based on the input arguments (e.g., '../retina_s250_l2.0_rot0.0_5000x5000.npz'), and checks if the file exists. The retina can now easily be rotated by specifying some angle
rot
. Axon maps,rot
, andaxon_lambda
are now all members of the object.You can now have single-pixel retinas, which is handy for model-tuning when you only need the brightest pixel.
All new functions have their own test cases, with increased coverage.