Conversation
Added Basic SourceTFR structure and Text Signed-off-by: Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at>
Added Basic SourceTFR structure and Text Signed-off-by: Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at>
Updated SourceTFR to version used for reading morlet/multitapers Signed-off-by: Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at>
| # | ||
| # Authors: Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at> | ||
| # TODO: This file uses a lot of stuff from io/base.py and source_estimate.py | ||
| # TODO: Maybe name these persons as authors too? |
There was a problem hiding this comment.
to me, the authors are the list of people that would know something about that file. It is the people I'm going to send them an email when things go south.
People who wrote the file or closely reviewed.
| if not (isinstance(vertices, np.ndarray) or | ||
| isinstance(vertices, list)): | ||
| raise ValueError('Vertices must be a numpy array or a list of ' | ||
| 'arrays') |
There was a problem hiding this comment.
we have functions for checking parameters.
| """ | ||
|
|
||
| @verbose | ||
| def __init__(self, data, vertices=None, tmin=None, tstep=None, |
There was a problem hiding this comment.
break the __init__ with functions. I know that some of our inits are huge, but they should be short easy to read in a glance.
def __init__(foo, bar, data, vertices, bla):
_check_param(foo, ...)
self._data = _prepare_data(data)
self._vercies = _foo(vertices)
...to me init should be just assigning stuff to the attributes. If _prepare_data has a signature like _prepare_data(data=data, n_col=vertices.shape[0], moon_color=foo) it tels me at a glance how data is related to the other things. Sure I would not know the details but I least I would know the possible relations. If I have to read the code and strip all the value errors out on my mind the task of reading the code is much harder.
keep in mind: code is written once but read endless times and when you read code you want to skim it. Not digest it.
| self._update_times() | ||
| self.subject = _check_subject(None, subject, False) | ||
|
|
||
| def __repr__(self): # noqa: D105 |
There was a problem hiding this comment.
Don't use '%' to parse strings (we are no longer in the 90s ;) ).
fstrings are much more readable and they are the way to go (see doc). However, we cannot use them until we drop python 3.5 support. Meanwhile, we could be using 'my name is {name}'.format(name='massich')
There was a problem hiding this comment.
We still use % all over the code base and have not agreed to change to / prefer .format -- I think % is fine for now
| """ | ||
| if ftype != 'h5': | ||
| raise ValueError('%s objects can only be written as HDF5 files.' | ||
| % (self.__class__.__name__,)) |
| raise ValueError('%s objects can only be written as HDF5 files.' | ||
| % (self.__class__.__name__,)) | ||
| if not fname.endswith('.h5'): | ||
| fname += '-stfr.h5' |
There was a problem hiding this comment.
avoid ifs without elses as much as possible. See this awesome talk by @ Katrina Owen
in a case like this one, I would do something like:
fname = fname if fname.endswith('h5') else '{}-stfr.h5'.format(fname)or any way you like to build the fname. (I like this one because it has no spaces but maybe fname + '-stfr.h5' is a better choice)
| return self._data.shape | ||
| return (self._kernel.shape[0], | ||
| self._sens_data.shape[1], | ||
| self._sens_data.shape[2]) |
There was a problem hiding this comment.
document what returns.
does this return always the same kind of tuple?
|
|
||
| @times.setter | ||
| def times(self, value): | ||
| raise ValueError('You cannot write to the .times attribute directly. ' |
There was a problem hiding this comment.
why is this a value error? isn't it a runtime?
Is there any other way to prevent the setter? maybe not.
| from mne.source_tfr import SourceTFR | ||
|
|
||
|
|
||
| def _fake_stfr(): |
| def _fake_kernel_stfr(): | ||
| """Create a fake kernelized SourceTFR.""" | ||
| kernel = np.random.rand(100, 40) | ||
| sens_data = np.random.rand(40, 20, 10) |
|
|
||
| # check if data is in correct shape | ||
| expected = [100, 10, 30] | ||
| assert_allclose([kernel_stfr.shape, full_stfr.shape, |
There was a problem hiding this comment.
why are you testing it like this? by creating a vector that will have no name and when we read the error we will know nothing about it? why allclose? is it possible to have an array with shape 100+epsilon?
assert kernel_stfr.shape == (100, 10, 30)
assert full_stfr.shape == (100, 10, 30)
assert bla.shape == (100, 10, 30)
assert foo.shape == (watever)will return a meaningful error that I will read and I would understand what is my next step. otherwise, I'll have to put a breakpoint stop the execution, understand the array you have created think about all those things and make a guess of what could be wrong. I'm not smart enough. I would rather prefer pytest telling me where should I look.
| assert_equal(stfr._data.shape[-1], n_times) | ||
| assert_array_equal(stfr.times, stfr.tmin + np.arange(n_times) * stfr.tstep) | ||
|
|
||
| assert_array_almost_equal( |
There was a problem hiding this comment.
avoid almost_equal either equal or allclose
|
|
||
| # tstep <= 0 is not allowed | ||
| pytest.raises(ValueError, attempt_assignment, stfr, 'tstep', 0) | ||
| pytest.raises(ValueError, attempt_assignment, stfr, 'tstep', -1) |
There was a problem hiding this comment.
I know this is compact, but when I read this I need to go from
attempt_assignment, stfr, 'tstep', 0 to attempt_assignment(foo=foo, bar=bar..) inside my head. Its better to write with pytest.raises(ValueError, match='AND A MEANINGFUL ERROR MATCH THAT TELLS ME WHY THIS SHOULD FAIL')
| pytest.raises(ValueError, attempt_assignment, stfr_kernel, 'data', | ||
| [np.arange(100)]) | ||
| pytest.raises(ValueError, attempt_assignment, stfr_kernel, 'data', | ||
| [[[np.arange(100)]]]) |
| @requires_h5py | ||
| def test_io_stfr_h5(): | ||
| """Test IO for stfr files using HDF5.""" | ||
| for stfr in [_fake_stfr(), _fake_kernel_stfr()]: |
| stfr_ = _fake_stfr() | ||
| kernel_stfr_ = _fake_kernel_stfr() | ||
|
|
||
| for stfr in [stfr_, kernel_stfr_]: |
|
|
||
| copy_2 = inst.copy() | ||
| assert_allclose(copy_2.crop(tmin=0.2).data, inst.data[:, :, 2:]) | ||
| assert_allclose(copy_2.times, inst.times[2:]) |
There was a problem hiding this comment.
the reference you compare with is the later assert actual == expected. Same thing `assert_allclose(actual, expected)
|
|
||
| def test_invalid_params(): | ||
| """Test catching invalid parameters for SourceTFR.""" | ||
| data = np.random.rand(40, 10, 20) |
There was a problem hiding this comment.
either you use a seed and then you compare against it or use np.empty.
| assert_allclose(copy_2.times, inst.times[2:]) | ||
|
|
||
|
|
||
| def test_invalid_params(): |
...according to review in PR mne-tools#6543 Signed-off-by: Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at>
Usually we try to isolate the things, split them as much as possible and write dependencies between them. (makes review easier) |
This is a draft PR for a tentative Version of a
SourceTFRclass which is used to store time-frequency transformed Source Estimates. It does not yet cover some relevant functions (i.e. plotting or reading from disc).This PR was mainly created, so @massich can review the code.