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

Add pydra tasks, workflow and update CLI #57

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,8 @@ dmypy.json
.pyre/

.vscode/

# Test Data
phys2denoise/tests/test_output_data/
phys2denoise/tests/data/fake_phys.phys
exports/
176 changes: 143 additions & 33 deletions phys2denoise/cli/run.py
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transfer the main workflow in workflow.py as that's the entry point of the CLI. This file should only contain the parser

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done @smoia

Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,28 @@


import argparse
import logging
import sys

from phys2denoise import __version__
from phys2denoise.metrics.cardiac import heart_beat_interval, heart_rate_variability
import numpy as np
import pydra
from loguru import logger

from phys2denoise import __version__, tasks, workflow
from phys2denoise.metrics.cardiac import (
cardiac_phase,
heart_beat_interval,
heart_rate,
heart_rate_variability,
)
from phys2denoise.metrics.chest_belt import (
env,
respiratory_pattern_variability,
respiratory_phase,
respiratory_variance,
respiratory_variance_time,
)
from phys2denoise.metrics.multimodal import retroicor
from phys2denoise.metrics.responses import crf, icrf, rrf


Expand Down Expand Up @@ -50,11 +63,19 @@ def _get_parser():
"physiological data, with or without extension.",
required=True,
)
required.add_argument(
"-md",
"--mode",
dest="mode",
type=str,
help="Format of the input physiological data. Options are: "
"physio or bids. Default is physio.",
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave this in for now, but let's also open an issue to remove this option and make a i/o function that automatically recognises the input type based on the given input.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be to hard, I can incorporate it for this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maestroque where are you regarding that ? If you don't have time for it there is no problem, we can definitely do what Stef suggested ! However, if you decide to do that, can you please add the default value to physio ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check this commit @smoia I partially implemented it


# Important optional arguments
optional = parser.add_argument_group("Optional arguments")
optional.add_argument(
"-outdir",
"-out",
"--output-dir",
dest="outdir",
type=str,
Expand All @@ -69,7 +90,7 @@ def _get_parser():
"--respiratory-pattern-variability",
dest="metrics",
action="append_const",
const=respiratory_pattern_variability,
const="respiratory_pattern_variability",
help="Respiratory pattern variability. Requires the following "
"input: window.",
default=[],
Expand All @@ -79,7 +100,7 @@ def _get_parser():
"--envelope",
dest="metrics",
action="append_const",
const=env,
const="env",
help="Respiratory pattern variability calculated across a sliding "
"window. Requires the following inputs: sample-rate, window and lags.",
default=[],
Expand All @@ -89,7 +110,7 @@ def _get_parser():
"--respiratory-variance",
dest="metrics",
action="append_const",
const=respiratory_variance,
const="respiratory_variance",
help="Respiratory variance. Requires the following inputs: "
"sample-rate, window and lags. If the input file "
"not a .phys file, it also requires peaks and troughs",
Expand All @@ -100,19 +121,29 @@ def _get_parser():
"--respiratory-variance-per-time",
dest="metrics",
action="append_const",
const=respiratory_variance_time,
const="respiratory_variance_time",
help="Respiratory volume-per-time. Requires the following inputs: "
"sample-rate, window, lags, peaks and troughs.",
default=[],
)
resp_met.add_argument(
"-rp",
"--respiratory-phase",
dest="metrics",
action="append_const",
const="respiratory_phase",
help="Respiratory phase. Requires the following inputs: "
"slice-timings, n_scans and t_r.",
default=[],
)

card_met = parser.add_argument_group("Cardiac signal based metrics")
card_met.add_argument(
"-hrv",
"--heart-rate-variability",
dest="metrics",
action="append_const",
const=heart_rate_variability,
const="heart_rate_variability",
help="Computes heart rate variability. Requires the following "
"inputs: peaks, samplerate, window and central measure operator.",
default=[],
Expand All @@ -122,11 +153,31 @@ def _get_parser():
"--heart-beat-interval",
dest="metrics",
action="append_const",
const=heart_beat_interval,
const="heart_beat_interval",
help="Computes heart beat interval. Requires the following "
"inputs: peaks, samplerate, window and central measure operator.",
default=[],
)
card_met.add_argument(
"-hr",
"--heart-rate",
dest="metrics",
action="append_const",
const="heart_rate",
help="Computes heart rate. Requires the following "
"inputs: peaks, samplerate, window and central measure operator.",
default=[],
)
card_met.add_argument(
"-cp",
"--cardiac-phase",
dest="metrics",
action="append_const",
const="cardiac_phase",
help="Computes cardiac phase. Requires the following "
"inputs: slice-timings, n_scans and t_r.",
default=[],
)

mmod_met = parser.add_argument_group("Multimodal signals based metrics")
mmod_met.add_argument(
Expand All @@ -150,6 +201,17 @@ def _get_parser():
default=[],
)

export_met = parser.add_argument_group("Export metrics")
export_met.add_argument(
"-e",
"--exported-metrics",
dest="metrics_to_export",
nargs="+",
type=str,
help="Full path and filename of the list with the metrics to export.",
default=None,
)

rfs = parser.add_argument_group("Response Functions")
rfs.add_argument(
"-crf",
Expand Down Expand Up @@ -218,26 +280,10 @@ def _get_parser():
help='Central measure operator to use in cardiac metrics. Default is "mean."',
default="mean",
)
metric_arg.add_argument(
"-tl",
"--time-length",
dest="time_length",
type=int,
help="RRF or CRF Kernel length in seconds.",
default=None,
)
metric_arg.add_argument(
"-onset",
"--onset",
dest="onset",
type=float,
help="Onset of the response in seconds. Default is 0.",
default=0,
)
metric_arg.add_argument(
"-tr",
"--tr",
dest="tr",
dest="t_r",
type=float,
help="TR of sequence in seconds.",
default=None,
Expand All @@ -262,7 +308,7 @@ def _get_parser():
metric_arg.add_argument(
"-nscans",
"--number-scans",
dest="nscans",
dest="n_scans",
type=int,
help="Number of timepoints in the imaging data. "
"Also called sub-bricks, TRs, scans, volumes."
Expand All @@ -277,18 +323,82 @@ def _get_parser():
help="Number of harmonics.",
default=None,
)
metric_arg.add_argument(
"-sl",
"--slice-timings",
dest="slice_timings",
nargs="*",
type=float,
help="Slice timings in seconds.",
default=None,
)

# Other optional arguments
otheropt = parser.add_argument_group("Other optional arguments")
otheropt.add_argument(
# BIDS arguments
bids = parser.add_argument_group("BIDS Arguments")
bids.add_argument(
"-sub",
"--subject",
dest="subject",
type=str,
help="Subject ID in BIDS format.",
default=None,
)
bids.add_argument(
"-ses",
"--session",
dest="session",
type=str,
help="Session ID in BIDS format.",
default=None,
)
bids.add_argument(
"-task",
"--task",
dest="task",
type=str,
help="Task ID in BIDS format.",
default=None,
)
bids.add_argument(
"-run",
"--run",
dest="run",
type=str,
help="Run ID in BIDS format.",
default=None,
)
bids.add_argument(
"-rec",
"--recording",
dest="recording",
type=str,
help="Recording ID in BIDS format.",
default=None,
)
bids.add_argument(
"-ch",
"--channel",
dest="bids_channel",
type=str,
help="Physiological signal channel ID in BIDS format.",
default=None,
)

# Logging style
log_style_group = parser.add_argument_group(
"Logging style arguments (optional and mutually exclusive)",
"Options to specify the logging style",
)
log_style_group_exclusive = log_style_group.add_mutually_exclusive_group()
log_style_group_exclusive.add_argument(
"-debug",
"--debug",
dest="debug",
action="store_true",
help="Only print debugging info to log file. Default is False.",
help="Print additional debugging info and error diagnostics to log file. Default is False.",
default=False,
)
otheropt.add_argument(
log_style_group_exclusive.add_argument(
"-quiet",
"--quiet",
dest="quiet",
Expand All @@ -299,7 +409,7 @@ def _get_parser():
optional.add_argument(
"-h", "--help", action="help", help="Show this help message and exit"
)
otheropt.add_argument(
optional.add_argument(
"-v", "--version", action="version", version=("%(prog)s " + __version__)
)

Expand Down
2 changes: 2 additions & 0 deletions phys2denoise/metrics/chest_belt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Denoising metrics for chest belt recordings."""
import numpy as np
import pandas as pd
from loguru import logger
from physutils import io, physio
from scipy.interpolate import interp1d
from scipy.stats import zscore
Expand Down Expand Up @@ -229,6 +230,7 @@ def _respiratory_pattern_variability(data, window):

# Convert window to Hertz
window = int(window * data.fs)
logger.debug(f"Window size in samples: {window}")

# Calculate RPV across a rolling window

Expand Down
5 changes: 5 additions & 0 deletions phys2denoise/metrics/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import functools
import inspect
import logging
import os

import numpy as np
from loguru import logger
Expand Down Expand Up @@ -293,6 +294,10 @@ def export_metric(
has_lags : bool, optional.
If True, `metric` contains lagged versions of itself - default is False
"""
output_dir = os.path.dirname(fileprefix)
if not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)

# Start resampling
len_tp = metric.shape[0]
len_newtp = int(np.around(metric.shape[0] * (1 / (sample_rate * tr))))
Expand Down
Loading