NeuralEnsemble / elephant

Elephant is the Electrophysiology Analysis Toolkit
http://www.python-elephant.org
BSD 3-Clause "New" or "Revised" License
200 stars 92 forks source link

[Bug] Creating LFP without generate_lfp? #552

Open rcpeene opened 1 year ago

rcpeene commented 1 year ago

Describe the bug I am attempting to use estimate_csd without generate fake LFP data. I extract genuine lfp data from our files, but it looks like it requires a very specific format of input LFP data in the form of a neo.AnalogSignal with specific annotations and units. I have had a little bit of difficulty with manually converting my LFP data (as a np array) into a neo AnalogSignal, as I am new to neo and I'm unsure exactly what information is required as input. I've done my best to try and mimic the output of generate_lfp but at this point, I've encountered an error which I am unsure if it's a bug or not. 'TypeError: len() of unsized object`.

To Reproduce

  1. Run the following code after importing the relevant neo and elephant namespaces.
    
    import quantities as pq
    coords = np.array(nwb.electrodes.x)
    hz = len(lfp.data) / lfp.timestamps[-1]

neo_lfp = AnalogSignal(lfp.data, units="V", sampling_rate = hzpq.Hz, coordinates = coords pq.mm) csd = estimate_csd(neo_lfp, method="KCSD2D")

where coords is an array of scalar values and hz is a scalar.

**Traceback**
```python
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [161], line 1
----> 1 csd = estimate_csd(neo_lfp, method="KCSD2D")

File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\elephant\utils.py:80, in deprecated_alias.<locals>.deco.<locals>.wrapper(*args, **kwargs)
     77 @wraps(func)
     78 def wrapper(*args, **kwargs):
     79     _rename_kwargs(func.__name__, kwargs, aliases)
---> 80     return func(*args, **kwargs)

File ~\AppData\Local\Programs\Python\Python39\lib\site-packages\elephant\current_source_density.py:139, in estimate_csd(lfp, coordinates, method, process_estimate, **kwargs)
    137     raise ValueError('Number of signals and coords is not same')
    138 for ii in coordinates:  # CHECK for Dimensionality of electrodes
--> 139     if len(ii) > 3:
    140         raise ValueError('Invalid number of coordinate positions')
    141 dim = len(coordinates[0])  # TODO : Generic co-ordinates!

TypeError: len() of unsized object

I suspect that it is the case that either the input coordinates ought to be provided as a 2D list, or the code which checks the length of the coordinates should cast the coordinate item in the input coordinates into a tuple of length 1.

Expected behavior I expect either the CSD analysis to produce valid output or to be given an explanatory error.

Environment Windows 10: Installed elephant with pip install elephant: Python version: 3.9.10 neo==0.12.0 numpy==1.21.5 elephant==0.12.0

Moritz-Alexander-Kern commented 1 year ago

Hey @rcpeene , thanks again for the report, these are really helpful hints to further improve the code. Gladly continue.

In the following example the lfp is annotated with the coordinates using lfp.annotate(coordinates=coordinates). (For the example you provided lfp.annotate(coordinates=coords*pq.mm))

See here for the documentation on annotations: https://neo.readthedocs.io/en/latest/core.html?highlight=annotate#annotations

import numpy as np
from elephant.current_source_density import generate_lfp, estimate_csd
from elephant.current_source_density_src.utility_functions import small_source_2D
import quantities as pq

xs=np.linspace(0, 10, 230).reshape(230,1)
ys=np.linspace(0, 10, 230).reshape(230,1)

lfp = generate_lfp(small_source_2D, xs, ys)
coordinates = np.stack((xs[:,0], ys[:,0]), axis=-1)*pq.mm

lfp.annotate(coordinates=coordinates)

print(lfp.annotations['coordinates'])
csd=estimate_csd(lfp, method="KCSD2D")
print(csd)

For the future I'm planning a refactor of the current_source_density module to improve documentation with examples and error messages that are more helpful for troubleshooting.

rcpeene commented 1 year ago

The alternative annotation was a part of the solution, but the actual solution appears to be wrapping the elements of coords into tuples. Even when working with 1D data (and 1D coords input), this error arises if each coord element of coordinates aren't tuples of length 1. In your refactor this should probably be handled.

Thanks for your assistance!