Skip to content

Commit

Permalink
Merge pull request #982 from neuropsychology/dev
Browse files Browse the repository at this point in the history
0.2.9
  • Loading branch information
DominiqueMakowski authored May 21, 2024
2 parents e3f4469 + 1dd967b commit 1aa8dee
Show file tree
Hide file tree
Showing 18 changed files with 357 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/releasePR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ jobs:
run: python setup.py sdist bdist_wheel

- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TEST_PYPI_PASSWORD }}
repository_url: https://test.pypi.org/legacy/

- name: Publish distribution 📦 to PyPI
#if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PASSWORD }}
5 changes: 2 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
.. image:: https://codecov.io/gh/neuropsychology/NeuroKit/branch/master/graph/badge.svg
:target: https://codecov.io/gh/neuropsychology/NeuroKit

.. image:: https://api.codeclimate.com/v1/badges/517cb22bd60238174acf/maintainability
:target: https://codeclimate.com/github/neuropsychology/NeuroKit/maintainability
:alt: Maintainability



**The Python Toolbox for Neurophysiological Signal Processing**
Expand All @@ -44,6 +42,7 @@ Quick Example
# Compute relevant features
results = nk.bio_analyze(processed_data, sampling_rate=100)
And **boom** 💥 your analysis is done 😎

Download
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def find_version():
"use_edit_page_button": True,
"logo_only": True,
"show_toc_level": 1,
"navigation_with_keys": False,
}


Expand Down
2 changes: 1 addition & 1 deletion neurokit2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .video import *

# Info
__version__ = "0.2.8"
__version__ = "0.2.9"


# Maintainer info
Expand Down
3 changes: 2 additions & 1 deletion neurokit2/complexity/entropy_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ def entropy_phase(signal, delay=1, k=4, show=False, **kwargs):
Tx = Tx.astype(bool)
Ys = np.sin(angles) * limx * np.sqrt(2)
Xs = np.cos(angles) * limx * np.sqrt(2)
colors = plt.get_cmap("jet")(np.linspace(0, 1, k))
resampled_cmap = plt.get_cmap("jet").resampled(k)
colors = resampled_cmap(np.linspace(0, 1, k))

plt.figure()
for i in range(k):
Expand Down
3 changes: 1 addition & 2 deletions neurokit2/events/events_plot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import matplotlib.cm
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
Expand Down Expand Up @@ -127,7 +126,7 @@ def events_plot(events, signal=None, color="red", linestyle="--"):
else:
# Convert color and style to list
if isinstance(color, str):
color_map = matplotlib.cm.get_cmap("rainbow")
color_map = plt.get_cmap("rainbow")
color = color_map(np.linspace(0, 1, num=len(events)))
if isinstance(linestyle, str):
linestyle = np.full(len(events), linestyle)
Expand Down
2 changes: 1 addition & 1 deletion neurokit2/hrv/hrv_nonlinear.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def _hrv_nonlinear_show(rri, rri_time=None, rri_missing=False, out={}, ax=None,
kernel = scipy.stats.gaussian_kde(values)
f = np.reshape(kernel(positions).T, xx.shape)

cmap = matplotlib.cm.get_cmap("Blues", 10)
cmap = plt.get_cmap("Blues").resampled(10)
ax.contourf(xx, yy, f, cmap=cmap)
ax.imshow(np.rot90(f), extent=[ax1_min, ax1_max, ax2_min, ax2_max], aspect="auto")

Expand Down
2 changes: 1 addition & 1 deletion neurokit2/microstates/microstates_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def microstates_plot(microstates, segmentation=None, gfp=None, info=None, epoch=
if epoch is None:
epoch = (0, len(gfp))

cmap = plt.cm.get_cmap("plasma", n)
cmap = plt.get_cmap("plasma").resampled(n)
# Plot the GFP line above the area
ax["GFP"].plot(
times[epoch[0] : epoch[1]], gfp[epoch[0] : epoch[1]], color="black", linewidth=0.5
Expand Down
2 changes: 1 addition & 1 deletion neurokit2/misc/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def summarize_table(signals):
def text_combine(info):
"""Reformat dictionary describing processing methods as strings to be inserted into HTML file."""
preprocessing = '<h2 style="background-color: #FB1CF0">Preprocessing</h1>'
for key in ["text_cleaning", "text_peaks"]:
for key in ["text_cleaning", "text_peaks", "text_quality"]:
if key in info.keys():
preprocessing += info[key] + "<br>"
ref = '<h2 style="background-color: #FBB41C">References</h1>'
Expand Down
2 changes: 2 additions & 0 deletions neurokit2/ppg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .ppg_peaks import ppg_peaks
from .ppg_plot import ppg_plot
from .ppg_process import ppg_process
from .ppg_quality import ppg_quality
from .ppg_segment import ppg_segment
from .ppg_simulate import ppg_simulate

Expand All @@ -23,6 +24,7 @@
"ppg_rate",
"ppg_process",
"ppg_plot",
"ppg_quality",
"ppg_methods",
"ppg_intervalrelated",
"ppg_eventrelated",
Expand Down
60 changes: 56 additions & 4 deletions neurokit2/ppg/ppg_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
from ..misc.report import get_kwargs
from .ppg_clean import ppg_clean
from .ppg_findpeaks import ppg_findpeaks
from .ppg_quality import ppg_quality


def ppg_methods(
sampling_rate=1000,
method="elgendi",
method_cleaning="default",
method_peaks="default",
method_quality="default",
**kwargs,
):
"""**PPG Preprocessing Methods**
Expand Down Expand Up @@ -38,6 +40,11 @@ def ppg_methods(
will be set to the value of ``"method"``. Defaults to ``"default"``.
For more information, see the ``"method"`` argument
of :func:`.ppg_findpeaks`.
method_quality: str
The method used to assess PPG signal quality. If ``"default"``,
will be set to the value of ``"templatematch"``. Defaults to ``"templatematch"``.
For more information, see the ``"method"`` argument
of :func:`.ppg_quality`.
**kwargs
Other arguments to be passed to :func:`.ppg_clean` and
:func:`.ppg_findpeaks`.
Expand All @@ -51,15 +58,17 @@ def ppg_methods(
See Also
--------
ppg_process, ppg_clean, ppg_findpeaks
ppg_process, ppg_clean, ppg_findpeaks, ppg_quality
Examples
--------
.. ipython:: python
import neurokit2 as nk
methods = nk.ppg_methods(sampling_rate=100, method="elgendi", method_cleaning="nabian2018")
methods = nk.ppg_methods(
sampling_rate=100, method="elgendi",
method_cleaning="nabian2018", method_quality="templatematch")
print(methods["text_cleaning"])
print(methods["references"][0])
Expand All @@ -71,7 +80,12 @@ def ppg_methods(
else str(method_cleaning).lower()
)
method_peaks = (
str(method).lower() if method_peaks == "default" else str(method_peaks).lower()
str(method).lower()
if method_peaks == "default"
else str(method_peaks).lower()
)
method_quality = (
str(method_quality).lower()
)

# Create dictionary with all inputs
Expand All @@ -80,16 +94,19 @@ def ppg_methods(
"method": method,
"method_cleaning": method_cleaning,
"method_peaks": method_peaks,
"method_quality": method_quality,
**kwargs,
}

# Get arguments to be passed to cleaning and peak finding functions
# Get arguments to be passed to cleaning, peak finding, and quality assessment functions
kwargs_cleaning, report_info = get_kwargs(report_info, ppg_clean)
kwargs_peaks, report_info = get_kwargs(report_info, ppg_findpeaks)
kwargs_quality, report_info = get_kwargs(report_info, ppg_quality)

# Save keyword arguments in dictionary
report_info["kwargs_cleaning"] = kwargs_cleaning
report_info["kwargs_peaks"] = kwargs_peaks
report_info["kwargs_quality"] = kwargs_quality

# Initialize refs list with NeuroKit2 reference
refs = ["""Makowski, D., Pham, T., Lau, Z. J., Brammer, J. C., Lespinasse, F., Pham, H.,
Expand Down Expand Up @@ -158,5 +175,40 @@ def ppg_methods(
"text_peaks"
] = f"The peak detection was carried out using the method {method_peaks}."

# 2. Quality
# ----------
if method_quality in ["templatematch"]:
report_info[
"text_quality"
] = (
"The quality assessment was carried out using template-matching, approximately as described "
+ "in Orphanidou et al. (2015)."
)
refs.append(
"""Orphanidou C, Bonnici T, Charlton P, Clifton D, Vallance D, Tarassenko L (2015)
Signal-quality indices for the electrocardiogram and photoplethysmogram: Derivation
and applications to wireless monitoring
IEEE Journal of Biomedical and Health Informatics 19(3): 832–838. doi:10.1109/JBHI.2014.2338351."""
)
elif method_quality in ["disimilarity"]:
report_info[
"text_quality"
] = (
"The quality assessment was carried out using a disimilarity measure of positive-peaked beats, "
+ "approximately as described in Sabeti et al. (2019)."
)
refs.append(
"""Sabeti E, Reamaroon N, Mathis M, Gryak J, Sjoding M, Najarian K (2019)
Signal quality measure for pulsatile physiological signals using
morphological features: Applications in reliability measure for pulse oximetry
Informatics in Medicine Unlocked 16: 100222. doi:10.1016/j.imu.2019.100222."""
)
elif method_quality in ["none"]:
report_info["text_quality"] = "There was no quality assessment carried out."
else:
report_info[
"text_quality"
] = f"The quality assessment was carried out using the method {method_quality}."

report_info["references"] = list(np.unique(refs))
return report_info
25 changes: 25 additions & 0 deletions neurokit2/ppg/ppg_peaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ..ecg.ecg_peaks import _ecg_peaks_plot_artefacts
from ..signal import signal_fixpeaks, signal_formatpeaks
from ..stats import rescale
from .ppg_findpeaks import ppg_findpeaks


Expand Down Expand Up @@ -143,6 +144,7 @@ def _ppg_peaks_plot(
info=None,
sampling_rate=1000,
raw=None,
quality=None,
ax=None,
):
x_axis = np.linspace(0, len(ppg_cleaned) / sampling_rate, len(ppg_cleaned))
Expand All @@ -154,6 +156,29 @@ def _ppg_peaks_plot(
ax.set_xlabel("Time (seconds)")
ax.set_title("PPG signal and peaks")

# Quality Area -------------------------------------------------------------
if quality is not None:
quality = rescale(
quality,
to=[
np.min([np.min(raw), np.min(ppg_cleaned)]),
np.max([np.max(raw), np.max(ppg_cleaned)]),
],
)
minimum_line = np.full(len(x_axis), quality.min())

# Plot quality area first
ax.fill_between(
x_axis,
minimum_line,
quality,
alpha=0.12,
zorder=0,
interpolate=True,
facecolor="#4CAF50",
label="Signal quality",
)

# Raw Signal ---------------------------------------------------------------
if raw is not None:
ax.plot(x_axis, raw, color="#B0BEC5", label="Raw signal", zorder=1)
Expand Down
1 change: 1 addition & 0 deletions neurokit2/ppg/ppg_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def ppg_plot(ppg_signals, info=None, static=True):
info=info,
sampling_rate=info["sampling_rate"],
raw=ppg_signals["PPG_Raw"].values,
quality=ppg_signals["PPG_Quality"].values,
ax=ax0,
)

Expand Down
18 changes: 16 additions & 2 deletions neurokit2/ppg/ppg_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
from .ppg_methods import ppg_methods
from .ppg_peaks import ppg_peaks
from .ppg_plot import ppg_plot
from .ppg_quality import ppg_quality


def ppg_process(
ppg_signal, sampling_rate=1000, method="elgendi", report=None, **kwargs
ppg_signal, sampling_rate=1000, method="elgendi", method_quality="templatematch", report=None, **kwargs
):
"""**Process a photoplethysmogram (PPG) signal**
Expand All @@ -26,6 +27,9 @@ def ppg_process(
method : str
The processing pipeline to apply. Can be one of ``"elgendi"``.
Defaults to ``"elgendi"``.
method_quality : str
The quality assessment approach to use. Can be one of ``"templatematch"``, ``"disimilarity"``.
Defaults to ``"templatematch"``.
report : str
The filename of a report containing description and figures of processing
(e.g. ``"myreport.html"``). Needs to be supplied if a report file
Expand Down Expand Up @@ -69,7 +73,7 @@ def ppg_process(
"""
# Sanitize input
ppg_signal = as_vector(ppg_signal)
methods = ppg_methods(sampling_rate=sampling_rate, method=method, **kwargs)
methods = ppg_methods(sampling_rate=sampling_rate, method=method, method_quality=method_quality, **kwargs)

# Clean signal
ppg_cleaned = ppg_clean(
Expand All @@ -94,12 +98,22 @@ def ppg_process(
info["PPG_Peaks"], sampling_rate=sampling_rate, desired_length=len(ppg_cleaned)
)

# Assess signal quality
quality = ppg_quality(
ppg_cleaned,
ppg_pw_peaks=info["PPG_Peaks"],
sampling_rate=sampling_rate,
method=methods["method_quality"],
**methods["kwargs_quality"]
)

# Prepare output
signals = pd.DataFrame(
{
"PPG_Raw": ppg_signal,
"PPG_Clean": ppg_cleaned,
"PPG_Rate": rate,
"PPG_Quality": quality,
"PPG_Peaks": peaks_signal["PPG_Peaks"].values,
}
)
Expand Down
Loading

0 comments on commit 1aa8dee

Please sign in to comment.