From 21d828baabb2f06e5dbbdfc6baed88c27067fc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Jaquier?= <72930209+AurelienJaquier@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:33:51 +0100 Subject: [PATCH] remove pdf documentation (#412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove pdf documentation * add bap and ais check to feature doc * add condition on mypy version for the tests * add tests for python 3.13 * use pyproject for cibuildwheel env variables * add indices features to doc * new example: settings * updated changelog --------- Co-authored-by: Jaquier Aurélien Tristan --- .github/workflows/build-wheels.yml | 2 - .github/workflows/test.yml | 2 +- CHANGELOG.rst | 32 + docs/examples_to_rst.sh | 7 +- docs/source/eFeatures.rst | 473 ++++- docs/source/examples.rst | 1 + docs/source/tex/efeature-documentation.tex | 196 -- docs/source/tex/efeatures.tex | 1933 -------------------- efel/settings.py | 10 +- efel/units/units.json | 26 +- examples/settings/settings_notebook.ipynb | 284 +++ pyproject.toml | 9 +- tox.ini | 3 +- 13 files changed, 836 insertions(+), 2142 deletions(-) delete mode 100644 docs/source/tex/efeature-documentation.tex delete mode 100644 docs/source/tex/efeatures.tex create mode 100644 examples/settings/settings_notebook.ipynb diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index ebf2775c..755c9c2d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -22,8 +22,6 @@ jobs: arch: x86_64 env: CIBW_BUILD: ${{ matrix.python }}*${{ matrix.arch }} - CIBW_TEST_REQUIRES: pytest neo[neomatlabio]>=0.5.1 pytest-xdist>=3.3.1 - CIBW_TEST_COMMAND: pytest -sx -n auto {project}/tests CIBW_SKIP: "*-musllinux_*" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f6f13e5..57fc502c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 29d54476..b38c2ff9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. +5.7.11 - 2024-11 +---------------- + +- Removed pdf documentation file. Reason: There was no substantial information in the pdf that were not already present in the official doc. +- added an example for using settings, and added this example to the docs +- added the (already implemented) following features to docs: + + * check_ais_initiation + * bpap_attenuation + * peak_indices + * AP_rise_indices + * AP_end_indices + * AP_fall_indices + * min_AHP_indices + * AP_begin_indices + * min_between_peaks_indices + * burst_begin_indices + * burst_end_indices + * ADP_peak_indices + * interburst_min_indices + * postburst_min_indices + * postburst_slow_ahp_indices + * postburst_fast_ahp_indices + * postburst_adp_peak_indices + * interburst_15percent_indices + * interburst_20percent_indices + * interburst_25percent_indices + * interburst_30percent_indices + * interburst_40percent_indices + * interburst_60percent_indices + * burst_ISI_indices + 5.7.9 - 2024-09 --------------- diff --git a/docs/examples_to_rst.sh b/docs/examples_to_rst.sh index 9af18044..03a0d160 100644 --- a/docs/examples_to_rst.sh +++ b/docs/examples_to_rst.sh @@ -11,6 +11,8 @@ rm docs/source/extrafeats_example.rst rm docs/source/multiprocessing_example.rst rm docs/source/voltage_clamp.rst rm -rf docs/source/voltage_clamp_files +rm docs/source/settings.rst +rm -rf docs/source/settings_notebook_files # convert jupyter nbconvert --to rst examples/sonata-network/sonata-network.ipynb @@ -19,6 +21,7 @@ jupyter nbconvert --to rst examples/neo/load_nwb.ipynb jupyter nbconvert --to rst examples/extracellular/extrafeats_example.ipynb jupyter nbconvert --to rst examples/parallel/multiprocessing_example.ipynb jupyter nbconvert --to rst examples/voltage_clamp/voltage_clamp.ipynb +jupyter nbconvert --to rst examples/settings/settings_notebook.ipynb # move mv examples/sonata-network/sonata-network.rst docs/source/ @@ -30,4 +33,6 @@ mv examples/neo/load_nwb_files docs/source/ mv examples/extracellular/extrafeats_example.rst docs/source/ mv examples/parallel/multiprocessing_example.rst docs/source/ mv examples/voltage_clamp/voltage_clamp.rst docs/source/ -mv examples/voltage_clamp/voltage_clamp_files docs/source/ \ No newline at end of file +mv examples/voltage_clamp/voltage_clamp_files docs/source/ +mv examples/settings/settings_notebook.rst docs/source/settings.rst +mv examples/settings/settings_notebook_files docs/source/ \ No newline at end of file diff --git a/docs/source/eFeatures.rst b/docs/source/eFeatures.rst index 12d483fd..9065b594 100644 --- a/docs/source/eFeatures.rst +++ b/docs/source/eFeatures.rst @@ -18,6 +18,21 @@ Spike event features .. image:: _static/figures/inv_ISI.png +peak_indices +~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the maxima of the peaks. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: +- **Units**: constant +- **Pseudocode**: :: + + peak_indices = [i for i in range(1, len(v)) if v[i] > threshold and v[i - 1] < threshold] + peak_time ~~~~~~~~~ @@ -364,6 +379,86 @@ mean_frequency last_spike_time = peak_time[peak_time < stim_end][-1] mean_frequency = 1000 * spikecount / (last_spike_time - stim_start) +burst_begin_indices +~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the first peak of each burst. + +The indices are to be applied to the peak_time feature, and not time or voltage. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0. + +- **Required features**: all_ISI_values +- **Units**: constant +- **Pseudocode**: :: + + burst_begin_indices = [1] + burst_end_indices = [] + count = 1 + + for i in range(2, len(isi_values)): + d_median = numpy.median(isi_values[count:i]) + in_burst = ( + len(burst_end_indices) == 0 or + burst_begin_indices[-1] > burst_end_indices[-1] + ) + + // look for end burst + if in_burst and isi_values[i] > (burst_factor * d_median): + burst_end_indices.append(i) + count = i + + if isi_values[i] < isi_values[i - 1] / burst_factor: + if in_burst: + burst_begin_indices[-1] = i + else: + burst_begin_indices.append(i) + count = i + +burst_end_indices +~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the last peak of each burst. + +The indices are to be applied to the peak_time feature, and not time or voltage. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0. + +- **Required features**: all_ISI_values +- **Units**: constant +- **Pseudocode**: :: + + burst_begin_indices = [1] + burst_end_indices = [] + count = 1 + + for i in range(2, len(isi_values)): + d_median = numpy.median(isi_values[count:i]) + in_burst = ( + len(burst_end_indices) == 0 or + burst_begin_indices[-1] > burst_end_indices[-1] + ) + + // look for end burst + if in_burst and isi_values[i] > (burst_factor * d_median): + burst_end_indices.append(i) + count = i + + if isi_values[i] < isi_values[i - 1] / burst_factor: + if in_burst: + burst_begin_indices[-1] = i + else: + burst_begin_indices.append(i) + count = i + strict_burst_mean_freq ~~~~~~~~~~~~~~~~~~~~~~ @@ -388,6 +483,34 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto ) ) +burst_ISI_indices +~~~~~~~~~~~~~~~~~ + +`ISI Python efeature`_ : The indices for each burst beginning. The indices are to be applied to the peak_time feature, and not time or voltage. + +- **Required features**: all_ISI_values +- **Units**: constant +- **Pseudocode**: :: + + burst_indices = [] + count = -1 + + for i in range(1, len(isi_values) - 1): + isi_p_copy = isi_values[count + 1: i] + n = len(isi_p_copy) + + if n == 0: + continue + + d_median = np.median(isi_p_copy) + + # Check burst condition + if isi_values[i] > (burst_factor * d_median) and isi_values[i + 1] < ( + isi_values[i] / burst_factor + ): + burst_indices.append(i + 1) + count = i - 1 + burst_mean_freq ~~~~~~~~~~~~~~~ @@ -583,6 +706,31 @@ Starting 5 ms after that peak take the voltage average until 5 ms before the fir interburst_voltage.append(numpy.mean(voltage[start_idx:end_idx + 1])) +interburst_min_indices +~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : Indices at minimum voltage between the end of a burst and the next spike. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0. + +- **Required features**: peak_indices, burst_end_indices +- **Units**: constant +- **Pseudocode**: :: + + interburst_min = [ + numpy.argmin( + v[peak_indices[i]:peak_indices[i + 1]] + ) for i in burst_end_indices if i + 1 < len(peak_indices) + ] + interburst_min_values ~~~~~~~~~~~~~~~~~~~~~ @@ -625,6 +773,33 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto if idx + 1 < len(peak_time) ] +interburst_15percent_indices, interburst_20percent_indices, interburst_25percent_indices, interburst_30percent_indices, interburst_40percent_indices, interburst_60percent_indices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : Indices after a given percentage (15%, 20%, 25%, 30%, 40% or 60%) of the interburst duration after the fast AHP. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Defalt value is 2.0. + +- **Required features**: postburst_fast_ahp_indices, burst_end_indices, peak_indices +- **Units**: constant +- **Pseudocode**: :: + + interburst_XXpercent_indices = [] + for i, postburst_fahp_i in enumerate(postburst_fahpi): + if i < len(burst_endi) and burst_endi[i] + 1 < len(peaki): + time_interval = t[peaki[burst_endi[i] + 1]] - t[postburst_fahp_i] + time_at_XXpercent = t[postburst_fahp_i] + time_interval * percentage / 100. + index_at_XXpercent = numpy.argwhere(t >= time_at_XXpercent)[0][0] + interburst_XXpercent_indices.append(index_at_XXpercent) + interburst_15percent_values, interburst_20percent_values, interburst_25percent_values, interburst_30percent_values, interburst_40percent_values, interburst_60percent_values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -690,6 +865,48 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto time_to_postburst_slow_ahp_py = t[postburst_slow_ahp_indices] - peak_time[burst_end_indices] +postburst_min_indices +~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the minimum voltage after the end of a burst. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0. + +- **Required features**: peak_indices, burst_end_indices +- **Units**: constant +- **Pseudocode**: :: + + postburst_min = [ + numpy.argmin( + v[peak_indices[i]:peak_indices[i + 1]] + ) + peak_indices[i] + for i in burst_end_indices + if i + 1 < len(peak_indices) + ] + + if len(postburst_min) < len(burst_end_indices): + if t[burst_end_indices[-1]] < stim_end: + end_idx = numpy.where(t >= stim_end)[0][0] + postburst_min.append( + numpy.argmin( + v[peak_indices[burst_end_indices[-1]]:end_idx] + ) + peak_indices[burst_end_indices[-1]] + ) + else: + postburst_min.append( + numpy.argmin( + v[peak_indices[burst_end_indices[-1]]:] + ) + peak_indices[burst_end_indices[-1]] + ) + postburst_min_values ~~~~~~~~~~~~~~~~~~~~ @@ -722,6 +939,40 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto v[peak_indices[burst_end_indices[-1]]:] )) +postburst_slow_ahp_indices +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the slow AHP voltage after the end of a burst. + +The number of ms to skip after the spike (in order to skip fast AHP and look for slow AHP) can be set with sahp_start. +Default is 5. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Defalt value is 2.0. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices, burst_end_indices +- **Units**: constant +- **Pseudocode**: :: + + postburst_slow_ahp = [] + for i in burst_end_indices: + i_start = numpy.where(t >= t[peak_indices[i]] + sahp_start)[0][0] + if i + 1 < len(peak_indices): + postburst_slow_ahp.append(numpy.argmin(v[i_start:peak_indices[i + 1]]) + i_start) + else: + if t[burst_end_indices[-1]] < stim_end: + end_idx = numpy.where(t >= stim_end)[0][0] + postburst_slow_ahp.append(numpy.argmin(v[i_start:end_idx]) + i_start) + else: + postburst_slow_ahp.append(numpy.argmin(v[i_start:]) + i_start) + postburst_slow_ahp_values ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -752,6 +1003,46 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto else: postburst_slow_ahp.append(numpy.min(v[i_start:])) +postburst_fast_ahp_indices +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the fast AHP voltage after the end of a burst. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Defalt value is 2.0. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices, burst_end_indices +- **Units**: constant +- **Pseudocode**: :: + + postburst_fahp = [] + for i in burst_end_indices: + if i + 1 < len(peak_indices): + stop_i = peak_indices[i + 1] + elif i + 1 < stim_end_index: + stop_i = stim_end_index + else: + stop_i = len(v) - 1 + + v_crop = v[peak_indices[i]:stop_i] + # get where the voltage is going up + crop_args = numpy.argwhere(numpy.diff(v_crop) >= 0)[:,0] + # the voltage should go up for at least two consecutive points + crop_arg_arg = numpy.argwhere(numpy.diff(crop_args) == 1)[0][0] + crop_arg = crop_args[crop_arg_arg] + end_i = peak_indices[i] + crop_arg + 1 + # the fast ahp is between last peak of burst and the point where voltage is going back up + postburst_fahp.append(numpy.argmin(v[peak_indices[i]:end_i]) + peak_indices[i]) + + return postburst_fahp + postburst_fast_ahp_values ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -788,6 +1079,33 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto return postburst_fahp +postburst_adp_peak_indices +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeEvent`_ : The indices of the small ADP peak after the fast AHP after the end of a burst. + +This implementation does not assume that every spike belongs to a burst. + +The first spike is ignored by default. This can be changed by setting ignore_first_ISI to 0. + +The burst detection can be fine-tuned by changing the setting strict_burst_factor. Defalt value is 2.0. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: postburst_fast_ahp_indices, postburst_slow_ahp_indices +- **Units**: constant +- **Pseudocode**: :: + + adp_peak_indices = [] + for i, sahpi in enumerate(postburst_sahpi): + if sahpi < postburst_fahpi[i]: + continue + adppeaki = numpy.argmax(v[postburst_fahpi[i]:sahpi]) + postburst_fahpi[i] + if adppeaki != sahpi - 1: + adp_peak_indices.append(adppeaki) + postburst_adp_peak_values ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -862,6 +1180,17 @@ The burst detection can be fine-tuned by changing the setting strict_burst_facto return time_to_postburst_adp_peaks +check_ais_initiation +~~~~~~~~~~~~~~~~~~~~ + +`Validation Python efeature`_ : Checks the initiation of action potential in AIS with respect to soma. +Returns True if no spike in the soma starts earlier than in the AIS. + +Attention! This cannot be used with the efel.get_feature_values function. You have to use the efel.pyfeatures.validation.check_ais_initiation function, +and pass it both the soma trace and the ais trace. + +- **Required features**: AP_begin_time +- **Units**: constant Spike shape features @@ -1031,6 +1360,20 @@ AP_amplitude_diff .. image:: _static/figures/AHP.png +min_AHP_indices +~~~~~~~~~~~~~~~ + +`SpikeShape`_ : Indices of the first after-hyperpolarization of each spike. + +- **Required features**: peak_indices +- **Units**: constant +- **Pseudocode**: :: + + peak_indices_plus = peak_indices + peak_indices_plus.append(len(voltage) - 1) + for i in range(peak_indices): + min_AHP_indices.append(numpy.argmin(v[i:i + 1]) + i) + min_AHP_values ~~~~~~~~~~~~~~ @@ -1151,6 +1494,25 @@ interspike interval - **Required features**: AHP_depth_abs_slow - **Units**: constant +ADP_peak_indices +~~~~~~~~~~~~~~~~ + +`SpikeShape`_ : Indices of the small afterdepolarization peak + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +strict_stiminterval should be set to True for this feature to behave as expected. + +- **Required features**: min_AHP_indices, min_between_peaks_indices +- **Units**: constant +- **Pseudocode**: :: + + adp_peak_indices = numpy.array( + [numpy.argmax(v[i:j + 1]) for (i, j) in zip(min_AHP_indices, min_v_indices)] + ) + ADP_peak_values ~~~~~~~~~~~~~~~ @@ -1208,6 +1570,32 @@ min_voltage_between_spikes for peak1, peak2 in zip(peak_indices[:-1], peak_indices[1:]): min_voltage_between_spikes.append(numpy.min(voltage[peak1:peak2])) +min_between_peaks_indices +~~~~~~~~~~~~~~~~~~~~~~~~~ + +`SpikeShape`_ : Indices of the minimal voltage between consecutive spikes + +The last value of min_between_peaks_values is the minimum between last spike and stimulus end +if strict stiminterval is True, or minimum between last spike and last voltage value +if strict stiminterval is False + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices +- **Units**: constant +- **Pseudocode**: :: + + if strict_stiminterval: + end_idx = numpy.argmin(t >= stim_end)[0][0] + else: + end_idx = len(time) - 1 + peak_indices_plus = peak_indices + peak_indices_plus.append(end_idx) + for i in range(peak_indices): + min_between_peaks_indices.append(numpy.argmin(v[i:i + 1]) + i) + min_between_peaks_values ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1228,6 +1616,44 @@ if strict stiminterval is False .. image:: _static/figures/AP_duration_half_width.png +AP_rise_indices +~~~~~~~~~~~~~~~ + +`SpikeShape`_ : indices at the half-height of the spike, in the rising phase of the spike. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices, AP_begin_indices +- **Units**: constant +- **Pseudocode**: :: + + AP_rise_indices = [] + for i in range(len(peak_indices)): + halfheight = (v[AP_begin_indices[i]] + v[peak_indices[i]] ) / 2. + diff = abs(v[AP_begin_indices[i]:peak_indices[i]] - halfheight) + AP_rise_indices.append(numpy.argmin(diff) + AP_begin_indices[i]) + +AP_fall_indices +~~~~~~~~~~~~~~~ + +`SpikeShape`_ : indices at the half-height of the spike, in the falling phase of the spike. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices, AP_begin_indices, AP_end_indices +- **Units**: constant +- **Pseudocode**: :: + + AP_rise_indices = [] + for i in range(len(peak_indices)): + halfheight = (v[peak_indices[i]] + v[AP_begin_indices[i]] ) / 2. + diff = abs(v[peak_indices[i]:AP_end_indices[i]] - halfheight) + AP_rise_indices.append(numpy.argmin(diff) + peak_indices[i]) + AP_duration_half_width ~~~~~~~~~~~~~~~~~~~~~~ @@ -1401,6 +1827,30 @@ AP2_AP1_begin_width_diff AP2_AP1_begin_width_diff = AP_begin_width[1] - AP_begin_width[0] +AP_begin_indices +~~~~~~~~~~~~~~~~ + +`SpikeShape`_ : Indices of spike start. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: min_AHP_indices, peak_indices +- **Units**: constant + +AP_end_indices +~~~~~~~~~~~~~~ + +`SpikeShape`_ : Indices of spike end. + +Attention! This feature represents indices of the interpolated time series. +If you want to use it on time or voltage, make sure that you are using the interpolated output time or voltage feature, +and not the time or voltage variable you gave as input. + +- **Required features**: peak_indices +- **Units**: constant + AP_begin_voltage, AP1_begin_voltage, AP2_begin_voltage ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1612,6 +2062,21 @@ initburst_sahp_vb numpy.array([initburst_sahp_value[0] - voltage_base[0]]) +bpap_attenuation +~~~~~~~~~~~~~~~~ + +`Multitrace Python efeature`_ : Attenuation (ratio of the amplitude of the action potential in the soma and the dendrite) of the backpropagating action potential. +The attenuation is computed by first subtracting the resting potential from the voltage traces. + +Attention! This cannot be used with the efel.get_feature_values function. You have to use the efel.pyfeatures.multitrace.bpap_attenuation function, +and pass it both the soma trace and the dendrite trace. + +- **Required features**: voltage_base +- **Units**: constant +- **Pseudocode**: :: + + return (numpy.max(v_soma) - vb_soma) / (numpy.max(v_dend) - vb_dend) + Subthreshold features --------------------- @@ -2216,7 +2681,11 @@ These features were written by Alessio Buccino and are described in `Buccino et al., 2024 `_ . The feautures can be either absolute, computed for each channel separately, or relative, computed with respect to the channel with the largest extracellular -signal amplitude: +signal amplitude. + +Attention! These features cannot be extracted with the usual get_feature_values function. +They have to be extracted using the efel.pyfeatures.extrafeats module. +To see how to use it, have a look at the `Extracellular Features Extraction for MEA Data `_ examples. peak_to_valley @@ -2487,3 +2956,5 @@ positive signal-amplitude value on the largest-amplitude channel. .. _Python efeature: https://github.com/BlueBrain/eFEL/blob/master/efel/pyfeatures/pyfeatures.py .. _ISI Python efeature: https://github.com/BlueBrain/eFEL/blob/master/efel/pyfeatures/isi.py .. _Extracellular: https://github.com/BlueBrain/eFEL/blob/master/efel/pyfeatures/extrafeats.py +.. _Multitrace Python efeature: https://github.com/BlueBrain/eFEL/blob/master/efel/pyfeatures/multitrace.py +.. _Validation Python efeature: https://github.com/BlueBrain/eFEL/blob/master/efel/pyfeatures/validation.py diff --git a/docs/source/examples.rst b/docs/source/examples.rst index a5b3e53a..e6bb0f5c 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -4,6 +4,7 @@ Examples .. toctree:: python_example1 + settings multiprocessing_example deap_optimisation neoIO_example diff --git a/docs/source/tex/efeature-documentation.tex b/docs/source/tex/efeature-documentation.tex deleted file mode 100644 index 95c13aaf..00000000 --- a/docs/source/tex/efeature-documentation.tex +++ /dev/null @@ -1,196 +0,0 @@ -%\documentclass[a4paper, twocolumn]{report} -\documentclass[a4paper]{article} - -%\usepackage[T1]{fontenc} -%\usepackage{courier} -\usepackage{amsmath} -\usepackage{boxedminipage} -\usepackage{color} -\usepackage{fancyhdr} -%\usepackage[draft]{pdfpages} -\usepackage{xspace} -%\usepackage{floatflt,graphicx} -%\usepackage{sidecap} -%\usepackage[font=small,format=plain,labelfont=bf,up,textfont=it,up]{caption} -%\usepackage{cite} -%\usepackage{listings} -\usepackage[colorinlistoftodos]{todonotes} - -\usepackage[pdftex]{hyperref} -\hypersetup{colorlinks,% - citecolor=black,% - filecolor=black,% - linkcolor=black,% - urlcolor=black,% - pdftitle={efeature documentation},% - pdfauthor={Ruben J. Moor},% - pdfsubject={electrical features},% - frenchlinks,% -} - -% configure fancy header -\pagestyle{fancy} - -% emphasis of identifiers in the feature list appendix -\newcommand{\myid}[1]{ -\texttt{#1}\xspace -} - -% simple box around paragraph with bold fond title -\newcommand{\mybox}[2]{ -\begin{flushleft} - \begin{boxedminipage}{\linewidth} - \textbf{#1} - #2 - \end{boxedminipage} -\end{flushleft} -} - -% custom figure environment -\newcommand*{\myfigurename}{} -\newenvironment{myfigure}[2][t] -{ - \renewcommand*{\myfigurename}{#2} - \begin{figure}[#1] - \centering - \vspace{-15pt} - \includegraphics{figures/\myfigurename} - \vspace{-25pt} -} -{ - \label{fig:\myfigurename} - \end{figure} -} - - -% custom side-caption figure environment -\newcommand*{\myscfigurename}{} -\newenvironment{myscfigure}[2][t] -{ - \renewcommand*{\myscfigurename}{#2} - \begin{SCfigure} - \centering - \vspace{-15pt} - \includegraphics{figures/\myscfigurename} -} -{ - \label{fig:\myscfigurename} - \end{SCfigure} -} - -\definecolor{MyGray}{rgb}{0.9,0.9,0.9} -% entry in the list of electrical features, allows an appropriate database-like style -\newcommand*{\fname}{} -\newcommand*{\fnamespace}{} -\newcommand*{\fidentifier}{} -\newcommand*{\funit}{} -\newcommand*{\frequiredfeatures}{} -\newcommand*{\frequiredtracedata}{} -\newcommand*{\frequiredparams}{} -\newcommand*{\fsemantics}{} -\newcommand*{\fdescription}{} -\newcommand*{\fremarks}{} -\newenvironment{efeature}[9] -{ - \renewcommand*{\fname}{#1} - \renewcommand*{\fnamespace}{#2} - \renewcommand*{\fidentifier}{#3} - \renewcommand*{\funit}{#4} - \renewcommand*{\frequiredfeatures}{#5} - \renewcommand*{\frequiredtracedata}{#6} - \renewcommand*{\frequiredparams}{#7} - \renewcommand*{\fsemantics}{#8} - \renewcommand*{\fdescription}{#9} - - % headline - \subsection{\fname} - - % indent - \hspace{.6cm} - %\begin{minipage}{\linewidth-0.6cm} - \begin{minipage}{\linewidth} - - % semantics - \fsemantics - - % technical details in a table - \vspace{.3cm} - \begin{tabular}{|l|p{6.2cm}|} - \hline - namespace / identifier & \texttt{\fnamespace:\fidentifier} \\ - \hline - unit & \funit \\ - \hline - required features & \frequiredfeatures \\ - \hline - required trace data & \frequiredtracedata \\ - \hline - required parameters & \frequiredparams \\ - \hline - \end{tabular} - - \vspace{.3cm} -} -% remarks -{ - \vspace{.3cm} - - % detailed description / specification of the feature - \fcolorbox{black}{MyGray}{ - \begin{minipage}{\linewidth} - \ttfamily - \scriptsize - \vspace{-2.5ex} - \begin{tabbing} - \quad \= \quad \= \quad \= \\ - \fdescription - \end{tabbing} - \end{minipage} - } - % end of indentated block - %\end{list} - \end{minipage} - - % end of list entry -} - -% abbreviations for symbols in math mode -\newcommand{\Xexp}{X_{\mathrm{exp}}} -\newcommand{\sigmaexp}{\sigma_{\mathrm{exp}}} -\newcommand{\Xsim}{X_{\mathrm{sim}}} -\newcommand{\isi}{\textrm{ISI}} -\newcommand{\tpeak}{t^{\mathrm{peak}}} -% argmax -\newcommand{\argmax}[1]{\underset{#1}{\operatorname{argmax}}} - -% abbreviations for terms -\newcommand{\bglib}{\emph{bglib1.5}\xspace} - -% font -\renewcommand{\rmdefault}{phv} - -\renewcommand{\cite}[1]{[#1]} - -\title{Electrophys Feature Extraction Library (eFEL)} -\author{Ruben Moor, Werner Van Geit} - -\fancyfoot{} -\fancyfoot[R]{\thepage} -\fancyfoot[L]{\copyright\; 2015 BBP, EPFL. All Rights Reserved} - -\fancypagestyle{plain}{% -\renewcommand{\headrulewidth}{0pt} -\fancyfoot[R]{\thepage} -\fancyfoot[L]{\copyright\; 2015 BBP, EPFL. All Rights Reserved}} - -\begin{document} - -\maketitle -\newpage - -\tableofcontents - -\section*{About this document} -\input{efeatures} - -\end{document} diff --git a/docs/source/tex/efeatures.tex b/docs/source/tex/efeatures.tex deleted file mode 100644 index 06cf9f62..00000000 --- a/docs/source/tex/efeatures.tex +++ /dev/null @@ -1,1933 +0,0 @@ -\label{app:features} -The following list is the result of the implementation of the feature concept. -It serves as a reference for the user of the feature library as well as for the developer. -In order to understand the meaning of a certain feature it is not necessary anymore to recover the extraction procedure from its implementation. - -\section{General remarks} - -In the feature library the voltage trace is represented by two vectors, the voltage vector \myid{V} and the corresponding time vector \myid{T}. -\emph{Voltage trace indices} are indices of these two vectors. -Some features require additional trace data, this is: - -\begin{itemize} - \item \myid{stim\_start}, the time at the beginning of the stimulus current. - \item \myid{stim\_end}, the time at the end of the stimulus current. -\end{itemize} - -Some features require parameters in order to determine the extraction procedure. - -Feature values are represented as vectors as well. -All elementary features which describe properties of action potentials have entries corresponding to one action potential. -Other features have only one entry containing the feature value. -For some features the extraction procedure can \emph{fail}. -In case of failure the vector containing the feature values is \emph{empty} and an error message is issued. -Note that a successful calculation of a feature can also result in an empty vector (e.g. \myid{peak indices} on a trace without action potentials). - -\section{Elementary features} - -% efeature -% args: -% feature name -% namespace -% identifier -% type -% required features -% required trace data -% required parameters -% semantics -% detailed description / specification -\begin{efeature} - {peak indices} - {LibV1} - {peak\_indices} - {(index)} - {none} - {V} - {Threshold} - {The voltage trace indices at the voltage maxima of the peaks} - { - v$_0, \ldots, $v$_{n-1} =$ V \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - IF v$_i <$ Threshold AND v$_{i+1} >$ Threshold THEN \+ \\ - APPEND i TO upwardsIndices \- \\ - ENDIF \\ - IF v$_i >$ Threshold AND v$_{i+1} <$ Threshold THEN \+ \\ - APPEND i TO downwardsIndices \- \\ - ENDIF \- \\ - ENDFOR \\ - IF length of upwardsIndices $\neq$ length of downwardsIndices THEN \+ \\ - FAIL "Bad trace shape." \- \\ - ENDIF \\ - u$_0, \ldots, $u$_{n-1} =$ upwardsIndices \\ - d$_0, \ldots, $d$_{n-1} =$ downwardsIndices \\ - FOR $j = 0, \ldots, n-1$ DO \+ \\ - APPEND $i$ TO peak\_indices WITH v$_i$ maximal AND $u_j \le i < d_j$ \- \\ - ENDFOR - } - Operating on the voltage trace starting at index 0, each upwards crossing of \myid{V} and the value of \myid{Threshold} is considered a peak onset, and each downwards crossing a peak offset respectively. - The peak index is the index of the maximum in between. -\end{efeature} -\mybox{remarks:}{ - The usage for experimental traces is not recommended. - For noisy traces small fluctuations around the value of \myid{Threshold} are counted as peaks. - Also traces have been observed where the minima between peaks laid above typical values of \myid{Threshold}, sometimes even above the maxima of other peaks. - This results in flawed peak count. - These issues have been addressed in \myid{LibV4:peak\_indices}. - } - -\begin{efeature} - {peak indices (2nd)} - {LibV4} - {peak\_indices} - {(index)} - {none} - {V} - {min spike height\\&threshold} - {The voltage trace indices at the voltage maxima of the peaks, noise save} - { - $\Delta$v$_0, \ldots, \Delta$v$_{n-1} = \Delta$V \\ - APPEND $0$ TO minima \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - IF $\Delta$v$_i < 0$ AND $\Delta$v$_{i+1} > 0$ THEN \+ \\ - APPEND $i + 1$ TO minima \- \\ - ENDIF \- \\ - ENDFOR \\ - APPEND $n - 1$ TO minima \\ - min$_0$, \ldots, min$_{n-1} =$ minima \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - max$_i = j$ WITH v$_j$ maximal AND min$_i \le j < $ min$_{i+1}$ \\ - h1 = V[max$_i$] - V[min$_i$] \\ - h2 = V[max$_i$] - V[min$_{i+1}$] \\ - IF h1 > min\_spike\_height AND h2 > min\_spike\_height \\ - OR \\ - V[max$_i$] > threshold AND (h1 > min\_spike\_height OR h2 > min\_spike\_height) THEN \+ \\ - APPEND max$_i$ TO peak\_indices \- \\ - ENDIF \- \\ - ENDFOR \\ - } - The nulls of the first derivative of \myid{V} with a change of sign from $-$ to $+$ are the minima. - The maxima between adjacent minima are presumable peaks. - The left-hand (right-hand) height of a peak is the difference of the maximum and the left-hand (right-hand) minimum. - A peak is kept if boths heights are bigger than \myid{min spike height} \emph{or} the maximum is bigger than \myid{threshold} and one height is bigger than \myid{min spike height}. -\end{efeature} - -\begin{efeature} - {peak voltage} - {LibV1} - {peak\_voltage} - {mV} - {peak indices} - {V} - {none} - {The voltages at the maxima of the peaks} - { - p$_0, \ldots, $p$_{n-1} =$ peak\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[p$_i$] TO peak\_voltage \- \\ - ENDFOR - } - Iterating over the \myid{peak indices} peak voltage yields \myid{V} at each index. - -\end{efeature} - -\begin{efeature} - {peak time} - {LibV1} - {peak\_time} - {ms} - {peak indices} - {T} - {none} - {The times of the maxima of the peaks} - { - p$_0, \ldots, $p$_{n-1} =$ peak\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND T[p$_i$] TO peak\_time \- \\ - ENDFOR - } - Iterating over the \myid{peak indices}, yield \myid{T} at each index. - -\end{efeature} - -\begin{efeature} - {trace check} - {LibV1} - {trace\_check} - {none} - {peak time} - {stim\_start\\&stim\_end} - { } - {Causes feature extraction failure with error message when peaks before or after the stimulus are detected} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - IF pt$_i$ < stim\_start OR pt$_i > 1.05 \cdot$ stim\_end THEN \+ \\ - FAIL "Trace sanity check failed, there were spike outside the stimulus interval." \- \\ - ENDIF\- \\ - ENDFOR \\ - APPEND 0 TO trace\_check - } - Iterating over the values \myid{peak time}, yield 0 as long as all the time values are bigger than \myid{stim start} and smaller than \myid{stim end}. - Otherwise fail. - -\end{efeature} - -\begin{efeature} - {ISI values} - {LibV1} - {ISI\_values} - {ms} - {peak time} - {none} - {none} - {The interspike intervals (i.e. time intervals) between adjacent peaks, starting at the second peak} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - IF $n < 3$ THEN \+ \\ - FAIL "Three spikes required for calculation of ISI\_values." \- \\ - ENDIF - FOR $i = 2, \dots, n - 1$ DO \+ \\ - APPEND pt$_i$ - pt$_{i-1}$ TO ISI\_values \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {doublet ISI} - {LibV1} - {doublet\_ISI} - {ms} - {peak time} - {none} - {none} - {The time interval between the first too peaks} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - IF $n < 2$ THEN \+ \\ - FAIL "Need at least two spikes for doublet\_ISI." \- \\ - ENDIF \\ - APPEND pt$_1$ - pt$_0$ TO doublet\_ISI \\ - } - -\end{efeature} - -\begin{efeature} - {burst ISI indices} - {LibV1} - {burst\_ISI\_indices} - {(index)} - {ISI values\\&peak indices} - {none} - {burst factor (optional)} - {ISI indices of those ISIs which are the beginning of a burst} - { - isi$_0, \ldots, $isi$_{n-1} =$ ISI\_values \\ - $c = 0$ \\ - FOR $i = 1, \dots, n - 2$ DO \+ \\ - median = median of $\{$ isi$_c$, \ldots, isi$_{n-1}\}$ \\ - IF isi$_i$ > burst\_factor $\cdot$ median \\ - AND isi$_{i+1}$ < isi$_i$ / burst\_factor THEN \+ \\ - APPEND $i+1$ TO burst\_ISI\_indices \- \\ - ENDIF \\ - $c = i$ \- \\ - ENDFOR - } - The median of the ISIs is determined. - The \myid{burst factor} defaults to 2. - Each ISI bigger than \myid{burst factor} times median divides two successive bursts, if it is also bigger than the following ISI times \myid{burst factor}. - -\end{efeature} - -\begin{efeature} - {mean frequency} - {LibV1} - {mean\_frequency} - {Hz} - {peak time} - {stim start\\&stim end} - {none} - {The mean frequency of the firing rate} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - $c = 0$ \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - IF pt$_i \ge$ stim\_start AND pt$_i \le$ stim\_end THEN \+ \\ - $c = c + 1$ \- \\ - ENDIF \- \\ - ENDFOR \\ - APPEND $1000 \cdot c / ($time\_to\_last\_spike $-$ stim\_start$)$ TO mean\_frequency - } - Yield the number of peaks divided by the time to the last spike. - \mybox{remarks:}{ - The resulting value might be unexpected for bursting or irregular spiking cells. - Regard \myid{ISI CV} and \myid{adaptation index} to assure that the cell is firing uniformly during the stimulus. - } -\end{efeature} - -\begin{efeature} - {time to first spike} - {LibV1} - {time\_to\_first\_spike} - {ms} - {peak time} - {stim start} - {none} - {Time from the start of the stimulus to the maximum of the first peak} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - IF $n < 1$ THEN \+ \\ - FAIL "One spike required for time\_to\_first\_spike." \- \\ - ENDIF \\ - APPEND pt$_0 -$ stim\_start TO time\_to\_first\_spike - } - -\end{efeature} - -\begin{efeature} - {min AHP indices} - {LibV1} - {min\_AHP\_indices} - {(index)} - {peak indices} - {V\\&T\\&stim end} - {none} - {Voltage trace indices at the after-hyperpolarization} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - IF $n < 1$ THEN \+ \\ - FAIL "At least one spike required for calculation of min\_AHP\_indices." \- \\ - ENDIF \\ - t$_0, \ldots, $t$_{n-1} =$ T \\ - end\_index = minimal $i$ WITH t$_i \ge$ stim\_end \\ - IF end\_index > pi$_{n-1} + 5$ THEN \+ \\ - pi' = (pi$_0$, \ldots, pi$_{n-1}$, end\_index) \- \\ - ENDIF \\ - $m$ = length of pi' \\ - FOR $i = 0, \dots, m - 2$ DO \+ \\ - APPEND j TO min\_AHP\_indices WITH V[j] minimal AND pi'$_i \le j <$ pi'$_{i+1}$ \- \\ - ENDFOR - } - Yield the indices at the voltage minima between two peaks. - For the last peak yield the minimum between the last peak and the end of the stimulus. - -\end{efeature} - -\begin{efeature} - {min AHP values} - {LibV1} - {min\_AHP\_values} - {mV} - {min AHP indices} - {V\\&T\\&stim end} - {none} - {Voltage values at the after-hyperpolarization} - { - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[ahp$_i$] TO min\_AHP\_values \- \\ - ENDFOR - } - Iterate over \myid{min AHP indices}. - Yield \myid{V} at every index. - -\end{efeature} - -\begin{efeature} - {adaptation index} - {LibV1} - {adaptation\_index} - {none} - {peak time} - {stim start\\&stim end} - {spike skipf\\&max spike skip\\&offset (optional)} - {Normalized average difference of two consecutive ISIs} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - pt$'_0$, \ldots, pt$'_{m-1}$ = $\{$ pt$_i$ | pt$_i \ge$ stim\_start $-$ offset AND pt$_i \le$ stim\_end $+$ offset $\}$ \\ - $k = \min \{$ spike\_skipf $\cdot m$, max\_spike\_skip$\}$ \\ - pt$''_0$, \ldots, pt$''_{l-1}$ = (pt$'_k$, \ldots, pt$'_m$) \\ - IF $l$ < 4 THEN \+ \\ - FAIL "Minimum 4 spike needed for feature [adaptation\_index]." \- \\ - ENDIF \\ - isi$_0$, \ldots, isi$_{j-1} =$ pt$''_1 -$ pt$''_0$, \ldots, pt$''_{l-1} -$ pt$''_{l-2}$ \\ - sub$_0$, \ldots, sub$_{i-1} =$ isi$_1 -$ isi$_0$, \ldots, isi$_{j-1} -$ isi$_{j-2}$ \\ - sum$_0$, \ldots, sum$_{i-1} =$ isi$_1 +$ isi$_0$, \ldots, isi$_{j-1} +$ isi$_{j-2}$ \\ - APPEND $\frac{1}{i-1} \sum_{n=0}^{i-1} \frac{\mathrm{sub}_n}{\mathrm{sum}_n}$ TO adaptation\_index - } - All peaks in the time interval of \myid{stim start}$-$\myid{offset} and \myid{stim end}$+$\myid{offset} are regarded, \myid{offset} defaults to zero. - The adaptation index is zero for a constant firing rate and bigger than zero for a decreasing firing rate: - \begin{align*} - A &= \frac{1}{N - k - 1} \sum_{i = k}^N \frac{\isi_i - \isi_{i-1}}{\isi_i + \isi_{i-1}} \\ - &= \frac{1}{N - k - 1} \sum_{i = k}^N \frac{\tpeak_{i+1} - 2 \tpeak_i + \tpeak_{i-1}}{\tpeak_{i+1} - \tpeak_{i-1}} - \end{align*} - with - \begin{align*} - \textrm{the interspike intervals: } & \isi_i = \tpeak_{i+1} - \tpeak_i, \\ - \textrm{the number of peaks: } & N. - \end{align*} - The first $k$ peaks are skipped. - The parameter \myid{spike skipf} is the fraction of skipped peaks, $k$ is the minimum of \myid{spike skipf} times $N$ and \myid{max spike skip}. - -\end{efeature} - -\begin{efeature} - {adaptation index 2} - {LibV1} - {adaptation\_index2} - {none} - {peak time} - {stim start\\&stim end} - {offset (optional)} - {Normalized average difference of two consecutive ISIs} - { - pt$_0, \ldots, $pt$_{n-1} =$ peak\_time \\ - pt$'_0$, \ldots, pt$'_{m-1}$ = $\{$ pt$_i$ | pt$_i \ge$ stim\_start AND pt$_i \le$ stim\_end $\}$ \\ - IF $m$ < 4 THEN \+ \\ - FAIL "Minimum 4 spike needed for feature [adaptation\_index]." \- \\ - ENDIF \\ - isi$_0$, \ldots, isi$_{j-1} =$ pt$'_1 -$ pt$'_0$, \ldots, pt$'_{m-1} -$ pt$'_{m-2}$ \\ - sub$_0$, \ldots, sub$_{i-1} =$ isi$_1 -$ isi$_0$, \ldots, isi$_{j-1} -$ isi$_{j-2}$ \\ - sum$_0$, \ldots, sum$_{i-1} =$ isi$_1 +$ isi$_0$, \ldots, isi$_{j-1} +$ isi$_{j-2}$ \\ - APPEND $\frac{1}{i-1} \sum_{n=0}^{i-1} \frac{\mathrm{sub}_n}{\mathrm{sum}_n}$ TO adaptation\_index - } - The extraction is identical to the one of \myid{adaptation index} for \myid{spike skipf} equal zero. - -\end{efeature} - -\begin{efeature} - {spike width 2} - {LibV1} - {spike\_width2} - {ms} - {min AHP indices} - {V\\&T} - {none} - {The FWHM of each peak} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - onset\_index = $\arg\max_j$ d$^2$V[j] WITH ahp$_i \le j \le$ pi$_{i+1}$ \\ - onset\_voltage = V[onset\_index] \\ - peak\_voltage = V[pi$_{i+1}$] \\ - half\_voltage = (onset\_voltage + peak\_voltage) / 2 \\ - *** rising phase *** \\ - half\_index = $\min j$ WITH V[j] > half\_voltage AND ahp$_i \le j \le$ pi$_{i+1}$ \\ - $t_0 =$ T[half\_index - 1] \\ - $v_0 =$ V[half\_index - 1] \\ - $v_1 =$ V[half\_index] \\ - $\Delta t =$ T[half\_index] - T[half\_index - 1] \\ - $t_1 = t_0 + \frac{\textrm{half\_voltage} - v_0}{v_1 - v_0} \Delta t$ \\ - *** falling phase (buggy) *** \\ - half\_index = $\min j$ WITH V[j] < half\_voltage AND pi$_{i+1} \le j \le $pi$_{i+1}$ \\ - IF half\_index = pi$_{i+1}$ THEN \+ \\ - FAIL "Falling phase of last spike is missing." \- \\ - ENDIF \\ - $t_0 =$ T[half\_index - 1] \\ - $v_0 =$ V[half\_index - 1] \\ - $v_1 =$ V[half\_index] \\ - $\Delta t =$ T[half\_index] - T[half\_index - 1] \\ - $t_2 = t_0 + \frac{\textrm{half\_voltage} - v_0}{v_1 - v_0} \Delta t$ \\ - APPEND $t_2 - t_1$ TO spike\_width2 \- \\ - ENDFOR - } - The peak onset is defined as the maximum of the second derivative. - For the calculation of the full width at half maximum the height of the peak is taken relative to the voltage at peak onset. - As one peak often contains only a little number of data points, the time vector is linearly interpolated in the rising and the falling flank. - -\end{efeature} - -\begin{efeature} - {AP width} - {LibV1} - {AP\_width} - {ms} - {peak indices\\&min AHP indices} - {V\\&T\\&stim start} - {Threshold} - {Width of each peak at the value of \myid{Threshold}} - { - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - pi$_0, \ldots, $pi$_{m-1} =$ peak\_indices \\ - start\_index = minimal $i$ WITH T[i] $\ge$ stim\_start \\ - ahp$'_0, \ldots, $ahp$'_{m-1} =$ start\_index, ahp$_0, \ldots, $ahp$_{n-1}$ \\ - FOR $j = 0, \dots, m - 2$ DO \+ \\ - onset\_index = minimal $i$ WITH V[i] $\ge$ Threshold AND ahp$'_j \le i <$ ahp$'_{j+1}$ \\ - offset\_index = minimal $i$ WITH V[i] $\le$ Threshold AND pi$_j \le i <$ ahp$'_{j+1}$ \\ - APPEND T[offset\_index] - T[onset\_index] TO AP\_width \- \\ - ENDFOR - } - The peak onset (offset) is determined as the upwards (downwards) crossing of the \myid{V} and the value of \myid{Threshold}. - AP width yields the time difference between peak onset and peak offset. - -\end{efeature} - -\begin{efeature} - {spike half width} - {LibV1} - {spike\_half\_width} - {ms} - {min AHP indices\\&peak indices} - {V\\&T\\&stim start} - {none} - {The FWHM of each peak} - { - start\_index = minimal $i$ WITH T[i] $\ge$ stim\_start \\ - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - pi$_0, \ldots, $pi$_{m-1} =$ peak\_indices \\ - ahp$'_0, \ldots, $ahp$'_{m-1} =$ start\_index, ahp$_0, \ldots, $ahp$_{n-1}$ \\ - FOR $i = 1, \dots, m - 1$ DO \+ \\ - half\_voltage = (pi$_{i-1}$ + ahp$'_i$) / 2 \\ - rise\_index = $\min j$ WITH V[j] > half\_voltage AND ahp$'_{i-1} \le j \le$ pi$_{i-1}$ \\ - $\delta v =$ half\_voltage - V[rise\_index] \\ - $\Delta v =$ V[rise\_index] - V[rise\_index - 1] \\ - $\Delta t =$ T[rise\_index] - T[rise\_index - 1] \\ - $\delta t_1 = \Delta t \frac{\delta v}{\Delta v}$ \\ - fall\_index = $\min j$ WITH V[j] < half\_voltage AND pi$_{i-1} \le j \le$ ahp$'_i$ \\ - $\delta v =$ half\_voltage - V[fall\_index] \\ - $\Delta v =$ V[fall\_index] - V[fall\_index - 1] \\ - $\Delta t =$ T[fall\_index] - T[fall\_index - 1] \\ - $\delta t_2 = \Delta t \frac{\delta v}{\Delta v}$ \\ - APPEND T[fall\_index] + $\delta t_1$ - T[rise\_index] + $\delta t_2$ TO spike\_half\_width \- \\ - ENDFOR - } - The height of the peak is defined relative to \myid{V} at the subsequent \myid{min AHP index}. - As one peak often contains only a little number of data points, the time vector is linearly interpolated in the rising and the falling flank. - -\end{efeature} - -\begin{efeature} - {burst mean frequency} - {LibV1} - {burst\_mean\_freq} - {Hz} - {burst ISI indices\\&peak time} - {none} - {none} - {The mean frequency during a burst for each burst} - { - isi$_0, \ldots, $isi$_{n-1} =$ burst\_ISI\_indices \\ - isi$'_0, \ldots, $isi$'_{m-1} =$ (0, isi$_0, \ldots, $isi$_{n-1}$) \\ - FOR $i = 0, \dots, m - 2$ DO \+ \\ - IF isi$'_{i+1} -$ isi$'_{i}$ = 1 THEN \+ \\ - $\nu$ = 0 \- \\ - ELSE \+ \\ - $\Delta t =$ peak\_time[isi$'_{i+1}$ - 1] - peak\_time[isi$'_i$] \\ - $\nu$ = 1000 (isi$'_{i+1}$ - isi$'_i$ + 1) / $\Delta t$ \- \\ - ENDIF \\ - APPEND $\nu$ TO burst\_mean\_frequency' \- \\ - ENDFOR \\ - $\Delta t =$ peak\_time[last] - peak\_time[isi$'_i$] \\ - $\nu$ = 1000 (length of peak\_time - isi$'_i$) / $\Delta t$ \\ - APPEND $\nu$ TO burst\_mean\_frequency' \\ - burst\_mean\_frequency = $\{\nu|\nu$ IN burst\_mean\_frequency AND $\nu \neq 0\}$ - } - Iterate over the \myid{burst ISI indices} and yield the number of peaks divided by the length of the burst. - -\end{efeature} - -\begin{efeature} - {interburst voltage} - {LibV1} - {interburst\_voltage} - {mV} - {peak indices\\&burst ISI indices} - {V\\&T} - {none} - {The voltage average in between two bursts} - { - isi$_0, \ldots, $isi$_{n-1} =$ burst\_ISI\_indices \\ - IF $n$ < 2 THEN \+ \\ - RETURN \- \\ - ENDIF \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - start\_index = peak\_indices[isi$_i$ - 1] \\ - t\_start = T[start\_index] + 5 \\ - end\_index = peak\_indices[isi$_i$] \\ - t\_end = T[end\_index] - 5 \\ - start\_index = -1 + minimal $j$ WITH $j \ge$ start\_index AND T[j] > t\_start \\ - end\_index = 1 + maximal $j$ WITH $j$ < end\_index AND T[j] < t\_end \\ - APPEND mean V[$j$] WITH start\_index < $j$ < end\_index TO interburst\_voltage \- \\ - ENDFOR - } - Iterating over the \myid{burst ISI indices} determine the last peak before the burst. - Starting 5 ms after that peak take the voltage average until 5 ms before the first peak of the subsequent burst. - -\end{efeature} - -\begin{efeature} - {voltage base} - {LibV1} - {voltage\_base} - {mV} - {none} - {V\\&T\\&stim start} - {none} - {The membrane resting potential} - { - start\_time = stim\_start $\cdot$ 0.25 \\ - end\_time = stim\_start $\cdot$ 0.75 \\ - t$_0, \ldots, $t$_{n-1} =$ T \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - IF t$_i \ge$ start\_time THEN \+ \\ - sum = sum + V[i] \\ - size = size + 1 \- \\ - ENDIF \\ - IF t$_i$ > end\_time THEN \+ \\ - EXIT FOR \- \\ - ENDIF \- \\ - ENDFOR \\ - APPEND sum / size TO voltage\_base \\ - } - Yield the average voltage during the time interval $\frac{1}{4}$ times \myid{stim start} and $\frac{3}{4}$ times \myid{stim start} well before the stimulus. - -\end{efeature} - -\begin{efeature} - {AP height} - {LibV1} - {AP\_height} - {mV} - {peak voltage} - {none} - {none} - {The voltages at the maxima of the peaks} - { - p$_0, \ldots, $p$_{n-1} =$ peak\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[p$_i$] TO AP\_height \- \\ - ENDFOR - } - Identical to peak voltage. - \mybox{remarks:}{ - This feature exists for reasons of compatibility. - I recommend the usage of \myid{peak voltage} instead. - \myid{AP height} should rather yield the height of the peak relative to the membrane resting potential. - } -\end{efeature} - -\begin{efeature} - {AP amplitude} - {LibV1} - {AP\_Amplitude} - {mV} - {AP begin indices\\&peak voltage} - {V} - {none} - {The relative height of the action potential} - { - pv$_0, \ldots, $pv$_{n-1} =$ peak\_voltage \\ - b$_0, \ldots, $b$_{n-1} =$ AP\_begin\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND pv$_i$ - b$_i$ TO AP\_amplitude \- \\ - ENDFOR - } - Yield the difference of \myid{peak voltage} and \myid{V} at \myid{AP begin indices} for each peak. - -\end{efeature} - -\begin{efeature} - {AHP depth abs} - {LibV1} - {AHP\_depth\_abs} - {mV} - {min AHP values} - {none} - {none} - {Voltage values at the after-hyperpolarization} - { - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[ahp$_i$] TO AHP\_depth\_abs \- \\ - ENDFOR - } - Identical to \myid{min AHP values} - \mybox{remarks:}{ - This feature exists for reasons of compatibility. - I recommend the usage of \myid{min AHP values} instead. - } -\end{efeature} - -\begin{efeature} - {AHP depth abs slow} - {LibV1} - {AHP\_depth\_abs\_slow} - {mV} - {peak indices} - {V\\&T} - {none} - {Voltage values at the ``slow'' after-hyperpolarization} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - IF n < 3 THEN \+ \\ - FAIL "At least 3 spikes needed for AHP\_depth\_abs\_slow and AHP\_slow\_time." \- \\ - ENDIF \\ - (pi$'_0$, \ldots, pi$'_{m-1}$) = (pi$_1$, \ldots, pi$'_{n-2}$) \\ - FOR $i = 0, \dots, m - 1$ DO \+ \\ - start\_time = T[pi$'_i$] + 5 \\ - start\_index = minimal $j$ WITH T[j] $\ge$ start\_time AND p$'_i \le j$ < p$'_{i+1}$ \\ - min\_voltage = $\min_j$ V[j] WITH start\_index $\le j <$ p$'_{i+1}$ \\ - APPEND min\_voltage TO AHP\_depth\_abs\_slow \- \\ - ENDFOR - } - Starting at the second peak iterating over \myid{peak indices} find the minimum of \myid{V} between each pair of peaks. - For each pair of peaks start the search 5 ms after the first peak. - -\end{efeature} - -\begin{efeature} - {AHP slow time} - {LibV1} - {AHP\_slow\_time} - {none} - {AHP depth abs slow} - {V\\&T} - {none} - {Relative timing of the ``slow'' after-hyperpolarization} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - IF n < 3 THEN \+ \\ - FAIL "At least 3 spikes needed for AHP\_depth\_abs\_slow and AHP\_slow\_time." \- \\ - ENDIF \\ - (pi$'_0$, \ldots, pi$'_{m-1}$) = (pi$_1$, \ldots, pi$'_{n-2}$) \\ - FOR $i = 0, \dots, m - 1$ DO \+ \\ - start\_time = T[pi$'_i$] + 5 \\ - start\_index = minimal $j$ WITH T[j] $\ge$ start\_time AND p$'_i \le j$ < p$'_{i+1}$ \\ - min\_index = $\arg\min_j$ V[j] WITH start\_index $\le j <$ p$'_{i+1}$ \\ - APPEND (T[min\_index] - T[pi$'_i$]) / (T[pi$'_{i+1}$] - T[pi$'_i$]) TO AHP\_slow\_time \- \\ - ENDFOR - } - Starting at the second peak iterating over \myid{peak indices} find the minimum of \myid{V} between each pair of peaks. - For each pair of peaks start the search 5 ms after the first peak. - Yield the time at the minimum divided by the length of the interspike interval. - -\end{efeature} - -\begin{efeature} - {time constant} - {LibV1} - {time\_constant} - {ms} - {none} - {V\\&T\\&stim start\\&stim end} - {none} - {The membrane time constant} - { - min\_derivative = 0.005 \\ - decay\_length = 10 \\ - min\_time = 70 \\ - start\_index = 10 + minimal $i$ WITH T[i] $\ge$ stim\_start \\ - middle\_index = minimal $i$ WITH T[i] $\ge$ (stim\_start + stim\_end) / 2 \\ - dvdt = $\frac{\Delta \mathrm{V}_i}{\Delta \mathrm{T}_i}$ WITH start\_index $\le i <$ middle\_index \\ - *** find the decay *** \\ - decay\_index = 0 \\ - WHILE THERE IS x IN (dvdt[decay\_index], \ldots, dvdt[decay\_index + 5]) DO \+ \\ - decay\_index = decay\_index + 1 \- \\ - ENDWHILE \\ - IF decay\_index + 5 = length of dvdt - 1 THEN \+ \\ - FAIL "Could not find the decay." \- \\ - ENDIF \\ - *** find the flat *** \\ - $i$ = decay\_index - WHILE T[$i$] < T[middle\_index] + min\_time DO \+ \\ - IF dvdt[$i$] > - min\_derivative THEN \+ \\ - $j =$ minimal $j$ WITH T[$j$] - T[$i$] > min\_time \\ - mean = mean of (dvdt[$i$], \ldots, dvdt[$j$]) \\ - IF mean > - min\_derivative THEN \+ \\ - EXIT WHILE \- \\ - ENDIF \- \\ - ENDIF \\ - $i = i + 1$ \- \\ - ENDWHILE \\ - flat\_index = $i$ \\ - IF flat\_index - decay\_index < decay\_length THEN \+ \\ - FAIL "Trace fall time too short." \- \\ - ENDIF \\ - (v$_0$, \ldots, v$_{n-1}$) = (v[decay\_index], \ldots, v[flat\_index]) \\ - (t$_0$, \ldots, t$_{n-1}$) = (t[decay\_index], \ldots, t[flat\_index]) \\ - $x = 0.38$ \\ - golden section search: \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - logv$_i = \log(v_i - v_{n-1} + x)$ \- \\ - ENDFOR \\ - slope, residuals = fit\_straight\_line(t, logv) \\ - repeat golden section search to minimize residuals \\ - APPEND -1 / slope TO time\_constant \\ - } - The extraction of the time constant requires a voltage trace of a cell in a hyperpolarized state. - Starting at \myid{stim start} find the beginning of the exponential decay where the first derivative of \myid{V(t)} is smaller than -0.005 $\frac{\mathrm{V}}{\mathrm{s}}$ in 5 subseequent points. - The flat subsequent to the exponential decay is defined as the point where the first derivative of the voltage trace is bigger than $-0.005$ and the mean of the follwowing 70 points as well. - If the voltage trace between the beginning of the decay and the flat includes more than 9 points, fit an exponential decay. - Yield the time constant of that decay. - -\end{efeature} - -\begin{efeature} - {voltage deflection} - {LibV1} - {voltage\_deflection} - {mV} - {none} - {V\\&T\\&stim start\\&stim end} - {none} - {The relative steady state voltage in a hyperpolarized state} - { - base = mean of V[i] WITH 0 $\le$ T[i] < stim\_start \\ - end\_index = minimal $i$ WITH t$_i \ge$ stim\_end \\ - ss = mean of V[i] WITH end\_index - 10 < i < end\_index - 5 \\ - APPEND ss - base TO voltage\_deflection - } - Calculate the base voltage as the mean of \myid{V} before \myid{stim start}. - Calculate the steady state voltage as the mean of 5 values of \myid{V} 10 points before \myid{stim end}. - Yield the difference of steady state voltage and base voltage. - -\end{efeature} - -\begin{efeature} - {ohmic input resistance} - {LibV1} - {ohmic\_input\_resistance} - {M$\Omega$} - {voltage deflection} - {stimulus current} - {none} - {The ohmic input resistance $R_\mathrm{in}$ of the cell} - { - APPEND voltage\_deflection / stimulus\_current TO ohmic\_input\_resistance - } - Yield \myid{voltage deflection} divided by \myid{stimulus current}. -\end{efeature} - -\begin{efeature} - {maximum voltage} - {LibV1} - {maximum\_voltage} - {mV} - {none} - {V\\&T\\&stim start\\&stim end} - {none} - {The maximum voltage during a stimulus} - { - max = maximal V[i] WITH i: stim\_start $\le$ T[i] < stim\_end \\ - APPEND max TO maximum\_voltage - } - Find the maximum of \myid{V} between \myid{stim start} and \myid{stim end}. -\end{efeature} - -\begin{efeature} - {steady state voltage} - {LibV1} - {steady\_state\_voltage} - {mV} - {none} - {V\\&T\\&stim end} - {none} - {Average voltage after the stimulus} - { - mean = mean V[i] WITH i: stim\_end < T[i] \\ - APPEND mean TO steady\_state\_voltage - } - Yield the average of \myid{V} after \myid{stim end}. -\end{efeature} - -\begin{efeature} - {ISI CV} - {LibV1} - {ISI\_CV} - {none} - {ISI values} - {none} - {none} - {The coefficient of variation of the ISIs} - { - $\mu$ = mean of ISI\_values \\ - $\sigma$ = standard deviation of ISI\_values \\ - APPEND $\frac{\sigma}{\mu}$ TO ISI\_CV - } - Yield the coefficient of variation: - \begin{align*} - c_v =& \frac{\sigma}{\mu} \\ - \end{align*} - with - \begin{align*} - \textrm{standard deviation: } \sigma =& \sqrt{\frac{1}{N-1} \sum_{i=1}^N \left(\isi_i - \mu \right)^2}, \\ - \textrm{mean: } \mu =& \frac{1}{N} \sum_{i=1}^N \isi_i. - \end{align*} - -\end{efeature} - -\begin{efeature} - {spike_count} - {Python efeature} - {spike_count} - {none} - {peak indices\\&trace check} - {none} - {none} - {The number of peaks during stimulus} - { - APPEND length of peak\_indices TO spike_count - } - Yield the length of \myid{peak indices}. - -\end{efeature} - -\begin{efeature} - {AHP depth} - {LibV1} - {AHP\_depth} - {mV} - {voltage base\\&min AHP values} - {none} - {none} - {Relative voltage values at the after-hyperpolarization} - { - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_values \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND ahp$_i$ - voltage\_base TO AHP\_depth \- \\ - ENDFOR - } - Iterate over \myid{min AHP values} and yield the difference of the value and \myid{voltage base}. - -\end{efeature} - -\begin{efeature} - {AP begin indices} - {LibV2} - {AP\_begin\_indices} - {(index)} - {min AHP indices\\&interpolate} - {V\\&T\\&stim start\\&stim end} - {none} - {Voltage trace indices at the onset of each action potential} - { - min\_derivative = 12.0 \\ - start\_index = minimal $i$ WITH T[i] $\ge$ stim\_start \\ - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - min$_0,$, \ldots, min$_{m-1}$ = (start\_index, ahp$_0$, \ldots, ahp$_{n-1}$) \\ - IF T[min$_{m-1}$] < stim\_end THEN \+ \\ - end\_index = minimal $i$ WITH t$_i \ge$ stim\_end \\ - APPEND end\_index TO min \- \\ - ENDIF \\ - dvdt = $\Delta$ V \\ - FOR $i$ = 0 TO length of min - 2 DO \+ \\ - IF $x \ge$ min\_derivative FOR ALL $x$ IN (dvdt[min$_i$], \ldots, dvdt[min$_i$ + 5]) THEN \+ \\ - APPEND min$_i$ TO AP\_begin\_indices \- \\ - ENDIF \- \\ - ENDFOR - } - Iterate over \myid{min AHP indices}. - If there is no AHP for the last peak, add the index at \myid{stim end} to the indices. - Yield the action potential onsets where the first derivative of the voltage trace is higher than $12 \frac{\mathrm{V}}{\mathrm{s}}$, for at least 5 points. - -\end{efeature} - -\begin{efeature} - {AP rise indices} - {LibV2} - {AP\_rise\_indices} - {(index)} - {peak indices\\&AP begin indices} - {V} - {none} - {Voltage trace index at the rising flank of each action potential.} - { - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - half\_voltage = (begin$_i$ + pi$_i$) / 2 \\ - rise\_index = $\arg\min_j$ |V[j] - half\_voltage| WITH begin$_i \le j <$ pi$_i$ \\ - APPEND rise\_index TO AP\_rise\_indices \- \\ - ENDFOR - } - Yield the indices of the voltage trace after each \myid{AP begin index} where \myid{V} reaches half the maximum of the amplitude of the action potential. - The amplitude of the action potential is taken relative to \myid{V} at \myid{AP begin indices}. - -\end{efeature} - -\begin{efeature} - {AP end indices} - {LibV2} - {AP\_end\_indices} - {(index)} - {peak indices} - {V\\&T} - {none} - {Voltage trace indices at the offset of each action potential} - { - pi$_0, \ldots, $pi$_{n-1} =$ (peak\_indices, length of V - 1) \\ - min\_derivative = -12.0 \\ - dvdt = $\Delta$ V \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - end\_index = minimal $j$ WITH pi$_i + 1 \le j <$ pi$_{i+1}$ AND dvdt[$j$] > min\_derivative \- \\ - ENDFOR - } - Iterate over \myid{peak indices} and find after each index where the first derivative of the voltage trace exceeds $-12 \frac{\mathrm{V}}{\mathrm{s}}$. - -\end{efeature} - -\begin{efeature} - {AP fall indices} - {LibV2} - {AP\_fall\_indices} - {(index)} - {peak indices\\&AP begin indices\\&AP end indices} - {V} - {none} - {Voltage trace index at the falling flank of each action potential} - { - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - end$_0, \ldots, $end$_{n-1} =$ AP\_end\_indices \\ - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - half\_voltage = (begin$_i$ + pi$_i$) / 2 \\ - fall\_index = $\arg\min_j$ |V[j] - half\_voltage| WITH pi$_i \le j <$ end$_i$ \\ - APPEND fall\_index TO AP\_fall\_indices \- \\ - ENDFOR - } - Yield the indices of the voltage trace after each \myid{peak index} where \myid{V} falls down to half the maximum of the amplitude of the action potential. - The amplitude of the action potential is taken relative to \myid{V} at \myid{AP begin indices}. - -\end{efeature} - -\begin{efeature} - {AP duration} - {LibV2} - {AP\_duration} - {ms} - {AP begin indices\\&AP end indices} - {T} - {none} - {Duration of an action potential from onset to offset} - { - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - end$_0, \ldots, $end$_{n-1} =$ AP\_end\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND T[end$_i$] - T[begin$_i$] TO AP\_duration \- \\ - ENDFOR - } - Iterate over \myid{AP begin indices} and return the difference of the time at the \myid{AP end index} and the \myid{AP begin index}. - -\end{efeature} - -\begin{efeature} - {AP duration half width} - {LibV2} - {AP\_duration\_half\_width} - {ms} - {AP rise indices\\&AP fall indices} - {T} - {none} - {FWHM of each action potential} - { - rise$_0, \ldots, $rise$_{n-1} =$ AP\_rise\_indices \\ - fall$_0, \ldots, $fall$_{n-1} =$ AP\_fall\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND T[fall$_i$] - T[rise$_i$] TO AP\_duration\_half\_width \- \\ - ENDFOR - } - Iterate over \myid{AP rise indices} and return the difference of the time at the \myid{AP fall index} and the \myid{AP rise index}. - -\end{efeature} - -\begin{efeature} - {AP rise time} - {LibV2} - {AP\_rise\_time} - {ms} - {AP begin indices\\&peak indices} - {T} - {none} - {Time from action potential onset to the maximum} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND T[pi$_i$] - T[begin$_i$] TO AP\_rise\_time \- \\ - ENDFOR - } - Iterate over \myid{AP begin indices} and return the difference of the time at the \myid{peak index} and the \myid{AP begin index}. - -\end{efeature} - -\begin{efeature} - {AP fall time} - {LibV2} - {AP\_fall\_time} - {ms} - {peak indices\\&AP end indices} - {T} - {none} - {Time from action potential maximum to the offset} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - end$_0, \ldots, $end$_{n-1} =$ AP\_end\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND T[end$_i$] - T[pi$_i$] TO AP\_fall\_time \- \\ - ENDFOR - } - Iterate over \myid{peak indices} and return the difference of the time at the \myid{end index} and the \myid{peak index}. - -\end{efeature} - -\begin{efeature} - {AP rise rate} - {LibV2} - {AP\_rise\_rate} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {AP begin indices\\&peak indices} - {V\\&T} - {none} - {Voltage change rate during the rising phase of the action potential} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND (V[pi$_i$] - V[begin$_i$]) / (T[pi$_i$] - T[begin$_i$]) TO AP\_rise\_rate \- \\ - ENDFOR - } - Iterate over \myid{AP begin indices} and return the ratio of the voltage difference and the time difference at the \myid{peak index} and the \myid{AP begin index}. - -\end{efeature} - -\begin{efeature} - {AP fall rate} - {LibV2} - {AP\_fall\_rate} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {peak indices\\&AP end indices} - {V\\&T} - {none} - {Voltage change rate during the falling phase of the action potential.} - { - pi$_0, \ldots, $pi$_{n-1} =$ peak\_indices \\ - end$_0, \ldots, $end$_{n-1} =$ AP\_end\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND (V[end$_i$] - V[pi$_i$]) / (T[end$_i$] - T[pi$_i$]) TO AP\_fall\_rate \- \\ - ENDFOR - } - Iterate over \myid{peak indices} and return the ratio of the voltage difference and the time difference at the \myid{AP end index} and the \myid{peak index}. - -\end{efeature} - -\begin{efeature} - {fast AHP} - {LibV2} - {fast\_AHP} - {mV} - {AP begin indices\\&min AHP indices} - {V} - {none} - {Voltage value of the action potential onset relative to the subsequent AHP} - { - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[begin$_i$] - V[ahp$_i$] TO fast\_AHP \- \\ - ENDFOR - } - Iterate over \myid{AP begin indices} and yield the difference of \myid{V} at the \myid{AP begin index} and the \myid{min AHP index}. - -\end{efeature} - -\begin{efeature} - {AP amplitude change} - {LibV2} - {AP\_amplitude\_change} - {none} - {peak voltage} - {none} - {none} - {Difference of the amplitudes of the second and the first action potential divided by the amplitude of the first action potential} - { - amp$_0, \ldots, $amp$_{n-1} =$ AP\_amplitude\_change \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (amp$_{i+1}$ - amp$_0$) / amp$_0$ TO AP\_amplitude\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {AP duration change} - {LibV2} - {AP\_duration\_change} - {none} - {AP duration} - {none} - {none} - {Difference of the durations of the second and the first action potential divided by the duration of the first action potential} - { - dur$_0, \ldots, $dur$_{n-1} =$ AP\_duration \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (dur$_{i+1}$ - dur$_0$) / dur$_0$ TO AP\_duration\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {AP rise rate change} - {LibV2} - {AP\_rise\_rate\_change} - {none} - {AP rise rate} - {none} - {none} - {Difference of the rise rates of the second and the first action potential divided by the rise rate of the first action potential} - { - rr$_0, \ldots, $rr$_{n-1} =$ AP\_rise\_rate \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (rr$_{i+1}$ - rr$_0$) / rr$_0$ TO AP\_rise\_rate\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {AP fall rate change} - {LibV2} - {AP\_fall\_rate\_change} - {none} - {AP fall rate} - {none} - {none} - {Difference of the fall rates of the second and the first action potential divided by the fall rate of the first action potential} - { - fr$_0, \ldots, $fr$_{n-1} =$ AP\_fall\_rate \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (fr$_{i+1}$ - fr$_0$) / fr$_0$ TO AP\_fall\_rate\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {fast AHP change} - {LibV2} - {fast\_AHP\_change} - {none} - {fast AHP} - {none} - {none} - {Difference of the \myid{fast AHP} of the second and the first action potential divided by the \myid{fast AHP} of the first action potential} - { - fahp$_0, \ldots, $fahp$_{n-1} =$ fast\_AHP \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (fahp$_{i+1}$ - fahp$_0$) / fahp$_0$ TO fast\_AHP\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {AP duration half width change} - {LibV2} - {AP\_duration\_half\_width\_change} - {none} - {AP duration half width} - {none} - {none} - {Difference of the FWHM of the second and the first action potential divided by the FWHM of the first action potential} - { - dhw$_0, \ldots, $dhw$_{n-1} =$ AP\_duration\_half\_width \\ - FOR $i = 0, \dots, n - 2$ DO \+ \\ - APPEND (dhw$_{i+1}$ - dhw$_0$) / dhw$_0$ TO AP\_duration\_half\_width\_change \- \\ - ENDFOR - } - -\end{efeature} - -\begin{efeature} - {steady state hyper} - {LibV2} - {steady\_state\_hyper} - {mV} - {none} - {V\\&T\\&stim end} - {none} - {Steady state voltage during hyperpolarization} - { - end\_index = minimal $i$ WITH t$_i \ge$ stim\_end \\ - mean = mean of V[$i$] WITH end\_index - 35 $\le i <$ end\_index - 5 \\ - APPEND mean TO steady\_state\_hyper - } - Find the voltage trace index at \myid{stim end}. - Yield the average of \myid{V} between that index minus 35 and that index minus 5. - -\end{efeature} - -\begin{efeature} - {amp drop first second} - {LibV2} - {amp\_drop\_first\_second} - {double} - {peak voltage} - {none} - {none} - {Difference of the amplitude of the first and the second peak} - { - IF length of peak\_voltage < 2 THEN \+ \\ - FAIL "At least 2 spikes needed for the calculation of amp\_drop\_first\_second." \- \\ - ENDIF \\ - APPEND peak\_voltage[0] - peak\_voltage[1] TO amp\_drop\_first\_second - } - -\end{efeature} - -\begin{efeature} - {amp drop first last} - {LibV2} - {amp\_drop\_first\_last} - {double} - {peak voltage} - {none} - {none} - {Difference of the amplitude of the first and the last peak} - { - IF length of peak\_voltage < 2 THEN \+ \\ - FAIL "At least 2 spikes needed for the calculation of amp\_drop\_first\_last." \- \\ - ENDIF \\ - APPEND peak\_voltage[0] - peak\_voltage[last] TO amp\_drop\_first\_last - } - -\end{efeature} - -\begin{efeature} - {amp drop second last} - {LibV2} - {amp\_drop\_second\_last} - {double} - {peak voltage} - {none} - {none} - {Difference of the amplitude of the second and the last peak} - { - IF length of peak\_voltage < 3 THEN \+ \\ - FAIL "At least 3 spikes needed for the calculation of amp\_drop\_second\_last." \- \\ - ENDIF \\ - APPEND peak\_voltage[1] - peak\_voltage[last] TO amp\_drop\_second\_last - } - -\end{efeature} - -\begin{efeature} - {max amp difference} - {LibV2} - {max\_amp\_difference} - {double} - {peak voltage} - {none} - {none} - {Maximum difference of the height of two subsequent peaks} - { - IF length of peak\_voltage < 2 THEN \+ \\ - FAIL "At least 2 spikes needed for the calculation of max\_amp\_difference." \- \\ - ENDIF \\ - APPEND $\max \Delta$peak\_voltage TO max\_amp\_difference - } - -\end{efeature} -\section{Regular features} - -% efeature -% args: -% feature name -% namespace -% identifier -% type -% required features -% required trace data -% required parameters -% semantics -% detailed description / specification - -\begin{efeature} - {back-propagating AP attenuation} - {LibV2} - {BPAPatt2} - {none} - {peak voltage;location\_soma\\&voltage base;location*} - {V;location\_dend620} - {none} - {Ratio of relative heights of somatic peak and dendritic peak} - { - bpapatt = (peak\_voltage[0];location\_soma - voltage\_base;location\_soma) / ($\max_i$ V[$i$];location\_dend - voltage\_base;location\_dend) \\ - APPEND bpapatt TO BPAPatt2 - } - Inject a short square pulse at soma that invokes exactly one action potential. - The relative height of the action potential at soma is the difference of \myid{peak voltage;location\_soma} and \myid{voltage base;location\_soma}. - The recording at the dendrite takes place at the thickest apical dendrite with a distance of 620 $\mu m$ from soma. - The relative height is the difference of the maximum of \myid{V;location\_dend620} and \myid{voltage base;location\_dend620}. - Yield the relative height at soma divided by the relative height at the dendritic location. - - \mybox{remarks:}{ - There exist a \emph{hoc} implementation under the same name, where instead of a ratio the actual relative height of the peak at the dendrite is returned. - } -\end{efeature} - -\begin{efeature} - {back-propagating AP attenuation (2nd)} - {LibV2} - {BPAPatt3} - {none} - {peak voltage;location\_soma\\&voltage base;location*} - {V;location\_dend800} - {none} - {Ratio of relative heights of somatic peak and dendritic peak} - { - bpapatt = (peak\_voltage[0];location\_soma - voltage\_base;location\_soma) / ($\max_i$ V[$i$];location\_dend - voltage\_base;location\_dend) \\ - APPEND bpapatt TO BPAPatt3 - } - Inject a short square pulse at soma that invokes exactly one action potential. - The relative height of the action potential at soma is the difference of \myid{peak voltage;location\_soma} and \myid{voltage base;location\_soma}. - The recording at the dendrite takes place at the thickest apical dendrite with a distance of 800 $\mu m$ from soma. - The relative height is the difference of the maximum of \myid{V;location\_dend800} and \myid{voltage base;location\_dend800}. - Yield the relative height at soma divided by the relative height at the dendritic location. - -\end{efeature} - -\begin{efeature} - {E2} - {LibV2} - {E2} - {mV} - {amp\_drop\_first\_second;APDrop*} - {none} - {none} - {Difference of the amplitude of the first and the second peak} - { - APPEND mean of amp\_drop\_first\_second;APDrop* TO E2 - } - Take the mean of \myid{peak voltage} over all repetitions of the stimulus protocol \myid{APDrop} for the first and the second peak. - Yield the difference. - -\end{efeature} - -\begin{efeature} - {E3} - {LibV2} - {E3} - {mV} - {amp\_drop\_first\_last;APDrop*} - {none} - {none} - {Difference of the amplitude of the first and the last peak} - { - APPEND mean of amp\_drop\_first\_last;APDrop* TO E3 - } - Take the mean of \myid{peak voltage} over all repetitions of the stimulus protocol \myid{APDrop} for the first and the last peak. - Yield the difference. - -\end{efeature} - -\begin{efeature} - {E4} - {LibV4} - {E4} - {mV} - {amp\_drop\_second\_last;APDrop*} - {none} - {none} - {Difference of the amplitude of the second and the last peak} - { - APPEND mean of amp\_drop\_second\_last;APDrop* TO E4 - } - Take the mean of \myid{peak voltage} over all repetitions of the stimulus protocol \myid{APDrop} for the second and the last peak. - Yield the difference. - -\end{efeature} - -\begin{efeature} - {E5} - {LibV2} - {E5} - {mV} - {max\_amp\_difference;APDrop*} - {none} - {none} - {Maximum difference of the height of two subsequent peaks} - { - APPEND mean of max\_amp\_difference;APDrop* TO E5 - } - Take the mean of \myid{peak voltage} over all repetitions of the stimulus protocol \myid{APDrop} for each peak. - Yield the biggest difference between two peaks. - -\end{efeature} - -\begin{efeature} - {E6 (AP amplitude)} - {LibV2} - {E6} - {mV} - {AP amplitude;APWaveForm*} - {none} - {none} - {Relative height of the first action potential} - { - APPEND mean of AP\_amplitude;APWaveForm* at index 0 TO E6 - } - Take the mean of the first \myid{AP amplitude} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E7 (AP duration)} - {LibV2} - {E7} - {ms} - {AP duration;APWaveForm*} - {none} - {none} - {Duration of the first action potential} - { - APPEND mean of AP\_duration;APWaveForm* at index 0 TO E7 - } - Take the mean of the first \myid{AP duration} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E8 (AP duration half width)} - {LibV2} - {E8} - {ms} - {AP duration half width;APWaveForm*} - {none} - {none} - {FWHM of the first action potential} - { - APPEND mean of AP\_duration\_half\_width;APWaveForm* at index 0 TO E8 - } - Take the mean of the first \myid{AP duration half width} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E9 (AP rise time)} - {LibV2} - {E9} - {ms} - {AP rise time;APWaveForm*} - {none} - {none} - {Time from onset of the first action potential to the maximum} - { - APPEND mean of AP\_rise\_time;APWaveForm* at index 0 TO E9 - } - Take the mean of the first \myid{AP rise time} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E10 (AP fall time)} - {LibV2} - {E10} - {ms} - {AP fall time;APWaveForm*} - {none} - {none} - {Time from maximum of the first action potential to offset} - { - APPEND mean of AP\_fall\_time;APWaveForm* at index 0 TO E10 - } - Take the mean of the first \myid{AP fall time} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E11 (AP rise rate)} - {LibV2} - {E11} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {AP rise rate;APWaveForm*} - {none} - {none} - {Voltage change rate during the rising phase of the first action potential} - { - APPEND mean of AP\_rise\_rate;APWaveForm* at index 0 TO E11 - } - Take the mean of the first \myid{AP rise rate} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E12 (AP fall rate)} - {LibV2} - {E12} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {AP fall rate} - {none} - {none} - {Voltage change rate during the falling phase of the first action potential} - { - APPEND mean of AP\_fall\_rate;APWaveForm* at index 0 TO E12 - } - Take the mean of the first \myid{AP fall rate} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E13 (fast AHP)} - {LibV2} - {E13} - {mV} - {fast AHP;APWaveForm*} - {none} - {none} - {Voltage value of the onset of the first action potential relative to the subsequent AHP} - { - APPEND mean of fast\_AHP;APWaveForm* at index 0 TO E13 - } - Take the mean of the first \myid{fast AHP} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E14 (AP amplitude)} - {LibV2} - {E14} - {mV} - {AP Amplitude;APWaveForm*} - {none} - {none} - {Relative height of the second action potential} - { - APPEND mean of AP\_amplitude;APWaveForm* at index 1 TO E14 - } - Take the mean of the second \myid{AP amplitude} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E15 (AP duration)} - {LibV2} - {E15} - {ms} - {AP duration;APWaveForm*} - {none} - {none} - {Duration of the second action potential} - { - APPEND mean of AP\_duration;APWaveForm* at index 1 TO E15 - } - Take the mean of the second \myid{AP duration} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E16 (AP duration half width)} - {LibV2} - {E16} - {ms} - {AP duration half width;APWaveForm*} - {none} - {none} - {FWHM of the second action potential} - { - APPEND mean of AP\_duration\_half\_width;APWaveForm* at index 1 TO E16 - } - Take the mean of the second \myid{AP duration half width} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E17 (AP rise time)} - {LibV2} - {E17} - {ms} - {AP rise time;APWaveForm*} - {none} - {none} - {Time from onset of the second action potential to the maximum} - { - APPEND mean of AP\_rise\_time;APWaveForm* at index 1 TO E17 - } - Take the mean of the second \myid{AP rise time} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E18 (AP fall time)} - {LibV2} - {E18} - {ms} - {AP fall time;APWaveForm*} - {none} - {none} - {Time from maximum of the second action potential to offset} - { - APPEND mean of AP\_fall\_time;APWaveForm* at index 1 TO E18 - } - Take the mean of the second \myid{AP fall time} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E19 (AP rise rate)} - {LibV2} - {E19} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {AP rise rate;APWaveForm*} - {none} - {none} - {Voltage change rate during the rising phase of the second action potential} - { - APPEND mean of AP\_rise\_rate;APWaveForm* at index 1 TO E19 - } - Take the mean of the second \myid{AP rise rate} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E20 (AP fall rate)} - {LibV2} - {E20} - {$\frac{\mathrm{V}}{\mathrm{s}}$} - {AP fall rate} - {none} - {none} - {Voltage change rate during the falling phase of the second action potential} - { - APPEND mean of AP\_fall\_rate;APWaveForm* at index 1 TO E20 - } - Take the mean of the second \myid{AP fall rate} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E21 (fast AHP)} - {LibV2} - {E21} - {mV} - {fast AHP;APWaveForm*} - {none} - {none} - {Voltage value of the onset of the second action potential relative to the subsequent AHP} - { - APPEND mean of fast\_AHP;APWaveForm* at index 1 TO E21 - } - Take the mean of the second \myid{fast AHP} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E22 (AP amplitude change)} - {LibV2} - {E22} - {none} - {AP amplitude change;APWaveForm*} - {none} - {none} - {Difference of the amplitudes of the second and the first action potential divided by the amplitude of the first action potential} - { - APPEND mean of AP\_amplitude\_change;APWaveForm* at index 0 TO E22 - } - Take the mean of \myid{AP amplitude change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E23 (AP duration change)} - {LibV2} - {E23} - {none} - {AP duration change;APWaveForm*} - {none} - {none} - {Difference of the durations of the second and the first action potential divided by the duration of the first action potential} - { - APPEND mean of AP\_duration\_change;APWaveForm* at index 0 TO E23 - } - Take the mean of \myid{AP duration change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E24 (AP duration half width change)} - {LibV2} - {E24} - {none} - {AP duration half width change;APWaveForm*} - {none} - {none} - {Difference of the FWHM of the second and the first action potential divided by the FWHM of the first action potential} - { - APPEND mean of AP\_duration\_half\_width\_change;APWaveForm* at index 0 TO E24 - } - Take the mean of \myid{AP duration half width change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E25 (AP rise rate change)} - {LibV2} - {E25} - {none} - {AP rise rate change;APWaveForm*} - {none} - {none} - {Difference of the rise rates of the second and the first action potential divided by the rise rate of the first action potential} - { - APPEND mean of AP\_rise\_rate\_change;APWaveForm* at index 0 TO E25 - } - Take the mean of \myid{AP rise rate change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E26 (AP fall rate change)} - {LibV2} - {E26} - {none} - {AP fall rate change;APWaveForm*} - {none} - {none} - {Difference of the fall rates of the second and the first action potential divided by the fall rate of the first action potential} - { - APPEND mean of AP\_fall\_rate\_change;APWaveForm* at index 0 TO E26 - } - Take the mean of \myid{AP fall rate change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E27 (fast AHP change)} - {LibV2} - {E27} - {none} - {fast AHP change;APWaveForm*} - {none} - {none} - {Difference of the \myid{fast AHP} of the second and the first action potential divided by the \myid{fast AHP} of the first action potential} - { - APPEND mean of fast\_AHP\_change;APWaveForm* at index 0 TO E27 - } - Take the mean of \myid{fast AHP change} over all repetitions and iterations of the stimulus protocol \myid{APWaveForm}. - -\end{efeature} - -\begin{efeature} - {E39} - {LibV2} - {E39} - {$\frac{\mathrm{Hz}}{\mathrm{nA}}$} - {mean frequency;IDthreshold*} - {stimulus current;IDthreshold*} - {none} - {The slope of a linear fit of the curve \myid{mean frequency} vs. \myid{stimulus current}} - { - FOR suffix IN IDthreshold* DO \+ \\ - APPEND stimulus\_current;suffix TO x \\ - APPEND mean\_frequency;suffix TO y \- \\ - ENDFOR \\ - slope, residuals, $R^2$ = fit\_straight\_line(x, y) \\ - APPEND slope TO E39 - } - Get the points (\myid{stimulus current}, \myid{mean frequency}) for all repetitions and iterations of the stimulus protocol \myid{IDthreshold} and fit a straight line. - - \mybox{remarks:}{ - A straight line generally is not an appropriate fit to the described curve. - } -\end{efeature} - -\begin{efeature} - {E39: coefficient of determination} - {LibV2} - {E39\_cod} - {none} - {E39} - {none} - {none} - {The coefficient of determination (often: R$^2$) of the straight line fit according to E39} - { - FOR suffix IN IDthreshold* DO \+ \\ - APPEND stimulus\_current;suffix TO x \\ - APPEND steady\_state\_hyper;suffix TO y \- \\ - ENDFOR \\ - slope, residuals, $R^2$ = fit\_straight\_line(x, y) \\ - APPEND $R^2$ TO E39\_cod - } - -\end{efeature} - -\begin{efeature} - {E40 (time to first spike)} - {LibV2} - {E40} - {ms} - {time to first spike;IDrest*} - {none} - {none} - {Average time from the begin of the stimulus to the maximum of the first peak} - { - APPEND mean of time\_to\_first\_spike;IDrest* at index 0 TO E40 - } - Take the mean of \myid{time to first spike} over all repetitions and iterations of the stimulus protocol \myid{IDrest} - -\end{efeature} - -\section{LibV5} - -\begin{efeature} - {AP begin voltage} - {LibV5} - {AP\_begin\_voltage} - {mV} - {AP\_begin\_indices} - {V\\&T\\&stim start\\&stim end} - {none} - {Voltage values at the onset of each action potential} - { - begin$_0, \ldots, $begin$_{n-1} =$ AP\_begin\_indices \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND V[begin$_{i}$] TO AP\_begin\_voltage \- \\ - ENDFOR - } - Return the voltage levels at \myid{AP\_begin\_indices}. - -\end{efeature} - -\begin{efeature} - {AHP time from peak} - {LibV5} - {AHP\_time\_from\_peak} - {ms} - {min\_AHP\_indices\\&peak\_indices} - {V\\&T\\&stim start\\&stim end} - {none} - {Time between AP peaks and AHP depths} - { - peak$_0, \ldots, $peak$_{m-1} =$ peak\_indices \\ - ahp$_0, \ldots, $ahp$_{n-1} =$ min\_AHP\_indices \\ - IF $m$ > $n$ THEN \+ \\ - FAIL \- \\ - ENDIF \\ - FOR $i = 0, \dots, m - 1$ DO \+ \\ - APPEND T[ahp$_i$] $-$ T[peak$_i$] TO AHP\_time\_from\_peak \- \\ - ENDFOR - } - Obtain the \myid{min\_AHP\_indices} and \myid{peak\_indices}, and calculate - the time between these indices in the T array. - -\end{efeature} - -\begin{efeature} - {AP amplitude from voltagebase} - {LibV1} - {AP\_amplitude\_from\_voltagebase} - {mV} - {voltage base\\&peak voltage} - {V} - {none} - {The height of the action potential measured from voltage base} - { - pv$_0, \ldots, $pv$_{n-1} =$ peak\_voltage \\ - FOR $i = 0, \dots, n - 1$ DO \+ \\ - APPEND pv$_i$ - voltage\_base TO AP\_amplitude\_from\_voltagebase \- \\ - ENDFOR - } - Yield the difference of \myid{peak voltage} and \myid{voltage base} for each peak. - -\end{efeature} diff --git a/efel/settings.py b/efel/settings.py index 64a10be6..0c22fbed 100644 --- a/efel/settings.py +++ b/efel/settings.py @@ -35,11 +35,11 @@ class Settings: Attributes: Threshold (float): Spike detection threshold (default: -20.0). DerivativeThreshold (float): Threshold value for derivative calculations - (default: 10.0). + (default: 10.0). DownDerivativeThreshold (float): Threshold value for downward derivative - calculations (default: -12.0). + calculations (default: -12.0). dependencyfile_path (str): Path to the dependency file - (default: 'DependencyV5.txt'). + (default: 'DependencyV5.txt'). spike_skipf (float): Fraction of spikes to skip (default: 0.1). max_spike_skip (int): Maximum number of spikes to skip (default: 2). interp_step (float): Interpolation step (default: 0.1). @@ -55,7 +55,7 @@ class Settings: min_spike_height (float): Minimum spike height (default: 20.0). strict_stiminterval (bool): Strict stimulus interval (default: False). initburst_freq_threshold (int): Initial burst frequency threshold - (default: 50) + (default: 50). initburst_sahp_start (int): Initial burst SAHP start (default: 5). initburst_sahp_end (int): Initial burst SAHP end (default: 100). DerivativeWindow (int): Derivative window (default: 3). @@ -66,7 +66,7 @@ class Settings: ignore_first_ISI (bool): Ignore first ISI (default: True). impedance_max_freq (float): Impedance maximum frequency (default: 50.0). inactivation_tc_end_skip (int): number of data points to skip before - stim end for inactivation_time_constant feature + stim end for inactivation_time_constant feature (default: 10). """ Threshold: float = -20.0 diff --git a/efel/units/units.json b/efel/units/units.json index e2cc0a89..a63d0301 100644 --- a/efel/units/units.json +++ b/efel/units/units.json @@ -159,5 +159,29 @@ "pos_image": "constant", "activation_time_constant": "ms", "deactivation_time_constant": "ms", - "inactivation_time_constant": "ms" + "inactivation_time_constant": "ms", + "check_ais_initiation": "constant", + "bpap_attenuation": "constant", + "peak_indices": "constant", + "AP_rise_indices": "constant", + "AP_end_indices": "constant", + "AP_fall_indices": "constant", + "min_AHP_indices": "constant", + "AP_begin_indices": "constant", + "min_between_peaks_indices": "constant", + "burst_begin_indices": "constant", + "burst_end_indices": "constant", + "ADP_peak_indices": "constant", + "interburst_min_indices": "constant", + "postburst_min_indices": "constant", + "postburst_slow_ahp_indices": "constant", + "postburst_fast_ahp_indices": "constant", + "postburst_adp_peak_indices": "constant", + "interburst_15percent_indices": "constant", + "interburst_20percent_indices": "constant", + "interburst_25percent_indices": "constant", + "interburst_30percent_indices": "constant", + "interburst_40percent_indices": "constant", + "interburst_60percent_indices": "constant", + "burst_ISI_indices": "constant" } diff --git a/examples/settings/settings_notebook.ipynb b/examples/settings/settings_notebook.ipynb new file mode 100644 index 00000000..426136d6 --- /dev/null +++ b/examples/settings/settings_notebook.ipynb @@ -0,0 +1,284 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to set the settings\n", + "\n", + "A lot of features in efel can have their behaviour changed with settings, in order to be more customisable, depending on the traces and the need of the user. Here, we are going to see how to change the settings with an exmaple, and where to find the list of all the settings in efel with their default values." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "For this example, we are going to use a trace of a cell model with a bAP (backpropagating action potential) stimulus, recorded in the dendrite." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import efel\n", + "\n", + "import numpy\n", + "import json\n", + "\n", + "%matplotlib notebook\n", + "%matplotlib inline\n", + "from matplotlib import pyplot as plt\n", + "plt.rcParams['figure.figsize'] = 10, 10\n", + "\n", + "test_url = '../../tests/testdata/allfeatures/testbap2data.txt'\n", + "test_data = numpy.loadtxt(test_url)\n", + "time = test_data[:,0]\n", + "voltage = test_data[:, 1]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now plot this data" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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bgt67JKSEhUUFBz24wEAIiN4UEXL9/Mf/ltd61VljUdJic7oFgYAQCMhD6pwOBwaPny4hg8fHvaTT58+XZdddtkh79OnT59mrx9//PGqra3Vt99+qwEDBig/P1+7du0Kuo//6/z8/BYf3+12hxXKAADR0dpBFamuBFmWb7lgSWUNoQoA0OZiOv0vJydHOTk5h/W9q1atksPhUG5uriRp9OjRuv3221VTUxM4kHj+/PkaMGBAi0v/AADmau2gCofDUpo7QSWVtSqpqFVudLb1AgDQopDPqYqFpUuX6ne/+51Wr16tTZs2ac6cOZo2bZouvvjiQGC68MIL5XK5NGXKFK1bt06vvvqqHnvssaClfQCA+GE3SFWHClVS/b6qUoZVAABiIC7OqXK73XrllVd09913q6qqSr1799a0adOCAlNGRobmzZunqVOnauTIkcrOztbMmTM5owoA4pS3Qafq0JHKfwBwBcMqAAAxEReh6phjjtHHH3/8vfcbOnSoFi9e3AYVAQCizW7lniqp/qyqEs6qAgDEwGEv/9u4caPmzp0bOBS44ZsfAADhCupUtXr5H50qAEDbCzlU7du3T6eddpr69++v//mf/9HOnTslSVOmTNH06dMjXiAAoGOy64aqH+rgX7/0JA4ABgDETsihatq0aUpISNDWrVuVkpISuH7++efr3XffjWhxAICOy78A4vu6VFL9WVUs/wMAxELIe6rmzZunuXPnqnv37kHX+/Xrpy1btkSsMABAx+YPVa3qVCXTqQIAxE7InaqysrKgDpVfUVERh+gCACLGf/hvazpV/kEV7KkCAMRCyKHqpJNO0ksvvRT42rIseb1ePfjgg/rBD34Q0eIAAB1XIFS14r6BThXL/wAAMRDy8r8HH3xQ48eP12effabq6mr96le/0rp161RUVKQPP/wwGjUCADqg+uV/relU+ULVAUIVACAGQu5UHX300dqwYYNOPPFE/fjHP1ZZWZnOPfdcrVy5UkceeWQ0agQAdED1gyq+/75ZKb5QVVxOqAIAtL3DOvw3IyNDt99+e6RrAQAgoH6k+venqswUlyRpf3l1VGsCAKA5IYeqNWvWNHvdsiwlJSWpR48eDKwAAITNf/hva/ZUBTpVFTXyem05WjMyEACACAk5VA0fPjwwicluZjJTYmKizj//fP3pT39SUlJShMoEAHQ09e8x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+ul2cnKynZ2dbU+fPt2uqalp479N+zZ58mS7Z8+etsvlsnNycuzx48cHApVt8zqZrHGo4rUyw/nnn2937drVdrlc9hFHHGGff/759saNGwO38zqZ5T//+Y999NFH22632x44cKD99NNPB93OzxXxzbJt245NjwwAAAAA4h97qgAAAAAgDIQqAAAAAAgDoQoAAAAAwkCoAgAAAIAwEKoAAAAAIAyEKgAAAAAIA6EKAAAAAMJAqAIAAACAMBCqAABx5bLLLtM555wTs+efNGmSHnjggag9/hdffKHu3burrKwsas8BAIgsy7ZtO9ZFAAAgSZZlHfL2u+66S9OmTZNt28rMzGybohpYvXq1Tj31VG3ZskWpqalRe56f/vSnGjZsmO68886oPQcAIHIIVQAAYxQWFgY+f/XVVzVz5kytX78+cC01NTWqYeb7XHHFFUpISNBTTz0V1ed56623dOWVV2rr1q1KSEiI6nMBAMLH8j8AgDHy8/MDHxkZGbIsK+haampqk+V/p5xyiq6//nrddNNNysrKUl5enp555hmVlZXp8ssvV1pamvr27at33nkn6Lk+//xznX766UpNTVVeXp4mTZqkvXv3tlibx+PR//3f/+mss84Kut6rVy/dd999uuSSS5SamqqePXvq3//+t/bs2aMf//jHSk1N1dChQ/XZZ58FvmfLli0666yzlJWVpU6dOmnw4MF6++23A7f/8Ic/VFFRkRYtWhTmP1EAQFsgVAEA4t6LL76o7OxsffLJJ7r++ut17bXX6rzzztOYMWO0YsUKTZgwQZMmTVJ5ebkk6cCBAzr11FM1YsQIffbZZ3r33Xe1a9cu/exnP2vxOdasWaPi4mKNGjWqyW2//e1vNXbsWK1cuVJnnHGGJk2apEsuuUQXX3yxVqxYoSOPPFKXXHKJ/ItDpk6dqqqqKn3wwQdau3atfvOb3wR14Fwul4YPH67FixdH+J8UACAaCFUAgLg3bNgw3XHHHerXr59uu+02JSUlKTs7W1deeaX69eunmTNnat++fVqzZo0k6Q9/+INGjBihBx54QAMHDtSIESP0/PPPa+HChdqwYUOzz7FlyxY5nU7l5uY2ue1//ud/dPXVVweeq6SkRMcee6zOO+889e/fX7feequ+/PJL7dq1S5K0detWjR07VkOGDFGfPn105plnaty4cUGP2a1bN23ZsiXC/6QAANFAqAIAxL2hQ4cGPnc6nerSpYuGDBkSuJaXlydJ2r17tyTfwImFCxcG9milpqZq4MCBkqRvvvmm2eeoqKiQ2+1udphGw+f3P9ehnv+GG27Qfffdp7Fjx+quu+4KhL2GkpOTA501AIDZCFUAgLiXmJgY9LVlWUHX/EHI6/VKkg4ePKizzjpLq1atCvr4+uuvm3SM/LKzs1VeXq7q6upDPr//uQ71/FdccYU2bdqkSZMmae3atRo1apQef/zxoMcsKipSTk5O6/4BAABiilAFAOhwjjnmGK1bt069evVS3759gz46derU7PcMHz5cku8cqUgoKCjQNddco3/+85+aPn26nnnmmaDbP//8c40YMSIizwUAiC5CFQCgw5k6daqKiop0wQUX6NNPP9U333yjuXPn6vLLL5fH42n2e3JycnTMMcdoyZIlYT//TTfdpLlz52rz5s1asWKFFi5cqKOOOipw+7fffqvt27frtNNOC/u5AADRR6gCAHQ43bp104cffiiPx6MJEyZoyJAhuummm5SZmSmHo+W3xiuuuEJz5swJ+/k9Ho+mTp2qo446Sj/60Y/Uv39//fGPfwzc/vLLL2vChAnq2bNn2M8FAIg+Dv8FAKCVKioqNGDAAL366qsaPXp0VJ6jurpa/fr109/+9jeNHTs2Ks8BAIgsOlUAALRScnKyXnrppUMeEhyurVu36v/9v/9HoAKAOEKnCgAAAADCQKcKAAAAAMJAqAIAAACAMBCqAAAAACAMhCoAAAAACAOhCgAAAADCQKgCAAAAgDAQqgAAAAAgDIQqAAAAAAgDoQoAAAAAwvD/AUy8/vHKTQKAAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.rcParams['figure.figsize'] = 10, 10\n", + "\n", + "fig1, ax1 = plt.subplots(1)\n", + "ax1.plot(time, voltage)\n", + "ax1.set_xlabel('Time (ms)')\n", + "ax1.set_ylabel('Membrane voltage (mV)');" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let us extract the peak time. We expect one value shortly after 300 ms." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spike detection threshold is -20.0 mV.\n", + "{'peak_time': None}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/path/to/efel/pyfeatures/cppfeature_access.py:14: RuntimeWarning: Error while calculating peak_time, An error occurred while computing the feature, feature is not found. Voltage never goes below or above threshold in spike detection.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "stim_start = 295\n", + "stim_end = 500\n", + "trace = {'T': time, 'V': voltage, 'stim_start': [stim_start], 'stim_end': [stim_end]}\n", + "\n", + "feature_values = efel.get_feature_values([trace], ['peak_time'])[0]\n", + "print(f'Spike detection threshold is {efel.get_settings().Threshold} mV.')\n", + "print(feature_values)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that we don't have any value returned. The reason is that, by default, eFEL looks for spikes that go above -20 mV. This is fine for most cells when recorded in the soma, but here, we are recording a spike in the dendrite after a soma stimulus. This spike has a smaller amplitude than the one in the soma, and is thus below the spike-detecting threshold.\n", + "\n", + "This can be solved by modifying the settings, using the easy to use set_setting function! By simply modifying the default threshold value of -20 to a lower value, e.g. -30, the spike gets detected!" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'peak_time': array([304.])}\n" + ] + } + ], + "source": [ + "efel.api.set_setting('Threshold', -30.)\n", + "\n", + "feature_values = efel.get_feature_values([trace], ['peak_time'])[0]\n", + "print(feature_values)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to see the current settings, you can do so by using the get_settings function:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Threshold: -30.0\n", + "DerivativeThreshold: 10.0\n", + "DownDerivativeThreshold: -12.0\n", + "dependencyfile_path: /path/to/efel/DependencyV5.txt\n", + "spike_skipf: 0.1\n", + "max_spike_skip: 2\n", + "interp_step: 0.1\n", + "burst_factor: 1.5\n", + "strict_burst_factor: 2.0\n", + "voltage_base_start_perc: 0.9\n", + "voltage_base_end_perc: 1.0\n", + "current_base_start_perc: 0.9\n", + "current_base_end_perc: 1.0\n", + "rise_start_perc: 0.0\n", + "rise_end_perc: 1.0\n", + "initial_perc: 0.1\n", + "min_spike_height: 20.0\n", + "strict_stiminterval: False\n", + "initburst_freq_threshold: 50\n", + "initburst_sahp_start: 5\n", + "initburst_sahp_end: 100\n", + "DerivativeWindow: 3\n", + "voltage_base_mode: mean\n", + "current_base_mode: mean\n", + "precision_threshold: 1e-10\n", + "sahp_start: 5.0\n", + "ignore_first_ISI: True\n", + "impedance_max_freq: 50.0\n", + "AP_phaseslope_range: 2\n", + "inactivation_tc_end_skip: 10\n" + ] + } + ], + "source": [ + "print(efel.get_settings())" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can reset the settings to their default value at any time using the reset() function. It is good practice to use it whenever you want to change the settings, in order to be sure that previously set settings are not interfering with your new settings." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "efel.reset()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The settings can also be passed down to efel by passing them through the trace dictionary, inside a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'peak_time': array([304.])}\n" + ] + } + ], + "source": [ + "trace['Threshold'] = [-30.]\n", + "feature_values = efel.get_feature_values([trace], ['peak_time'])[0]\n", + "print(feature_values)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The complete list of settings and their default value can be found in the documentation of [the Settings class here](https://efel.readthedocs.io/en/latest/_autosummary/efel.settings.html#efel.settings.Settings)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/pyproject.toml b/pyproject.toml index fa5343b7..6a881f07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,4 +10,11 @@ exclude_lines = [ ] [tool.pytest.ini_options] -filterwarnings = ["ignore::DeprecationWarning"] \ No newline at end of file +filterwarnings = ["ignore::DeprecationWarning"] + +[tool.cibuildwheel] +test-requires = "pytest neo[neomatlabio]>=0.5.1 pytest-xdist>=3.3.1" +test-command = "pytest -sx -n auto {project}/tests" + +[tool.cibuildwheel.windows] +test-command = "pytest -sx -n auto {project}\\tests" \ No newline at end of file diff --git a/tox.ini b/tox.ini index 887183f2..a611f166 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ python = 3.10: py3 3.11: py3 3.12: py3 + 3.13: py3 [testenv] @@ -88,7 +89,7 @@ commands = envdir = {toxworkdir}/lint deps = pycodestyle>=2.11.0 - mypy>=1.8.0 + mypy>=1.8.0,<1.12 commands = pycodestyle --ignore=E402,W503,W504 --exclude=_version.py --max-line-length=88 efel tests mypy efel tests --ignore-missing-imports