55
66import importlib
77import json
8+ import os
89import os .path as op
910
1011import numpy as np
12+ from bids import BIDSLayout
1113from loguru import logger
1214
1315from physutils import physio
1416
1517EXPECTED = ["data" , "fs" , "history" , "metadata" ]
1618
1719
20+ def load_from_bids (
21+ bids_path ,
22+ subject ,
23+ session = None ,
24+ task = None ,
25+ run = None ,
26+ recording = None ,
27+ extension = "tsv.gz" ,
28+ suffix = "physio" ,
29+ ):
30+ """
31+ Load physiological data from BIDS-formatted directory
32+
33+ Parameters
34+ ----------
35+ bids_path : str
36+ Path to BIDS-formatted directory
37+ subject : str
38+ Subject identifier
39+ session : str
40+ Session identifier
41+ task : str
42+ Task identifier
43+ run : str
44+ Run identifier
45+ suffix : str
46+ Suffix of file to load
47+
48+ Returns
49+ -------
50+ data : :class:`physutils.Physio`
51+ Loaded physiological data
52+ """
53+
54+ # check if file exists and is in BIDS format
55+ if not op .exists (bids_path ):
56+ raise FileNotFoundError (f"Provided path { bids_path } does not exist" )
57+
58+ layout = BIDSLayout (bids_path , validate = False )
59+ bids_file = layout .get (
60+ subject = subject ,
61+ session = session ,
62+ task = task ,
63+ run = run ,
64+ suffix = suffix ,
65+ extension = extension ,
66+ recording = recording ,
67+ )
68+ logger .debug (f"BIDS file found: { bids_file } " )
69+ if len (bids_file ) == 0 :
70+ raise FileNotFoundError (
71+ f"No files found for subject { subject } , session { session } , task { task } , run { run } , recording { recording } "
72+ )
73+ if len (bids_file ) > 1 :
74+ raise ValueError (
75+ f"Multiple files found for subject { subject } , session { session } , task { task } , run { run } , recording { recording } "
76+ )
77+
78+ config_file = bids_file [0 ].get_metadata ()
79+ fs = config_file ["SamplingFrequency" ]
80+ t_start = config_file ["StartTime" ] if "StartTime" in config_file else 0
81+ columns = config_file ["Columns" ]
82+ logger .debug (f"Loaded structure contains columns: { columns } " )
83+
84+ physio_objects = {}
85+ data = np .loadtxt (bids_file [0 ].path )
86+
87+ if "time" in columns :
88+ idx_0 = np .argmax (data [:, columns .index ("time" )] >= t_start )
89+ else :
90+ idx_0 = 0
91+ logger .warning (
92+ "No time column found in file. Assuming data starts at the beginning of the file"
93+ )
94+
95+ for col in columns :
96+ col_physio_type = None
97+ if any ([x in col .lower () for x in ["cardiac" , "ppg" , "ecg" , "card" , "pulse" ]]):
98+ col_physio_type = "cardiac"
99+ elif any (
100+ [
101+ x in col .lower ()
102+ for x in ["respiratory" , "rsp" , "resp" , "breath" , "co2" , "o2" ]
103+ ]
104+ ):
105+ col_physio_type = "respiratory"
106+ elif any ([x in col .lower () for x in ["trigger" , "tr" ]]):
107+ col_physio_type = "trigger"
108+ elif any ([x in col .lower () for x in ["time" ]]):
109+ continue
110+ else :
111+ logger .warning (
112+ f"Column { col } 's type cannot be determined. Additional features may be missing."
113+ )
114+
115+ if col_physio_type in ["cardiac" , "respiratory" ]:
116+ physio_objects [col ] = physio .Physio (
117+ data [idx_0 :, columns .index (col )],
118+ fs = fs ,
119+ history = [physio ._get_call (exclude = [])],
120+ )
121+ physio_objects [col ]._physio_type = col_physio_type
122+ physio_objects [col ]._label = (
123+ bids_file [0 ].filename .split ("." )[0 ].replace ("_physio" , "" )
124+ )
125+
126+ if col_physio_type == "trigger" :
127+ # TODO: Implement trigger loading using the MRI data object
128+ logger .warning ("MRI trigger characteristics extraction not yet implemented" )
129+ physio_objects [col ] = physio .Physio (
130+ data [idx_0 :, columns .index (col )],
131+ fs = fs ,
132+ history = [physio ._get_call (exclude = [])],
133+ )
134+
135+ return physio_objects
136+
137+
18138def load_physio (data , * , fs = None , dtype = None , history = None , allow_pickle = False ):
19139 """
20140 Returns `Physio` object with provided data
21141
22142 Parameters
23143 ----------
24- data : str or array_like or Physio_like
144+ data : str, os.path.PathLike or array_like or Physio_like
25145 Input physiological data. If array_like, should be one-dimensional
26146 fs : float, optional
27147 Sampling rate of `data`. Default: None
@@ -46,7 +166,7 @@ def load_physio(data, *, fs=None, dtype=None, history=None, allow_pickle=False):
46166
47167 # first check if the file was made with `save_physio`; otherwise, try to
48168 # load it as a plain text file and instantiate a history
49- if isinstance (data , str ):
169+ if isinstance (data , str ) or isinstance ( data , os . PathLike ) :
50170 try :
51171 inp = dict (np .load (data , allow_pickle = allow_pickle ))
52172 for attr in EXPECTED :
0 commit comments