From f28eb89e33fb4d5b3c0469498c26dd4f6973aa82 Mon Sep 17 00:00:00 2001 From: Ghislain Vaillant Date: Fri, 18 Nov 2022 13:49:27 +0100 Subject: [PATCH] Read BIDS datasets using pydra-bids tasks (#791) * Add pydra-bids to dependencies * Fix invalid escape sequence in regexp * Remove erroneous return * Read BIDS datasets with BIDSDataReader * Remove datatype attribute from BIDS queries It is not supported by `pydra-bids` and could be computed from the suffix anyway. * Ugrade pydra-bids to version 0.0.3 * Fix name of BIDS dataset reader task * Run formatter --- clinica/pydra/engine_utils.py | 4 +-- clinica/pydra/interfaces.py | 13 ++++----- clinica/pydra/query.py | 8 ++--- poetry.lock | 39 ++++++++++++++++++------- pyproject.toml | 1 + test/unittests/pydra/test_engine.py | 4 +-- test/unittests/pydra/test_interfaces.py | 7 ++--- test/unittests/pydra/test_query.py | 9 ++---- 8 files changed, 47 insertions(+), 38 deletions(-) diff --git a/clinica/pydra/engine_utils.py b/clinica/pydra/engine_utils.py index 066abd67d..0b924801e 100644 --- a/clinica/pydra/engine_utils.py +++ b/clinica/pydra/engine_utils.py @@ -72,10 +72,10 @@ def run(wf: Workflow) -> Result: with Submitter(plugin="cf") as submitter: submitter(wf) except Exception as e: - path = re.search("\/.*\.pklz", str(e)) + path = re.search(r"/.*\.pklz", str(e)) if path: print(read_error(path.group(0))) - return print(str(e)) + print(str(e)) return wf.result(return_inputs=False) diff --git a/clinica/pydra/interfaces.py b/clinica/pydra/interfaces.py index 1af63e308..4524e4eca 100644 --- a/clinica/pydra/interfaces.py +++ b/clinica/pydra/interfaces.py @@ -116,7 +116,7 @@ def bids_reader(query: BIDSQuery, input_dir: PathLike): Parameters ---------- query : BIDSQuery - Input to BIDSDataGrabber (c.f https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.io.html#bidsdatagrabber) + BIDS dataset reader query. input_dir : PathLike The BIDS input directory. @@ -125,14 +125,11 @@ def bids_reader(query: BIDSQuery, input_dir: PathLike): Nipype1Task The task used for reading files from BIDS. """ - bids_data_grabber = nio.BIDSDataGrabber(output_query=query.query) - bids_reader_task = Nipype1Task( - name="bids_reader_task", - interface=bids_data_grabber, - base_dir=input_dir, - output_query=query.query, + from pydra.tasks.bids import BIDSDatasetReader + + return BIDSDatasetReader(output_query=query.query).to_task( + name="bids_reader_task", dataset_path=input_dir ) - return bids_reader_task def caps_reader(query: CAPSQuery, input_dir: PathLike): diff --git a/clinica/pydra/query.py b/clinica/pydra/query.py index 20c864d1a..68355d4bb 100644 --- a/clinica/pydra/query.py +++ b/clinica/pydra/query.py @@ -69,7 +69,7 @@ def format_query(self, input_query: Optional[Dict] = None) -> Dict: {"label" : {query dict} or list of query dicts} - example: {"T1w": {"datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"]}} + example: {"T1w": {"suffix": "T1w", "extension": [".nii.gz"]}} Parameters ---------- @@ -150,12 +150,12 @@ class BIDSQuery(Query): >>> len(q) 1 >>> q.query - {'T1w': {'datatype': 'anat', 'suffix': 'T1w', 'extension': ['.nii.gz']}} + {'T1w': {'suffix': 'T1w', 'extension': ['.nii.gz']}} """ _default_queries = { - "T1w": {"datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"]}, - "pet": {"datatype": "pet", "suffix": "pet", "extension": [".nii.gz"]}, + "T1w": {"suffix": "T1w", "extension": [".nii.gz"]}, + "pet": {"suffix": "pet", "extension": [".nii.gz"]}, } def parse_query(self, query: Dict) -> Dict: diff --git a/poetry.lock b/poetry.lock index b0c79f8bc..d63fb9c44 100644 --- a/poetry.lock +++ b/poetry.lock @@ -50,6 +50,14 @@ python-versions = ">=3.6" [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "ancpbids" +version = "0.2.1" +description = "Read/write/validate/query BIDS datasets" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "argcomplete" version = "1.12.3" @@ -1289,6 +1297,18 @@ docs = ["attrs (>=19.1.0)", "cloudpickle", "filelock", "packaging", "sphinx (>=2 test = ["boutiques", "codecov", "numpy", "psutil", "pyld", "pympler", "pytest (>=6.2.5)", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist (<2.0)", "python-dateutil", "tornado"] tests = ["boutiques", "codecov", "numpy", "psutil", "pyld", "pympler", "pytest (>=6.2.5)", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist (<2.0)", "python-dateutil", "tornado"] +[[package]] +name = "pydra-bids" +version = "0.0.3" +description = "Pydra tasks for BIDS I/O" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +ancpbids = ">=0.2.1,<0.3.0" +pydra = ">=0.20,<0.21" + [[package]] name = "pydra-nipype1" version = "0.1.0" @@ -1984,7 +2004,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = ">=3.8,<3.11" -content-hash = "0c5ee3499fd3f2c5f0776b383077a8dee896205293e6c4ba1ca1984130d0e008" +content-hash = "8cb5d3cdc86a50fd4e1c2454e9b88d753b93cd8137f786bdefb9b944e3105fda" [metadata.files] abagen = [ @@ -2084,6 +2104,10 @@ aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] +ancpbids = [ + {file = "ancpbids-0.2.1-py3-none-any.whl", hash = "sha256:feefcc08caa29f316bbc58026f7e4be42d894164c00eed6e55ad313823a77fc2"}, + {file = "ancpbids-0.2.1.tar.gz", hash = "sha256:b0dfb67c169418af330a442d81776fc4ca98d30555c04afc57fb77c699021d71"}, +] argcomplete = [ {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, @@ -2146,23 +2170,14 @@ biopython = [ {file = "biopython-1.79.tar.gz", hash = "sha256:edb07eac99d3b8abd7ba56ff4bedec9263f76dfc3c3f450e7d2e2bcdecf8559b"}, ] black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, @@ -3050,6 +3065,10 @@ pydot = [ pydra = [ {file = "pydra-0.20-py3-none-any.whl", hash = "sha256:8e6c823a046c1a2d9fa64a4215d0058bb1d47ad4d8e79f94909c55da75ecc747"}, ] +pydra-bids = [ + {file = "pydra_bids-0.0.3-py3-none-any.whl", hash = "sha256:c4ba96dc97da25a52b6f2e14f0537738730503599b67fe50447816e2dbfc2817"}, + {file = "pydra_bids-0.0.3.tar.gz", hash = "sha256:4c4279ce95d27060a88d2a10178ae57773d2a8753789b2968528a50b4ecc8796"}, +] pydra-nipype1 = [ {file = "pydra-nipype1-0.1.0.tar.gz", hash = "sha256:4a080871317ea23b73bf703ae4ccf105884b5580a093d621ad2a71047fed985c"}, {file = "pydra_nipype1-0.1.0-py3-none-any.whl", hash = "sha256:774a06cde2874ec41465cb198864ed6232eb8e98da3da928acd439c693762798"}, diff --git a/pyproject.toml b/pyproject.toml index 7ac7f0c6a..d27f793a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ joblib = "^1.2.0" attrs = ">=20.1.0" cattrs = "^1.9.0" brainstat = "^0.3.6" +pydra-bids = "^0.0.3" [tool.poetry.group.dev.dependencies] black = "*" diff --git a/test/unittests/pydra/test_engine.py b/test/unittests/pydra/test_engine.py index c7159a318..4858d9bb0 100644 --- a/test/unittests/pydra/test_engine.py +++ b/test/unittests/pydra/test_engine.py @@ -25,9 +25,7 @@ def smooth_image(input_image: PathLike) -> PurePath: def bids_query(): from clinica.pydra.query import BIDSQuery - return BIDSQuery( - {"T1w": {"datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"]}} - ) + return BIDSQuery({"T1w": {"suffix": "T1w", "extension": [".nii.gz"]}}) @pytest.fixture diff --git a/test/unittests/pydra/test_interfaces.py b/test/unittests/pydra/test_interfaces.py index 9735783d5..f879f6a02 100644 --- a/test/unittests/pydra/test_interfaces.py +++ b/test/unittests/pydra/test_interfaces.py @@ -13,15 +13,14 @@ def test_bids_reader_instantiation(tmp_path): - from nipype.interfaces.io import BIDSDataGrabber + from pydra.engine.task import FunctionTask from clinica.pydra.interfaces import bids_reader task = bids_reader(BIDSQuery(), tmp_path) - assert isinstance(task, Nipype1Task) + assert isinstance(task, FunctionTask) assert task.name == "bids_reader_task" - assert task.inputs.base_dir == tmp_path - assert isinstance(task._interface, BIDSDataGrabber) + assert task.inputs.dataset_path == tmp_path def test_bids_reader(tmp_path): diff --git a/test/unittests/pydra/test_query.py b/test/unittests/pydra/test_query.py index 6e13edd9d..d7846bc76 100644 --- a/test/unittests/pydra/test_query.py +++ b/test/unittests/pydra/test_query.py @@ -15,15 +15,12 @@ def test_bids_query(): assert len(q) == 0 q = BIDSQuery({"T1w": {}}) - assert q.query == { - "T1w": {"datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"]} - } + assert q.query == {"T1w": {"suffix": "T1w", "extension": [".nii.gz"]}} assert len(q) == 1 q = BIDSQuery({"T1w": {"foo": "bar"}}) assert q.query == { "T1w": { - "datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"], "foo": "bar", @@ -32,9 +29,7 @@ def test_bids_query(): assert len(q) == 1 q = BIDSQuery({"T1w": {}, "foo": {"bar": "baz"}}) - assert q.query == { - "T1w": {"datatype": "anat", "suffix": "T1w", "extension": [".nii.gz"]} - } + assert q.query == {"T1w": {"suffix": "T1w", "extension": [".nii.gz"]}} assert len(q) == 1