Skip to content

Commit

Permalink
Merge pull request #2 from smoia/add-pydra
Browse files Browse the repository at this point in the history
A few mods stemming from PR convo
  • Loading branch information
maestroque authored Oct 12, 2024
2 parents 1cd7539 + 94b711c commit 1a72423
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 69 deletions.
9 changes: 7 additions & 2 deletions physutils/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import os.path as op

import numpy as np
from bids import BIDSLayout
from loguru import logger

from physutils import physio
Expand All @@ -28,7 +27,7 @@ def load_from_bids(
suffix="physio",
):
"""
Load physiological data from BIDS-formatted directory
Load physiological data from BIDS-formatted directory.
Parameters
----------
Expand All @@ -50,6 +49,12 @@ def load_from_bids(
data : :class:`physutils.Physio`
Loaded physiological data
"""
try:
from bids import BIDSLayout
except ImportError:
raise ImportError(
"To use BIDS-based feature, pybids must be installed. Install manually or with `pip install physutils[bids]`"
)

# check if file exists and is in BIDS format
if not op.exists(bids_path):
Expand Down
95 changes: 40 additions & 55 deletions physutils/tasks.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,48 @@
import logging
from functools import wraps
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from bids import BIDSLayout
from loguru import logger
"""Helper class for holding physiological data and associated metadata information."""

from physutils.io import load_from_bids, load_physio
from physutils.physio import Physio
import logging

LGR = logging.getLogger(__name__)
LGR.setLevel(logging.DEBUG)
from .io import load_from_bids, load_physio
from .physio import Physio
from .utils import is_bids_directory

try:
import pydra
# from loguru import logger

pydra_imported = True
try:
from pydra import task
except ImportError:
pydra_imported = False


def mark_task(pydra_imported=pydra_imported):
def decorator(func):
if pydra_imported:
# If the decorator exists, apply it
@wraps(func)
def wrapped_func(*args, **kwargs):
logger.debug(f"Creating pydra task for {func.__name__}")
return pydra.mark.task(func)(*args, **kwargs)
from .utils import task

return wrapped_func
# Otherwise, return the original function
return func

return decorator
LGR = logging.getLogger(__name__)
LGR.setLevel(logging.DEBUG)


def is_bids_directory(directory):
try:
# Attempt to create a BIDSLayout object
_ = BIDSLayout(directory)
return True
except Exception as e:
# Catch other exceptions that might indicate the directory isn't BIDS compliant
logger.error(
f"An error occurred while trying to load {directory} as a BIDS Layout object: {e}"
)
return False
@task
def generate_physio(
input_file: str, mode="auto", fs=None, bids_parameters=dict(), col_physio_type=None
) -> Physio:
"""
Load a physio object from either a BIDS directory or an exported physio object.
Parameters
----------
input_file : str
Path to input file
mode : 'auto', 'physio', or 'bids', optional
Mode to operate with
fs : None, optional
Set or force set sapmling frequency (Hz).
bids_parameters : dictionary, optional
Dictionary containing BIDS parameters
col_physio_type : int or None, optional
Object to pick up in a BIDS array of physio objects.
@mark_task(pydra_imported=pydra_imported)
def transform_to_physio(
input_file: str, mode="physio", fs=None, bids_parameters=dict(), bids_channel=None
) -> Physio:
if not pydra_imported:
LGR.warning(
"Pydra is not installed, thus transform_to_physio is not available as a pydra task. Using the function directly"
)
LGR.debug(f"Loading physio object from {input_file}")
if not fs:
fs = None
"""
LGR.info(f"Loading physio object from {input_file}")

if mode == "auto":
if input_file.endswith((".phys", ".physio", ".1D", ".txt", ".tsv", ".csv")):
Expand All @@ -66,20 +51,20 @@ def transform_to_physio(
mode = "bids"
else:
raise ValueError(
"Could not determine mode automatically, please specify mode"
"Could not determine input mode automatically. Please specify it manually."
)
if mode == "physio":
if fs is not None:
physio_obj = load_physio(input_file, fs=fs, allow_pickle=True)
else:
physio_obj = load_physio(input_file, allow_pickle=True)
physio_obj = load_physio(input_file, fs=fs, allow_pickle=True)

elif mode == "bids":
if bids_parameters is {}:
raise ValueError("BIDS parameters must be provided when loading from BIDS")
else:
physio_array = load_from_bids(input_file, **bids_parameters)
physio_obj = physio_array[bids_channel]
physio_obj = (
physio_array[col_physio_type] if col_physio_type else physio_array
)
else:
raise ValueError(f"Invalid transform_to_physio mode: {mode}")
raise ValueError(f"Invalid generate_physio mode: {mode}")

return physio_obj
20 changes: 10 additions & 10 deletions physutils/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from physutils.tests.utils import create_random_bids_structure


def test_transform_to_physio_phys_file():
"""Test transform_to_physio task."""
def test_generate_physio_phys_file():
"""Test generate_physio task."""
physio_file = os.path.abspath("physutils/tests/data/ECG.phys")
task = tasks.transform_to_physio(input_file=physio_file, mode="physio")
task = tasks.generate_physio(input_file=physio_file, mode="physio")
assert task.inputs.input_file == physio_file
assert task.inputs.mode == "physio"
assert task.inputs.fs is None
Expand All @@ -23,8 +23,8 @@ def test_transform_to_physio_phys_file():
assert physio_obj.data.shape == (44611,)


def test_transform_to_physio_bids_file():
"""Test transform_to_physio task."""
def test_generate_physio_bids_file():
"""Test generate_physio task."""
create_random_bids_structure("physutils/tests/data", recording_id="cardiac")
bids_parameters = {
"subject": "01",
Expand All @@ -34,7 +34,7 @@ def test_transform_to_physio_bids_file():
"recording": "cardiac",
}
bids_dir = os.path.abspath("physutils/tests/data/bids-dir")
task = tasks.transform_to_physio(
task = tasks.generate_physio(
input_file=bids_dir,
mode="bids",
bids_parameters=bids_parameters,
Expand All @@ -53,7 +53,7 @@ def test_transform_to_physio_bids_file():
assert isinstance(physio_obj, physio.Physio)


def test_transform_to_physio_auto():
def test_generate_physio_auto():
create_random_bids_structure("physutils/tests/data", recording_id="cardiac")
bids_parameters = {
"subject": "01",
Expand All @@ -63,7 +63,7 @@ def test_transform_to_physio_auto():
"recording": "cardiac",
}
bids_dir = os.path.abspath("physutils/tests/data/bids-dir")
task = tasks.transform_to_physio(
task = tasks.generate_physio(
input_file=bids_dir,
mode="auto",
bids_parameters=bids_parameters,
Expand All @@ -82,9 +82,9 @@ def test_transform_to_physio_auto():
assert isinstance(physio_obj, physio.Physio)


def test_transform_to_physio_auto_error(caplog):
def test_generate_physio_auto_error(caplog):
bids_dir = os.path.abspath("physutils/tests/data/non-bids-dir")
task = tasks.transform_to_physio(
task = tasks.generate_physio(
input_file=bids_dir,
mode="auto",
bids_channel="cardiac",
Expand Down
68 changes: 68 additions & 0 deletions physutils/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Helper class for holding physiological data and associated metadata information."""

import logging
from functools import wraps

from loguru import logger

LGR = logging.getLogger(__name__)
LGR.setLevel(logging.DEBUG)


def task(func):
"""
Fake task decorator to import when pydra is not installed/used.
Parameters
----------
func: function
Function to run the wrapper around
Returns
-------
function
"""

@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
LGR.debug(
"Pydra is not installed, thus generate_physio is not available as a pydra task. Using the function directly"
)

return wrapper


def is_bids_directory(path_to_dir):
"""
Check if a directory is a BIDS compliant directory.
Parameters
----------
path_to_dir : os.path or str
Path to (supposed) BIDS directory
Returns
-------
bool
True if the given path is a BIDS directory, False is not.
"""
try:
from bids import BIDSLayout
except ImportError:
raise ImportError(
"To use BIDS-based feature, pybids must be installed. Install manually or with `pip install physutils[bids]`"
)
try:
# Attempt to create a BIDSLayout object
_ = BIDSLayout(path_to_dir)
return True
except Exception as e:
# Catch other exceptions that might indicate the directory isn't BIDS compliant
logger.error(
f"An error occurred while trying to load {path_to_dir} as a BIDS Layout object: {e}"
)
return False
8 changes: 6 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ install_requires =
matplotlib
numpy >=1.9.3
loguru
pydra
pybids
tests_require =
pytest >=3.6
test_suite = pytest
Expand All @@ -34,6 +32,10 @@ packages = find:
include_package_data = True

[options.extras_require]
pydra =
pydra
bids =
pybids
doc =
sphinx >=2.0
sphinx-argparse
Expand All @@ -49,6 +51,8 @@ test =
scipy
pytest >=5.3
pytest-cov
%(pydra)s
%(bids)s
%(style)s
devtools =
pre-commit
Expand Down

0 comments on commit 1a72423

Please sign in to comment.