Skip to content

Commit 2466f8c

Browse files
committed
Refactor: move CSV files to package and build interpolation dynamically
1 parent 7e04a59 commit 2466f8c

8 files changed

Lines changed: 65 additions & 70 deletions

File tree

‎MANIFEST.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ include settings.ini
22
include LICENSE
33
include CONTRIBUTING.md
44
include README.md
5-
include tklfp/*.pkl
5+
include tklfp/*.csv
66
recursive-exclude * __pycache__

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ The package uses [parameters from the original 2020 paper](https://github.com/si
4646
tklfp = TKLFP(..., params=new_params)
4747
```
4848

49-
The new params must have the same content as the default [`tklfp.params2020`](https://github.com/siplab-gt/tklfp/blob/master/tklfp/__init__.py#:~:text=_sig_i%20%3D%202.1-,params2020%20%3D,-%7B). The `A0_by_depth` params are scipy interpolation objects, but could theoretically be any callable that will return A0 (in μV) for an arbitrary depth (in mm).
49+
The new params must have the same content as the default [`tklfp.params2020`](https://github.com/siplab-gt/tklfp/blob/master/tklfp/__init__.py#:~:text=_sig_i%20%3D%202.1-,params2020%20%3D,-%7B). The `exc_A0_by_depth` and `inh_A0_by_depth` params are scipy interpolation objects built dynamically from the package's CSV data, but could theoretically be any callable that will return A0 (in μV) for an arbitrary depth (in mm).
5050

5151
## Development
5252
For development, you can install the package in editable mode with development dependencies:

‎notebooks/param_prep.ipynb‎

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,19 @@
4848
}
4949
],
5050
"source": [
51+
"from tklfp import read_data, clean_data\n",
5152
"\n",
52-
"def read_data(fname):\n",
53-
" with open(fname) as csvfile:\n",
54-
" r = csv.reader(csvfile)\n",
55-
" rows = []\n",
56-
" for row in r:\n",
57-
" rows.append(row)\n",
58-
" return np.array(rows, dtype=float)\n",
59-
"\n",
60-
"def clean_data(depth_amp, table_amp_values):\n",
61-
" depth_amp = np.vstack((depth_amp, [800, 0]))\n",
62-
" depth_amp[:, 0] = np.around(depth_amp[:, 0], -1)\n",
63-
" depth_amp[[0, 4, 8, 12], 1] = table_amp_values\n",
64-
" return depth_amp\n",
65-
"\n",
66-
"exc_depth_amp = read_data('../web-plot-digitizer/fig5a-points.csv')\n",
53+
"exc_depth_amp = read_data(\"../tklfp/fig5a-points.csv\")\n",
6754
"exc_depth_amp = clean_data(exc_depth_amp, [-1.6, 4.8, 2.4, -0.8])\n",
6855
"\n",
69-
"inh_depth_amp = read_data('../web-plot-digitizer/fig5b-points.csv')\n",
56+
"inh_depth_amp = read_data(\"../tklfp/fig5b-points.csv\")\n",
7057
"inh_depth_amp = clean_data(inh_depth_amp, [-2, 30, -12, 3])\n",
7158
"\n",
7259
"fig, axs = plt.subplots(1, 2, sharey=True, figsize=(6, 2.5))\n",
7360
"axs[0].scatter(exc_depth_amp[:, 0], exc_depth_amp[:, 1])\n",
74-
"axs[0].set(title='Excitatory uLFP', ylabel='uLFP amplitude (μV)', xlabel='Depth (μm)')\n",
61+
"axs[0].set(title=\"Excitatory uLFP\", ylabel=\"uLFP amplitude (μV)\", xlabel=\"Depth (μm)\")\n",
7562
"axs[1].scatter(inh_depth_amp[:, 0], inh_depth_amp[:, 1])\n",
76-
"axs[1].set(title='Inhibitory uLFP', xlabel='Depth (μm)')\n",
63+
"axs[1].set(title=\"Inhibitory uLFP\", xlabel=\"Depth (μm)\")\n",
7764
"plt.show()"
7865
]
7966
},
@@ -83,11 +70,11 @@
8370
"metadata": {},
8471
"source": [
8572
"## Some modifications\n",
86-
"The amplitude values in the figure are 10 times what is recommended in table 1, so we will scale them down.\n",
87-
"\n",
88-
"To make the profile more flexible, we'll also interpolate and extrapolate, assuming that by 200 μm outside the reported range the uLFP drops to 0.\n",
89-
"\n",
90-
"Finally, we'll change from μm to mm for consistency with horizontal coordinates."
73+
"The package preprocesses the data a little more and interpolates.\n",
74+
"Preprocessing steps:\n",
75+
"- The amplitude values in the figure are 10 times what is recommended in table 1, so we scale them down.\n",
76+
"- To make the profile more flexible, we'll also interpolate and extrapolate, assuming that by 200 μm outside the reported range the uLFP drops to 0.\n",
77+
"- Finally, we change from μm to mm for consistency with horizontal coordinates."
9178
]
9279
},
9380
{
@@ -108,31 +95,16 @@
10895
}
10996
],
11097
"source": [
111-
"from scipy.interpolate import interp1d\n",
112-
"def modify_and_interpolate_data(data):\n",
113-
" data_mod = data.copy()\n",
114-
" data_mod[:, 1] /= 10\n",
115-
" npad = 10 # adding multiple zeros will prevent spline interp overshoot\n",
116-
" deep_padding = np.zeros((npad, 2))\n",
117-
" deep_padding[:, 0] = np.linspace(-700, -600, npad)\n",
118-
" sup_padding = np.zeros((npad, 2))\n",
119-
" sup_padding[:, 0] = np.linspace(1000, 1100, npad)\n",
120-
" data_mod = np.vstack([deep_padding, data_mod, sup_padding])\n",
121-
" data_mod[:, 0] /= 1000\n",
122-
" return interp1d(data_mod[:, 0], data_mod[:, 1], kind='cubic', \n",
123-
" fill_value=0, bounds_error=False)\n",
124-
"\n",
125-
"f_exc = modify_and_interpolate_data(exc_depth_amp)\n",
126-
"f_inh = modify_and_interpolate_data(inh_depth_amp)\n",
98+
"from tklfp import f_exc, f_inh\n",
12799
"\n",
128100
"fig, axs = plt.subplots(1, 2, sharey=True, figsize=(6, 2.5))\n",
129101
"depths = np.linspace(-1, 1.4)\n",
130-
"axs[0].scatter(exc_depth_amp[:, 0]/1000, exc_depth_amp[:, 1]/10)\n",
131-
"axs[0].set(title='Excitatory uLFP', ylabel='uLFP amplitude (μV)', xlabel='Depth (mm)')\n",
102+
"axs[0].scatter(exc_depth_amp[:, 0] / 1000, exc_depth_amp[:, 1] / 10)\n",
103+
"axs[0].set(title=\"Excitatory uLFP\", ylabel=\"uLFP amplitude (μV)\", xlabel=\"Depth (mm)\")\n",
132104
"axs[0].plot(depths, f_exc(depths), lw=1)\n",
133-
"axs[1].scatter(inh_depth_amp[:, 0]/1000, inh_depth_amp[:, 1]/10, label='data')\n",
134-
"axs[1].set(title='Inhibitory uLFP', xlabel='Depth (mm)')\n",
135-
"axs[1].plot(depths, f_inh(depths), lw=1, label='interp')\n",
105+
"axs[1].scatter(inh_depth_amp[:, 0] / 1000, inh_depth_amp[:, 1] / 10, label=\"data\")\n",
106+
"axs[1].set(title=\"Inhibitory uLFP\", xlabel=\"Depth (mm)\")\n",
107+
"axs[1].plot(depths, f_inh(depths), lw=1, label=\"interp\")\n",
136108
"axs[1].legend()\n",
137109
"plt.show()"
138110
]
@@ -142,20 +114,7 @@
142114
"id": "414972a3",
143115
"metadata": {},
144116
"source": [
145-
"This looks reasonable. Now let's save the interpolation objects:"
146-
]
147-
},
148-
{
149-
"cell_type": "code",
150-
"execution_count": null,
151-
"id": "5e918b8d",
152-
"metadata": {},
153-
"outputs": [],
154-
"source": [
155-
"import pickle\n",
156-
"with open('../tklfp/exc_A0_by_depth.pkl', 'wb') as exc_file, open('../tklfp/inh_A0_by_depth.pkl', 'wb') as inh_file:\n",
157-
" pickle.dump(f_exc, exc_file)\n",
158-
" pickle.dump(f_inh, inh_file)"
117+
"This looks reasonable."
159118
]
160119
}
161120
],

‎tklfp/__init__.py‎

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,57 @@
11
__version__ = "0.3.0"
22
"""Lightweight implementation of Telenczuk 2020 kernel LFP approximation"""
3-
import pickle
3+
import csv
44
from typing import Union
5-
65
import numpy as np
7-
import scipy # noqa: F401
86
from importlib_resources import files
97
from numpy.typing import ArrayLike
8+
from scipy.interpolate import interp1d
9+
10+
11+
def read_data(fname):
12+
try:
13+
with files(__package__).joinpath(fname).open("r") as csvfile:
14+
r = csv.reader(csvfile)
15+
rows = []
16+
for row in r:
17+
rows.append(row)
18+
return np.array(rows, dtype=float)
19+
except (FileNotFoundError, ModuleNotFoundError, ValueError):
20+
with open(fname, "r") as csvfile:
21+
r = csv.reader(csvfile)
22+
rows = []
23+
for row in r:
24+
rows.append(row)
25+
return np.array(rows, dtype=float)
26+
27+
28+
def clean_data(depth_amp, table_amp_values):
29+
depth_amp = np.vstack((depth_amp, [800, 0]))
30+
depth_amp[:, 0] = np.around(depth_amp[:, 0], -1)
31+
depth_amp[[0, 4, 8, 12], 1] = table_amp_values
32+
return depth_amp
33+
1034

35+
def _load_uLFP_A0_profile(fname, table_amp_values):
36+
depth_amp = read_data(fname)
37+
depth_amp = clean_data(depth_amp, table_amp_values)
1138

12-
def _load_uLFP_A0_profile(fname):
13-
# with open_binary(__package__, fname) as f:
14-
with files(__package__).joinpath(fname).open("rb") as f:
15-
A0_profile = pickle.load(f)
39+
data_mod = depth_amp.copy()
40+
data_mod[:, 1] /= 10
41+
npad = 10 # adding multiple zeros will prevent spline interp overshoot
42+
deep_padding = np.zeros((npad, 2))
43+
deep_padding[:, 0] = np.linspace(-700, -600, npad)
44+
sup_padding = np.zeros((npad, 2))
45+
sup_padding[:, 0] = np.linspace(1000, 1100, npad)
46+
data_mod = np.vstack([deep_padding, data_mod, sup_padding])
47+
data_mod[:, 0] /= 1000
48+
return interp1d(
49+
data_mod[:, 0], data_mod[:, 1], kind="cubic", fill_value=0, bounds_error=False
50+
)
1651

17-
return A0_profile
1852

53+
f_exc = _load_uLFP_A0_profile("fig5a-points.csv", [-1.6, 4.8, 2.4, -0.8])
54+
f_inh = _load_uLFP_A0_profile("fig5b-points.csv", [-2, 30, -12, 3])
1955

2056
######### PARAMETERS #############
2157
_sig_i = 2.1
@@ -25,8 +61,8 @@ def _load_uLFP_A0_profile(fname):
2561
"sig_i_ms": _sig_i, # std-dev of ihibition (in ms)
2662
"sig_e_ms": 1.5 * _sig_i, # std-dev for excitation
2763
"d_ms": 10.4, # constant delay
28-
"exc_A0_by_depth": _load_uLFP_A0_profile("exc_A0_by_depth.pkl"),
29-
"inh_A0_by_depth": _load_uLFP_A0_profile("inh_A0_by_depth.pkl"),
64+
"exc_A0_by_depth": f_exc,
65+
"inh_A0_by_depth": f_inh,
3066
}
3167

3268

‎tklfp/exc_A0_by_depth.pkl‎

-2.15 KB
Binary file not shown.

‎tklfp/inh_A0_by_depth.pkl‎

-2.15 KB
Binary file not shown.

0 commit comments

Comments
 (0)