From 012550841483f2e849878407516dbb355142ac99 Mon Sep 17 00:00:00 2001 From: Ashley Stewart Date: Fri, 6 Mar 2026 09:46:40 +1000 Subject: [PATCH 1/5] Add derivative filename validation Include schema.rules.files.deriv in the regex chain so derivative filenames (with entities like space, desc, res, den) are recognized as valid BIDS. Closes #62 --- src/bids_validator/bids_validator.py | 2 +- tests/test_derivatives.py | 78 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/test_derivatives.py diff --git a/src/bids_validator/bids_validator.py b/src/bids_validator/bids_validator.py index 6c25ab5..e761210 100644 --- a/src/bids_validator/bids_validator.py +++ b/src/bids_validator/bids_validator.py @@ -103,7 +103,7 @@ def _init_regexes(cls) -> None: all_rules = chain.from_iterable( bst.rules.regexify_filename_rules(group, schema, level=2) - for group in (schema.rules.files.common, schema.rules.files.raw) + for group in (schema.rules.files.common, schema.rules.files.raw, schema.rules.files.deriv) ) cls.regexes = [rule['regex'] for rule in all_rules] diff --git a/tests/test_derivatives.py b/tests/test_derivatives.py new file mode 100644 index 0000000..7cf15b3 --- /dev/null +++ b/tests/test_derivatives.py @@ -0,0 +1,78 @@ +"""Test BIDSValidator support for derivative filenames.""" + +import pytest + +from bids_validator import BIDSValidator + + +@pytest.fixture(scope='module') +def validator() -> BIDSValidator: + """Return a BIDSValidator instance.""" + return BIDSValidator() + + +@pytest.mark.parametrize( + 'fname', + [ + '/sub-04/anat/sub-04_space-MNI152_T1w.nii.gz', + '/sub-04/anat/sub-04_space-MNI152_desc-preproc_T1w.nii.gz', + '/sub-04/ses-02/func/sub-04_ses-02_task-rest_space-MNI152NLin2009cAsym_desc-preproc_bold.nii', + '/sub-04/ses-02/func/sub-04_ses-02_task-nback_run-02_space-T1w_label-brain_mask.nii', + '/sub-04/ses-01/func/sub-04_ses-01_task-nback_run-01_space-T1w_desc-preproc_bold.json', + '/sub-01/anat/sub-01_res-1_T1w.nii.gz', + '/sub-01/anat/sub-01_space-MNI152_dseg.nii.gz', + ], +) +def test_derivative_filenames_are_bids(validator: BIDSValidator, fname: str) -> None: + """Test that derivative filenames are recognized as valid BIDS.""" + assert validator.is_bids(fname) + + +@pytest.mark.parametrize( + ('fname', 'expected'), + [ + ( + '/sub-04/anat/sub-04_space-MNI152_desc-preproc_T1w.nii.gz', + { + 'subject': '04', + 'datatype': 'anat', + 'space': 'MNI152', + 'description': 'preproc', + 'suffix': 'T1w', + 'extension': '.nii.gz', + }, + ), + ( + '/sub-04/ses-02/func/sub-04_ses-02_task-rest_space-MNI152NLin2009cAsym_desc-preproc_bold.nii', + { + 'subject': '04', + 'session': '02', + 'datatype': 'func', + 'task': 'rest', + 'space': 'MNI152NLin2009cAsym', + 'description': 'preproc', + 'suffix': 'bold', + 'extension': '.nii', + }, + ), + ], +) +def test_parse_derivative_entities( + validator: BIDSValidator, fname: str, expected: dict[str, str] +) -> None: + """Test that parse() returns derivative-specific entities.""" + result = validator.parse(fname) + assert result == expected + + +@pytest.mark.parametrize( + 'fname', + [ + '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.exe', + '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.niigz', + '/sub-01/func/sub-01_space-MNI152_desc-preproc_bold.nii.gz', # missing required task entity + ], +) +def test_invalid_derivative_filenames(validator: BIDSValidator, fname: str) -> None: + """Test that invalid derivative filenames are rejected.""" + assert not validator.is_bids(fname) From 78f6959ca0cf3c446da6f9a85fdd2920b7231331 Mon Sep 17 00:00:00 2001 From: Ashley Stewart Date: Fri, 6 Mar 2026 09:54:37 +1000 Subject: [PATCH 2/5] Fix line length lint error --- src/bids_validator/bids_validator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bids_validator/bids_validator.py b/src/bids_validator/bids_validator.py index e761210..261537f 100644 --- a/src/bids_validator/bids_validator.py +++ b/src/bids_validator/bids_validator.py @@ -103,7 +103,11 @@ def _init_regexes(cls) -> None: all_rules = chain.from_iterable( bst.rules.regexify_filename_rules(group, schema, level=2) - for group in (schema.rules.files.common, schema.rules.files.raw, schema.rules.files.deriv) + for group in ( + schema.rules.files.common, + schema.rules.files.raw, + schema.rules.files.deriv, + ) ) cls.regexes = [rule['regex'] for rule in all_rules] From a098ac48c98be56ae5dd2221f6c35a0b93e6168e Mon Sep 17 00:00:00 2001 From: Ashley Stewart Date: Fri, 6 Mar 2026 09:58:12 +1000 Subject: [PATCH 3/5] Remove unnecessary fixture from derivative tests --- tests/test_derivatives.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/test_derivatives.py b/tests/test_derivatives.py index 7cf15b3..aee5e41 100644 --- a/tests/test_derivatives.py +++ b/tests/test_derivatives.py @@ -5,12 +5,6 @@ from bids_validator import BIDSValidator -@pytest.fixture(scope='module') -def validator() -> BIDSValidator: - """Return a BIDSValidator instance.""" - return BIDSValidator() - - @pytest.mark.parametrize( 'fname', [ @@ -23,9 +17,9 @@ def validator() -> BIDSValidator: '/sub-01/anat/sub-01_space-MNI152_dseg.nii.gz', ], ) -def test_derivative_filenames_are_bids(validator: BIDSValidator, fname: str) -> None: +def test_derivative_filenames_are_bids(fname: str) -> None: """Test that derivative filenames are recognized as valid BIDS.""" - assert validator.is_bids(fname) + assert BIDSValidator.is_bids(fname) @pytest.mark.parametrize( @@ -57,12 +51,9 @@ def test_derivative_filenames_are_bids(validator: BIDSValidator, fname: str) -> ), ], ) -def test_parse_derivative_entities( - validator: BIDSValidator, fname: str, expected: dict[str, str] -) -> None: +def test_parse_derivative_entities(fname: str, expected: dict[str, str]) -> None: """Test that parse() returns derivative-specific entities.""" - result = validator.parse(fname) - assert result == expected + assert BIDSValidator.parse(fname) == expected @pytest.mark.parametrize( @@ -73,6 +64,6 @@ def test_parse_derivative_entities( '/sub-01/func/sub-01_space-MNI152_desc-preproc_bold.nii.gz', # missing required task entity ], ) -def test_invalid_derivative_filenames(validator: BIDSValidator, fname: str) -> None: +def test_invalid_derivative_filenames(fname: str) -> None: """Test that invalid derivative filenames are rejected.""" - assert not validator.is_bids(fname) + assert not BIDSValidator.is_bids(fname) From 67dd6da35c4aa86e4ac8b9d5f06a85e26ab01fdf Mon Sep 17 00:00:00 2001 From: Ashley Stewart Date: Fri, 6 Mar 2026 10:01:16 +1000 Subject: [PATCH 4/5] Add comments noting derivative-specific features in tests --- tests/test_derivatives.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_derivatives.py b/tests/test_derivatives.py index aee5e41..19155ea 100644 --- a/tests/test_derivatives.py +++ b/tests/test_derivatives.py @@ -8,13 +8,13 @@ @pytest.mark.parametrize( 'fname', [ - '/sub-04/anat/sub-04_space-MNI152_T1w.nii.gz', - '/sub-04/anat/sub-04_space-MNI152_desc-preproc_T1w.nii.gz', - '/sub-04/ses-02/func/sub-04_ses-02_task-rest_space-MNI152NLin2009cAsym_desc-preproc_bold.nii', - '/sub-04/ses-02/func/sub-04_ses-02_task-nback_run-02_space-T1w_label-brain_mask.nii', - '/sub-04/ses-01/func/sub-04_ses-01_task-nback_run-01_space-T1w_desc-preproc_bold.json', - '/sub-01/anat/sub-01_res-1_T1w.nii.gz', - '/sub-01/anat/sub-01_space-MNI152_dseg.nii.gz', + '/sub-04/anat/sub-04_space-MNI152_T1w.nii.gz', # space + '/sub-04/anat/sub-04_space-MNI152_desc-preproc_T1w.nii.gz', # space + desc + '/sub-04/ses-02/func/sub-04_ses-02_task-rest_space-MNI152NLin2009cAsym_desc-preproc_bold.nii', # space + desc + '/sub-04/ses-02/func/sub-04_ses-02_task-nback_run-02_space-T1w_label-brain_mask.nii', # space + label + '/sub-04/ses-01/func/sub-04_ses-01_task-nback_run-01_space-T1w_desc-preproc_bold.json', # space + desc + '/sub-01/anat/sub-01_res-1_T1w.nii.gz', # res + '/sub-01/anat/sub-01_space-MNI152_dseg.nii.gz', # dseg suffix ], ) def test_derivative_filenames_are_bids(fname: str) -> None: @@ -59,8 +59,8 @@ def test_parse_derivative_entities(fname: str, expected: dict[str, str]) -> None @pytest.mark.parametrize( 'fname', [ - '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.exe', - '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.niigz', + '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.exe', # wrong extension + '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.niigz', # wrong extension '/sub-01/func/sub-01_space-MNI152_desc-preproc_bold.nii.gz', # missing required task entity ], ) From 640b2fc77f8e23a6227a49312c88dab894a7762b Mon Sep 17 00:00:00 2001 From: Ashley Stewart Date: Fri, 6 Mar 2026 10:02:09 +1000 Subject: [PATCH 5/5] Remove redundant invalid filename tests These tested general BIDS validation (wrong extensions, missing entities) rather than anything derivative-specific. --- tests/test_derivatives.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/test_derivatives.py b/tests/test_derivatives.py index 19155ea..12082a3 100644 --- a/tests/test_derivatives.py +++ b/tests/test_derivatives.py @@ -54,16 +54,3 @@ def test_derivative_filenames_are_bids(fname: str) -> None: def test_parse_derivative_entities(fname: str, expected: dict[str, str]) -> None: """Test that parse() returns derivative-specific entities.""" assert BIDSValidator.parse(fname) == expected - - -@pytest.mark.parametrize( - 'fname', - [ - '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.exe', # wrong extension - '/sub-01/anat/sub-01_space-MNI152_desc-preproc_T1w.niigz', # wrong extension - '/sub-01/func/sub-01_space-MNI152_desc-preproc_bold.nii.gz', # missing required task entity - ], -) -def test_invalid_derivative_filenames(fname: str) -> None: - """Test that invalid derivative filenames are rejected.""" - assert not BIDSValidator.is_bids(fname)