Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.2.9 #982

Merged
merged 25 commits into from
May 21, 2024
Merged

0.2.9 #982

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c1dfbe1
bump version
DominiqueMakowski Apr 6, 2024
09dddbb
Merge branch 'master' into dev
DominiqueMakowski Apr 6, 2024
c43c747
remove codeclimate icon from readme
DominiqueMakowski Apr 6, 2024
4fb624a
fix tests and GH actions
DominiqueMakowski Apr 10, 2024
2d17cde
added PPG quality assessment functionality and two initial quality as…
peterhcharlton May 7, 2024
a84d14d
fixing style: line lengths and unused packages
peterhcharlton May 7, 2024
fbd0136
Added test for nk.cor
ujdcodr May 9, 2024
57bc9da
Removed negative test due to flake8 F841
ujdcodr May 9, 2024
bf8d260
Rewrote negative test to resolve flake8 warning
ujdcodr May 11, 2024
52390a3
added comment for negative test
ujdcodr May 11, 2024
a5ec390
specify zero_mean=False to address FutureWarning
danibene May 19, 2024
1cd76e4
add peaks back to signals dataframe
danibene May 19, 2024
079f978
split up string to reduce line length
danibene May 19, 2024
36fc948
match syntax of entropy_phase.py for get_cmap
danibene May 19, 2024
5532779
change other instances of get_cmap
danibene May 19, 2024
dcc9d08
remove unused imports
danibene May 19, 2024
4a4b658
use "resampled" https://github.com/matplotlib/matplotlib/issues/20853
danibene May 19, 2024
ff44270
set "navigation_with_keys" to False to address failing doc check
danibene May 19, 2024
8dc4e50
Merge pull request #989 from ujdcodr/add_test_stats_cor
DominiqueMakowski May 20, 2024
b3e3828
Merge pull request #991 from danibene/fix/get_cmap
danibene May 20, 2024
836f62a
get array of colors instead of colormap object
danibene May 20, 2024
7adde35
Merge branch 'dev' into add_ppg_quality
danibene May 20, 2024
64eead6
split text up into multiple lines
danibene May 20, 2024
dddc8e0
close parenthesis
danibene May 20, 2024
1dd967b
Merge pull request #987 from peterhcharlton/add_ppg_quality
danibene May 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading