diff --git a/fsl/utils/filetree/__init__.py b/fsl/utils/filetree/__init__.py index 73589296af8b84d6d5677b03dde9058a229b2827..9d31383db6c525d926f9eeb5e558f530700a376c 100644 --- a/fsl/utils/filetree/__init__.py +++ b/fsl/utils/filetree/__init__.py @@ -193,9 +193,9 @@ Assuming that the input T1w's already exist, we can then simply run BET for ever from fsl.utils.filetree import FileTree from fsl.wrappers.bet import bet tree = FileTree.read(<tree filename>) - variables = tree.get_all_vars('T1w') # extract the set of variables for all existing T1w files - for single_variable_set in variables: - T1w_tree = tree.update(**single_variable_set) + + # Iterates over set of variables that correspond to each T1-weighted image file matching the template + for T1w_tree in tree.get_all_trees('T1w', glob_vars='all'): # get retrieves the filenames based on the current set of variables # make_dir=True ensures that the output directory containing the "bet_output" actually exists bet(input=T1w_tree.get('T1w'), output=T1w_tree.get('bet_output', make_dir=True), mask=True) diff --git a/fsl/utils/filetree/filetree.py b/fsl/utils/filetree/filetree.py index 93bf98070bfffd4054af95a62105e7e3cb9deb58..fb7aa082f47f432666feed07b00474c3c5c23d70 100644 --- a/fsl/utils/filetree/filetree.py +++ b/fsl/utils/filetree/filetree.py @@ -171,7 +171,7 @@ class FileTree(object): :return: sorted sequence of paths """ text, variables = self.get_template(short_name) - return tuple(utils.get_all(text, variables, glob_vars=glob_vars)) + return tuple(str(Path(fn)) for fn in utils.get_all(text, variables, glob_vars=glob_vars)) def get_all_vars(self, short_name: str, glob_vars=()) -> Tuple[Dict[str, str]]: """ @@ -185,16 +185,33 @@ class FileTree(object): """ return tuple(self.extract_variables(short_name, fn) for fn in self.get_all(short_name, glob_vars=glob_vars)) + def get_all_trees(self, short_name: str, global_vars=()) -> Tuple["FileTree"]: + """ + Gets all the trees that generate the existing files matching the pattern + + tree.get_all(short_name) == tuple(tree.get(short_name) for tree in tree.get_all_trees(short_name)) + + :param short_name: short name of the path template + :param glob_vars: sequence of undefined variables that can take any possible values when looking for matches on the disk. + Any defined variables in `glob_vars` will be ignored. + If glob_vars is set to 'all', all undefined variables will be used to look up matches. + :return: sequence of FileTrees used to generate each file on disk matching the pattern of `short_name` + """ + return tuple(self.update(**vars) for vars in self.get_all_vars(short_name, glob_vars=global_vars)) + def update(self, **variables) -> "FileTree": """ - Creates a new filetree with updated variables + Creates a new FileTree with updated variables :param variables: new values for the variables - Setting variables to None will explicitly unset them + Setting a variable to None will cause the variable to be unset :return: New FileTree with same templates for directory names and filenames, but updated variables """ new_tree = deepcopy(self) new_tree.variables.update(variables) + for key, value in variables.items(): + if value is None: + del new_tree.variables[key] return new_tree def extract_variables(self, short_name: str, filename: str) -> Dict[str, str]: diff --git a/fsl/utils/filetree/utils.py b/fsl/utils/filetree/utils.py index 4ad5f63aff9164531b1a491056f9f9574f963b70..e7dd4aad3347c678e32217e09eecf33d2ecdee35 100644 --- a/fsl/utils/filetree/utils.py +++ b/fsl/utils/filetree/utils.py @@ -170,6 +170,8 @@ def extract_variables(template, filename, known_vars=None): raise ValueError('Multiple values found for {}'.format(var)) else: extracted_value[var] = value + if any('/' in value for value in extracted_value.values()): + continue for name in find_variables(template): if name not in extracted_value: extracted_value[name] = None diff --git a/tests/test_filetree/test_read.py b/tests/test_filetree/test_read.py index b27984477218cd69f757199948865c2a06e6f8d1..954f671e791ca8edf3d3ef0a8932488e3669f7ab 100644 --- a/tests/test_filetree/test_read.py +++ b/tests/test_filetree/test_read.py @@ -77,8 +77,8 @@ def test_custom_tree(): assert len(tree.get_all('sub_file', glob_vars='all')) == 2 assert len(tree.get_all('sub_file')) == 1 assert len(tree.update(opt=None).get_all('sub_file')) == 1 - assert len(tree.update(opt=None).get_all('sub_file', glob_vars=['opt'])) == 1 - assert len(tree.update(opt=None).get_all('sub_file', glob_vars='all')) == 1 + assert len(tree.update(opt=None).get_all('sub_file', glob_vars=['opt'])) == 2 + assert len(tree.update(opt=None).get_all('sub_file', glob_vars='all')) == 2 for fn, settings in zip(tree.get_all('sub_file', glob_vars='all'), tree.get_all_vars('sub_file', glob_vars='all')): @@ -90,6 +90,10 @@ def test_custom_tree(): tree.get_all('opt_file') assert len(tree.get_all('opt_file', glob_vars=['opt'])) == 1 + for short_name in ('sub_file', 'opt_file'): + for glob_vars in (['opt'], 'all'): + assert tree.get_all(short_name, glob_vars) == tuple(stree.get(short_name) for stree in tree.get_all_trees(short_name, glob_vars)) + for vars in ({'opt': None}, {'opt': 'test'}): filename = tree.update(**vars).get('sub_file') assert vars == tree.extract_variables('sub_file', filename) diff --git a/tests/test_filetree/test_template.py b/tests/test_filetree/test_template.py index 96ecc18f00adb1add2b191c8d3744460f1c02bc6..c4042078df8a762c3affb5b2d98e1c63cb9a4b81 100644 --- a/tests/test_filetree/test_template.py +++ b/tests/test_filetree/test_template.py @@ -21,3 +21,7 @@ def test_get_variables(): utils.extract_variables('{var}[_{other_var}]_{var}', 'test_foo_bar') with pytest.raises(ValueError): utils.extract_variables('bar{var}[_{other_var}]_{var}', 'test') + + assert {'subject': '01', 'session': 'A'} == utils.extract_variables('sub-{subject}/[ses-{session}]/T1w.nii.gz', 'sub-01/ses-A/T1w.nii.gz') + with pytest.raises(ValueError): + utils.extract_variables('sub-{subject}/[ses-{session}]/T1w.nii.gz', 'sub-01/other/T1w.nii.gz')