-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from maestroque/add-pydra
Add BIDS reading support and prepare input loading for pydra workflow
- Loading branch information
Showing
9 changed files
with
300 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
|
||
"""Helper class for holding physiological data and associated metadata information.""" | ||
|
||
import logging | ||
|
||
from .io import load_from_bids, load_physio | ||
from .physio import Physio | ||
from .utils import is_bids_directory | ||
|
||
# from loguru import logger | ||
|
||
try: | ||
from pydra.mark import task | ||
except ImportError: | ||
from .utils import task | ||
|
||
|
||
LGR = logging.getLogger(__name__) | ||
LGR.setLevel(logging.DEBUG) | ||
|
||
|
||
@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. | ||
""" | ||
LGR.info(f"Loading physio object from {input_file}") | ||
|
||
if mode == "auto": | ||
if input_file.endswith((".phys", ".physio", ".1D", ".txt", ".tsv", ".csv")): | ||
mode = "physio" | ||
elif is_bids_directory(input_file): | ||
mode = "bids" | ||
else: | ||
raise ValueError( | ||
"Could not determine input mode automatically. Please specify it manually." | ||
) | ||
if mode == "physio": | ||
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[col_physio_type] if col_physio_type else physio_array | ||
) | ||
else: | ||
raise ValueError(f"Invalid generate_physio mode: {mode}") | ||
|
||
return physio_obj |
12 changes: 12 additions & 0 deletions
12
...ils/tests/data/non-bids-dir/sub-01/ses-01/func/sub-01_ses-01_task-rest_run-01_physio.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"SamplingFrequency": 10000.0, | ||
"StartTime": -3, | ||
"Columns": [ | ||
"time", | ||
"respiratory_chest", | ||
"trigger", | ||
"cardiac", | ||
"respiratory_CO2", | ||
"respiratory_O2" | ||
] | ||
} |
12 changes: 12 additions & 0 deletions
12
...-bids-dir/sub-01/ses-01/func/sub-01_ses-01_task-rest_run-01_recording-cardiac_physio.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"SamplingFrequency": 10000.0, | ||
"StartTime": -3, | ||
"Columns": [ | ||
"time", | ||
"respiratory_chest", | ||
"trigger", | ||
"cardiac", | ||
"respiratory_CO2", | ||
"respiratory_O2" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
"""Tests for physutils.tasks and their integration.""" | ||
|
||
import os | ||
|
||
import physutils.tasks as tasks | ||
from physutils import physio | ||
from physutils.tests.utils import create_random_bids_structure | ||
|
||
|
||
def test_generate_physio_phys_file(): | ||
"""Test generate_physio task.""" | ||
physio_file = os.path.abspath("physutils/tests/data/ECG.phys") | ||
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 | ||
|
||
task() | ||
|
||
physio_obj = task.result().output.out | ||
assert isinstance(physio_obj, physio.Physio) | ||
assert physio_obj.fs == 1000 | ||
assert physio_obj.data.shape == (44611,) | ||
|
||
|
||
def test_generate_physio_bids_file(): | ||
"""Test generate_physio task.""" | ||
create_random_bids_structure("physutils/tests/data", recording_id="cardiac") | ||
bids_parameters = { | ||
"subject": "01", | ||
"session": "01", | ||
"task": "rest", | ||
"run": "01", | ||
"recording": "cardiac", | ||
} | ||
bids_dir = os.path.abspath("physutils/tests/data/bids-dir") | ||
task = tasks.generate_physio( | ||
input_file=bids_dir, | ||
mode="bids", | ||
bids_parameters=bids_parameters, | ||
col_physio_type="cardiac", | ||
) | ||
|
||
assert task.inputs.input_file == bids_dir | ||
assert task.inputs.mode == "bids" | ||
assert task.inputs.fs is None | ||
assert task.inputs.bids_parameters == bids_parameters | ||
assert task.inputs.col_physio_type == "cardiac" | ||
|
||
task() | ||
|
||
physio_obj = task.result().output.out | ||
assert isinstance(physio_obj, physio.Physio) | ||
|
||
|
||
def test_generate_physio_auto(): | ||
create_random_bids_structure("physutils/tests/data", recording_id="cardiac") | ||
bids_parameters = { | ||
"subject": "01", | ||
"session": "01", | ||
"task": "rest", | ||
"run": "01", | ||
"recording": "cardiac", | ||
} | ||
bids_dir = os.path.abspath("physutils/tests/data/bids-dir") | ||
task = tasks.generate_physio( | ||
input_file=bids_dir, | ||
mode="auto", | ||
bids_parameters=bids_parameters, | ||
col_physio_type="cardiac", | ||
) | ||
|
||
assert task.inputs.input_file == bids_dir | ||
assert task.inputs.mode == "auto" | ||
assert task.inputs.fs is None | ||
assert task.inputs.bids_parameters == bids_parameters | ||
assert task.inputs.col_physio_type == "cardiac" | ||
|
||
task() | ||
|
||
physio_obj = task.result().output.out | ||
assert isinstance(physio_obj, physio.Physio) | ||
|
||
|
||
def test_generate_physio_auto_error(caplog): | ||
bids_dir = os.path.abspath("physutils/tests/data/non-bids-dir") | ||
task = tasks.generate_physio( | ||
input_file=bids_dir, | ||
mode="auto", | ||
col_physio_type="cardiac", | ||
) | ||
|
||
assert task.inputs.input_file == bids_dir | ||
assert task.inputs.mode == "auto" | ||
assert task.inputs.fs is None | ||
assert task.inputs.col_physio_type == "cardiac" | ||
|
||
try: | ||
task() | ||
except Exception: | ||
assert caplog.text.count("ERROR") == 1 | ||
assert ( | ||
caplog.text.count( | ||
"dataset_description.json' is missing from project root. Every valid BIDS dataset must have this file." | ||
) | ||
== 1 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters