Skip to content

Commit d828a56

Browse files
authored
Merge pull request #113 from Remi-Gau/qc_on_input
[ENH] run QC on input data
2 parents dcd85d0 + 2f63be5 commit d828a56

55 files changed

Lines changed: 2769 additions & 992 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/system_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ concurrency:
1313
jobs:
1414
system_test:
1515

16-
runs-on: ubuntu-latest
16+
runs-on: ubuntu-22.04
1717

1818
strategy:
1919
matrix:

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cff-version: 1.2.0
22

33
title: "bidsMReye"
44

5-
version: 0.1.0
5+
version: 0.2.0
66

77
abstract:
88
"BIDS app using deepMReye to decode eye motion for fMRI time series data."
@@ -41,4 +41,4 @@ keywords:
4141
- MRI
4242
- Python
4343
- Eyetracking
44-
- MAchine learning
44+
- Machine learning

FAQ.md

Lines changed: 0 additions & 17 deletions
This file was deleted.

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ ds002799_prepare: get_ds002799
178178
--action prepare \
179179
--debug \
180180
--participant_label 302 307 \
181-
--space MNI152NLin2009cAsym T1w \
181+
--space MNI152NLin2009cAsym \
182182
--run 1 2
183183

184184

@@ -189,7 +189,7 @@ ds002799_generalize:
189189
--action generalize \
190190
--debug \
191191
--participant_label 302 307 \
192-
--space MNI152NLin2009cAsym T1w \
192+
--space MNI152NLin2009cAsym \
193193
--run 1 2
194194

195195

@@ -200,7 +200,7 @@ ds002799: clean-ds002799 get_ds002799
200200
--action all \
201201
--debug \
202202
--participant_label 302 307 \
203-
--space MNI152NLin2009cAsym T1w \
203+
--space MNI152NLin2009cAsym \
204204
--run 1 2 \
205205
-vv
206206

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
[![codecov](https://codecov.io/gh/cpp-lln-lab/bidsMReye/branch/main/graph/badge.svg?token=G5fm2kaloM)](https://codecov.io/gh/cpp-lln-lab/bidsMReye)
44
[![Documentation Status](https://readthedocs.org/projects/bidsmreye/badge/?version=latest)](https://bidsmreye.readthedocs.io/en/latest/?badge=latest)
55
[![License](https://img.shields.io/badge/license-GPL3-blue.svg)](./LICENSE)
6+
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/bidsmreye)
67
![https://github.com/psf/black](https://img.shields.io/badge/code%20style-black-000000.svg)
8+
[![Sourcery](https://img.shields.io/badge/Sourcery-enabled-brightgreen)](https://sourcery.ai)
79
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg)](#contributors)
810
[![Paper_link](https://img.shields.io/badge/DOI-10.1038%2Fs41593--021--00947--w-blue)](https://doi.org/10.1038/s41593-021-00947-w)
911

@@ -31,7 +33,7 @@ Decoded gaze positions allow computing eye movements.
3133

3234
Some basic quality control and outliers detection is also performed.
3335

34-
![](docs/source/images/sub-01_task-auditory_space-MNI152NLin6Asym_desc-bidsmreye_eyetrack.png)
36+
![](https://github.com/cpp-lln-lab/bidsMReye/blob/b9b60b4ec9d1bd6904da6151f0d6c44aa425536d/docs/source/images/bidsMReye_logo.png)
3537

3638
For more information, see the
3739
[User Recommendations](https://deepmreye.slite.com/p/channel/MUgmvViEbaATSrqt3susLZ/notes/kKdOXmLqe).

bidsmreye/bids_utils.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Any
6+
7+
from bids import BIDSLayout # type: ignore
8+
9+
from . import _version
10+
from bidsmreye.configuration import Config
11+
from bidsmreye.configuration import config_to_dict
12+
from bidsmreye.configuration import get_bids_filter_config
13+
from bidsmreye.configuration import get_bidsname_config
14+
from bidsmreye.configuration import get_pybids_config
15+
from bidsmreye.logging import bidsmreye_log
16+
from bidsmreye.methods import methods
17+
from bidsmreye.utils import copy_license
18+
from bidsmreye.utils import create_dir_if_absent
19+
20+
log = bidsmreye_log("bidsmreye")
21+
22+
__version__ = _version.get_versions()["version"]
23+
24+
25+
def check_layout(cfg: Config, layout: BIDSLayout, for_file: str = "bold") -> None:
26+
"""Check layout.
27+
28+
:param cfg: Configuration object
29+
:type cfg: Config
30+
31+
:param layout: BIDSLayout of the dataset.
32+
:type layout: BIDSLayout
33+
34+
:raises RuntimeError: _description_
35+
:raises RuntimeError: _description_
36+
"""
37+
desc = layout.get_dataset_description()
38+
if (
39+
"DatasetType" not in desc
40+
and "PipelineDescription" not in desc
41+
or "DatasetType" in desc
42+
and desc["DatasetType"] != "derivative"
43+
):
44+
raise RuntimeError("Input dataset should be BIDS derivative")
45+
46+
this_filter = get_bids_filter_config()[for_file]
47+
48+
if "GeneratedBy" in desc:
49+
generated_by = desc["GeneratedBy"]
50+
elif "PipelineDescription" in desc:
51+
generated_by = desc["PipelineDescription"]
52+
53+
if isinstance(generated_by, list):
54+
generated_by = generated_by[0]
55+
56+
if generated_by["Name"].lower() == "bidsmreye":
57+
this_filter = get_bids_filter_config()["mask"]
58+
59+
this_filter["task"] = cfg.task
60+
this_filter["space"] = cfg.space
61+
this_filter["run"] = cfg.run
62+
63+
log.debug(f"Looking for files with filter\n{this_filter}")
64+
65+
bf = layout.get(
66+
return_type="filename",
67+
regex_search=True,
68+
**this_filter,
69+
)
70+
71+
if bf == []:
72+
raise RuntimeError(
73+
f"""
74+
Input dataset {layout.root} does not have any data to process for filter\n{this_filter}
75+
"""
76+
)
77+
78+
79+
def create_bidsname(
80+
layout: BIDSLayout, filename: dict[str, str] | str | Path, filetype: str
81+
) -> Path:
82+
"""Return a BIDS valid filename for layout and a filename or a dict of BIDS entities.
83+
84+
:param layout: BIDSLayout of the dataset.
85+
:type layout: BIDSLayout
86+
87+
:param filename: Dictionary of BIDS entities or a Path to a file.
88+
:type filename: Union[dict, str, Path]
89+
90+
:param filetype: One of the file type available in the BIDS name config.
91+
:type filetype: str
92+
93+
:raises TypeError:
94+
95+
:return:
96+
:rtype: Path
97+
"""
98+
if isinstance(filename, dict):
99+
entities = filename
100+
elif isinstance(filename, (Path, str)):
101+
entities = layout.parse_file_entities(filename)
102+
else:
103+
raise TypeError(f"filename must be a dict or a Path, not {type(filename)}")
104+
105+
bids_name_config = get_bidsname_config()
106+
107+
output_file = layout.build_path(entities, bids_name_config[filetype], validate=False)
108+
109+
output_file = Path(layout.root).joinpath(output_file)
110+
111+
return output_file.resolve()
112+
113+
114+
def create_sidecar(
115+
layout: BIDSLayout, filename: str, SamplingFrequency: float | None = None
116+
) -> None:
117+
"""Create sidecar for the eye motion timeseries."""
118+
if SamplingFrequency is None:
119+
SamplingFrequency = 0
120+
content = {
121+
"SamplingFrequency": SamplingFrequency,
122+
}
123+
sidecar_name = create_bidsname(layout, filename, "confounds_json")
124+
json.dump(content, open(sidecar_name, "w"), indent=4)
125+
126+
127+
def get_dataset_layout(
128+
dataset_path: str | Path,
129+
config: dict[str, str] | None = None,
130+
use_database: bool = False,
131+
) -> BIDSLayout:
132+
"""Return a BIDSLayout object for the dataset at the given path.
133+
134+
:param dataset_path: Path to the dataset.
135+
:type dataset_path: Union[str, Path]
136+
137+
:param config: Pybids config to use. Defaults to None.
138+
:type config: Optional[dict], optional
139+
140+
:param use_database: Defaults to False
141+
:type use_database: bool, optional
142+
143+
:return: _description_
144+
:rtype: BIDSLayout
145+
"""
146+
if isinstance(dataset_path, str):
147+
dataset_path = Path(dataset_path)
148+
create_dir_if_absent(dataset_path)
149+
150+
dataset_path = dataset_path.resolve()
151+
152+
if config is None:
153+
pybids_config = get_pybids_config()
154+
155+
log.info(f"indexing {dataset_path}")
156+
157+
if not use_database:
158+
return BIDSLayout(
159+
dataset_path,
160+
validate=False,
161+
derivatives=False,
162+
config=pybids_config,
163+
)
164+
165+
database_path = dataset_path.joinpath("pybids_db")
166+
return BIDSLayout(
167+
dataset_path,
168+
validate=False,
169+
derivatives=False,
170+
config=pybids_config,
171+
database_path=database_path,
172+
)
173+
174+
175+
def init_dataset(cfg: Config, qc_only: bool = False) -> BIDSLayout:
176+
177+
layout_out = init_derivatives_layout(cfg)
178+
179+
citation_file = methods(cfg.output_dir, qc_only=qc_only)
180+
log.info(f"Method section generated: {citation_file}")
181+
182+
license_file = copy_license(cfg.output_dir)
183+
log.info(f"License file added: {license_file}")
184+
185+
return layout_out
186+
187+
188+
def init_derivatives_layout(cfg: Config) -> BIDSLayout:
189+
"""Initialize a derivatives dataset and returns its layout.
190+
191+
:param output_dir:
192+
:type output_dir: Path
193+
194+
:return:
195+
:rtype: BIDSLayout
196+
"""
197+
create_dir_if_absent(cfg.output_dir)
198+
layout_out = get_dataset_layout(cfg.output_dir)
199+
layout_out = set_dataset_description(layout_out)
200+
layout_out.dataset_description["DatasetType"] = "derivative"
201+
layout_out.dataset_description["GeneratedBy"][0]["Name"] = "bidsmreye"
202+
layout_out.dataset_description["GeneratedBy"][0]["config"] = config_to_dict(cfg)
203+
write_dataset_description(layout_out)
204+
return layout_out
205+
206+
207+
def list_subjects(cfg: Config, layout: BIDSLayout) -> list[str]:
208+
"""List subject in a BIDS dataset for a given Config.
209+
210+
:param cfg: Configuration object
211+
:type cfg: Config
212+
213+
:param layout: BIDSLayout of the dataset.
214+
:type layout: BIDSLayout
215+
216+
:raises RuntimeError: _description_
217+
218+
:return: _description_
219+
:rtype: list
220+
"""
221+
subjects = layout.get(return_type="id", target="subject", subject=cfg.participant)
222+
223+
if subjects == [] or subjects is None:
224+
raise RuntimeError("No subject found")
225+
226+
if cfg.debug:
227+
subjects = [subjects[0]]
228+
log.debug("Running first subject only.")
229+
230+
log.info(f"processing subjects: {subjects}")
231+
232+
return subjects
233+
234+
235+
def set_dataset_description(layout: BIDSLayout, is_derivative: bool = True) -> BIDSLayout:
236+
"""Add dataset description to a layout.
237+
238+
:param layout: _description_
239+
:type layout: BIDSLayout
240+
241+
:param is_derivative: Defaults to True
242+
:type is_derivative: bool, optional
243+
244+
:return: Updated BIDSLayout of the dataset
245+
:rtype: BIDSLayout
246+
"""
247+
data: dict[str, Any] = {
248+
"Name": "dataset name",
249+
"BIDSVersion": "1.7.0",
250+
"DatasetType": "raw",
251+
"License": "CCO",
252+
"Authors": ["", ""],
253+
"Acknowledgements": "Special thanks to ",
254+
"HowToAcknowledge": """Please cite this paper: Frey, M., Nau, M. & Doeller, C.F.
255+
Magnetic resonance-based eye tracking using deep neural networks.
256+
Nat Neurosci 24, 1772-1779 (2021).
257+
https://doi.org/10.1038/s41593-021-00947-w""",
258+
"Funding": ["", ""],
259+
"ReferencesAndLinks": ["https://doi.org/10.1038/s41593-021-00947-w"],
260+
"DatasetDOI": "doi:",
261+
}
262+
263+
if is_derivative:
264+
data["GeneratedBy"] = [
265+
{
266+
"Name": "bidsMReye",
267+
"Version": __version__,
268+
"Container": {"Type": "", "Tag": ""},
269+
"Description": "",
270+
"CodeURL": "https://github.com/cpp-lln-lab/bidsMReye.git",
271+
},
272+
]
273+
274+
data["SourceDatasets"] = [
275+
{
276+
"DOI": "doi:",
277+
"URL": "",
278+
"Version": "",
279+
}
280+
]
281+
282+
layout.dataset_description = data
283+
284+
return layout
285+
286+
287+
def write_dataset_description(layout: BIDSLayout) -> None:
288+
"""Add a dataset_description.json to a BIDS dataset.
289+
290+
:param layout: BIDSLayout of the dataset to update.
291+
:type layout: BIDSLayout
292+
"""
293+
output_file = Path(layout.root).joinpath("dataset_description.json")
294+
295+
with open(output_file, "w") as ff:
296+
json.dump(layout.dataset_description, ff, indent=4)

0 commit comments

Comments
 (0)