From 027d95471ac3fea90be65bc7ee19bbe5b4bb9c00 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 16:07:09 -0600 Subject: [PATCH 01/12] Change get_metadata to return display_name_ --- bids/layout/layout.py | 4 ++-- bids/layout/tests/test_layout.py | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 42f3dd9d8..89a2363e2 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -354,9 +354,9 @@ def get_metadata(self, path, include_entities=False, scope='all'): file = self.dataset.get_file(path) md = file.get_metadata() if md and include_entities: - schema_entities = {e.entity_: e.literal_ for e in list(self.schema.EntityEnum)} + schema_entities = {e.literal_: e.display_name_ for e in list(self.schema.EntityEnum)} md.update({schema_entities[e.key]: e.value for e in file.entities}) - bmd = BIDSMetadata(file.path) + bmd = BIDSMetadata(file['name']) bmd.update(md) return bmd diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py index e5b3ed1f4..7a7007b04 100644 --- a/bids/layout/tests/test_layout.py +++ b/bids/layout/tests/test_layout.py @@ -17,10 +17,7 @@ NoMatchError, TargetError, ) -from bids.layout import BIDSLayout, Query -from bids.layout.index import BIDSLayoutIndexer -from bids.layout.models import Config -from bids.layout.utils import PaddedInt +from bids.layout import BIDSLayout from bids.tests import get_test_data_path from bids.utils import natural_sort @@ -205,8 +202,8 @@ def test_get_metadata5(layout_7t_trt): result = layout_7t_trt.get_metadata( join(layout_7t_trt.root, *target), include_entities=True) assert result['EchoTime'] == 0.020 - assert result['subject'] == '01' - assert result['acquisition'] == 'fullbrain' + assert result['Subject'] == '01' + assert result['Acquisition'] == 'fullbrain' def test_get_metadata_via_bidsfile(layout_7t_trt): From b9fa17fa41f1900880529f7ccc9fe37343bc1da8 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 17:05:45 -0600 Subject: [PATCH 02/12] FIX: Mismatch of metadata key in return_type 'id' --- bids/layout/layout.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 89a2363e2..cdaa9cf48 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -574,7 +574,6 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None 'entity must also be specified.') # Resolve proper target names to their "key", e.g., session to ses # XXX should we allow ses? - target = getattr(self.dataset._schema.EntityEnum, target, target) self_entities = self.get_entities() if target not in self_entities: potential = list(self_entities.keys()) From 8617bbd81591c7579f6ddd7bb1e4598b628f68b4 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 17:40:09 -0600 Subject: [PATCH 03/12] Update for latest ancpbids version --- bids/layout/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index cdaa9cf48..c15427646 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -354,7 +354,7 @@ def get_metadata(self, path, include_entities=False, scope='all'): file = self.dataset.get_file(path) md = file.get_metadata() if md and include_entities: - schema_entities = {e.literal_: e.display_name_ for e in list(self.schema.EntityEnum)} + schema_entities = {e.name: e.value['display_name'] for e in list(self.schema.EntityEnum)} md.update({schema_entities[e.key]: e.value for e in file.entities}) bmd = BIDSMetadata(file['name']) bmd.update(md) From 563b184bdbd77fb220fcd07105928e97eeb768b9 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 17:47:06 -0600 Subject: [PATCH 04/12] Use name, not literal --- bids/layout/layout.py | 2 +- bids/layout/tests/test_layout.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 89a2363e2..d0cf64029 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -354,7 +354,7 @@ def get_metadata(self, path, include_entities=False, scope='all'): file = self.dataset.get_file(path) md = file.get_metadata() if md and include_entities: - schema_entities = {e.literal_: e.display_name_ for e in list(self.schema.EntityEnum)} + schema_entities = {e.value['name']: e.name for e in list(self.schema.EntityEnum)} md.update({schema_entities[e.key]: e.value for e in file.entities}) bmd = BIDSMetadata(file['name']) bmd.update(md) diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py index 7a7007b04..a6adc5ab6 100644 --- a/bids/layout/tests/test_layout.py +++ b/bids/layout/tests/test_layout.py @@ -202,8 +202,8 @@ def test_get_metadata5(layout_7t_trt): result = layout_7t_trt.get_metadata( join(layout_7t_trt.root, *target), include_entities=True) assert result['EchoTime'] == 0.020 - assert result['Subject'] == '01' - assert result['Acquisition'] == 'fullbrain' + assert result['subject'] == '01' + assert result['acquisition'] == 'fullbrain' def test_get_metadata_via_bidsfile(layout_7t_trt): From 62cad4379ae9125bc79be6ba0c921d841fcd0441 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 18:08:52 -0600 Subject: [PATCH 05/12] Use long form entities --- bids/layout/layout.py | 6 +++--- bids/layout/tests/test_layout.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index e0b7b16ed..abbb144d2 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -354,8 +354,7 @@ def get_metadata(self, path, include_entities=False, scope='all'): file = self.dataset.get_file(path) md = file.get_metadata() if md and include_entities: - schema_entities = {e.value['name']: e.name for e in list(self.schema.EntityEnum)} - md.update({schema_entities[e.key]: e.value for e in file.entities}) + md.update(file.entities) bmd = BIDSMetadata(file['name']) bmd.update(md) return bmd @@ -574,6 +573,7 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None 'entity must also be specified.') # Resolve proper target names to their "key", e.g., session to ses # XXX should we allow ses? + assert 0 self_entities = self.get_entities() if target not in self_entities: potential = list(self_entities.keys()) @@ -622,7 +622,7 @@ def get_entities(self, scope: str = None, sort: bool = False) -> dict: dict a unique set of entities found within the dataset as a dict """ - return query_entities(self.dataset, scope, sort) + return query_entities(self.dataset, scope, sort, long_form=True) def get_dataset_description(self, scope='self', all_=False) -> Union[List[Dict], Dict]: """Return contents of dataset_description.json. diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py index a6adc5ab6..30b5df74d 100644 --- a/bids/layout/tests/test_layout.py +++ b/bids/layout/tests/test_layout.py @@ -336,8 +336,8 @@ def test_layout_with_derivs(layout_ds005_derivs): event_file = "sub-01_task-mixedgamblestask_run-01_desc-extra_events.tsv" deriv_files = [f.name for f in files] assert event_file in deriv_files - entities = deriv.query_entities() - assert 'sub' in entities + entities = deriv.query_entities(long_form=True) + assert 'subject' in entities def test_layout_with_multi_derivs(layout_ds005_multi_derivs): From aeb780154651e63a5d453584dca8998e73b86c17 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Thu, 9 Feb 2023 18:10:36 -0600 Subject: [PATCH 06/12] Remove TMP --- bids/layout/layout.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index abbb144d2..4be287540 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -571,9 +571,6 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None if target is None: raise TargetError(f'If return_type is "id" or "dir", a valid target ' 'entity must also be specified.') - # Resolve proper target names to their "key", e.g., session to ses - # XXX should we allow ses? - assert 0 self_entities = self.get_entities() if target not in self_entities: potential = list(self_entities.keys()) From 0da7322f58235ce1f2b0ee1d935aaab89c593e9a Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Fri, 10 Feb 2023 15:14:14 -0600 Subject: [PATCH 07/12] Update to use DatasetOptions --- bids/layout/layout.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 6513aa523..dc843ad1f 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -18,7 +18,7 @@ ) from ancpbids import CustomOpExpr, EntityExpr, AllExpr, ValidationPlugin, load_dataset, validate_dataset, \ - write_derivative + write_derivative, DatasetOptions from ancpbids.query import query, query_entities, FnMatchExpr, AnyExpr from ancpbids.utils import deepupdate, resolve_segments, convert_to_relative, parse_bids_name @@ -274,7 +274,6 @@ def __init__( root = root.absolute() if ignore is None: - # If there is no .bidsignore file, apply default ignore patterns if not (Path(root) / '.bidsignore').exists(): ignore = ['.*', 'models', 'stimuli', 'code', 'sourcedata'] warnings.warn( @@ -290,7 +289,9 @@ def __init__( DeprecationWarning ) - self.dataset = load_dataset(root, ignore=ignore) + options = DatasetOptions(ignore=ignore) + + self.dataset = load_dataset(root, options=options) self.schema = self.dataset.get_schema() self.validationReport = None From c1d1de283ba06187e486cabf2bff96e94e5d7ea1 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Fri, 10 Feb 2023 15:15:15 -0600 Subject: [PATCH 08/12] Merge conflict --- bids/layout/layout.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index dc843ad1f..02b370c1d 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -602,21 +602,12 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None # Provide some suggestions if target is specified and invalid. if return_type in ("dir", "id"): -<<<<<<< HEAD if target is None: raise TargetError(f'If return_type is "id" or "dir", a valid target ' 'entity must also be specified.') self_entities = self.get_entities() if target not in self_entities: potential = list(self_entities.keys()) -======= - # Resolve proper target names to their "key", e.g., session to ses - # XXX should we allow ses? - target_match = [e for e in self.dataset._schema.EntityEnum - if target in [e.name, e.literal_]] - potential = list(self.get_entities().keys()) - if (not target_match) or target_match[0].name not in potential: ->>>>>>> rf/ancp-layout suggestions = difflib.get_close_matches(target, potential) if suggestions: message = "Did you mean one of: {}?".format(suggestions) @@ -666,11 +657,7 @@ def get_entities(self, scope: str = None, sort: bool = False, long_form: bool = dict a unique set of entities found within the dataset as a dict """ -<<<<<<< HEAD - return query_entities(self.dataset, scope, sort, long_form=True) -======= return query_entities(self.dataset, scope, sort, long_form=long_form) ->>>>>>> rf/ancp-layout def get_dataset_description(self, scope='self', all_=False) -> Union[List[Dict], Dict]: """Return contents of dataset_description.json. From 0023430309a307ee9586b99abc822035ee9d202c Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Fri, 10 Feb 2023 15:51:12 -0600 Subject: [PATCH 09/12] Return long form entities in BIDSFile.get_entities --- bids/layout/layout.py | 6 +++--- bids/layout/models.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 02b370c1d..6eb10b41c 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -421,7 +421,7 @@ def parse_file_entities(self, filename, scope=None, entities=None, results = parse_bids_name(filename) entities = results.pop('entities') - schema_entities = {e.literal_: e.name for e in list(self.schema.EntityEnum)} + schema_entities = {e.value['name']: e.name for e in list(self.schema.EntityEnum)} entities = {schema_entities[k]: v for k, v in entities.items()} results = {**entities, **results} @@ -614,7 +614,7 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None else: message = "Valid targets are: {}".format(potential) raise TargetError(f"Unknown target '{target}'. {message}") - target = target_match[0].name + folder = self.dataset result = query(folder, return_type, target, scope, extension, suffix, regex_search, **entities) if return_type == 'file': @@ -622,7 +622,7 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None if return_type == "object": if result: result = natural_sort( - [BIDSFile(res) for res in result], + [BIDSFile(res, schema=self.schema) for res in result], "path" ) return result diff --git a/bids/layout/models.py b/bids/layout/models.py index 178445cdd..5a02cf4c4 100644 --- a/bids/layout/models.py +++ b/bids/layout/models.py @@ -4,7 +4,7 @@ from typing import Union from pathlib import Path import json -from ancpbids.model_v1_8_0 import Artifact +from ancpbids.model_v1_8_0 import Artifact, Dataset from ancpbids.utils import parse_bids_name from ..utils import listify @@ -37,9 +37,10 @@ def from_filename(cls, filename): break return cls(path) - def __init__(self, file_ref: Union[str, os.PathLike, Artifact]): + def __init__(self, file_ref: Union[str, os.PathLike, Artifact], schema): self._path = None self._artifact = None + self._schema = schema # Adding this to be able to convert metadata if isinstance(file_ref, (str, os.PathLike)): self._path = Path(file_ref) elif isinstance(file_ref, Artifact): @@ -106,7 +107,7 @@ def get_associations(self, kind=None, include_parents=False): def get_metadata(self): """Return all metadata associated with the current file. """ md = BIDSMetadata(self.path) - md.update(self.get_entities(metadata=True)) + md.update(self._artifact.get_metadata()) return md @property @@ -139,10 +140,9 @@ def get_entities(self, metadata=False, values='tags'): """ try: entities = self._artifact.get_entities() - # Convert literal entity values to their names - # schema_entities = {e.literal_: e.name for e in list(self.schema.EntityEnum)} - # entities = {schema_entities[k]: v for k, v in entities.items()} + known_entities = {e.value['name']: e.name for e in list(self._schema.EntityEnum)} + entities = {known_entities[k] if k in known_entities else k: v for k, v in entities.items()} entities['suffix'] = self._artifact.suffix entities['extension'] = self._artifact.extension From 3d62613ed2229310dca9724e1e3be6eecbd165c2 Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Fri, 10 Feb 2023 16:04:18 -0600 Subject: [PATCH 10/12] Revert tests to using long-form name --- bids/layout/tests/test_layout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py index f657c99b8..5f094cf1f 100644 --- a/bids/layout/tests/test_layout.py +++ b/bids/layout/tests/test_layout.py @@ -236,7 +236,7 @@ def test_get_with_bad_target(layout_7t_trt): msg = str(exc.value) assert 'subject' in msg and 'reconstruction' in msg and 'proc' in msg with pytest.raises(TargetError) as exc: - layout_7t_trt.get(target='sub') + layout_7t_trt.get(target='subject') msg = str(exc.value) assert 'subject' in msg and 'reconstruction' not in msg @@ -282,7 +282,7 @@ def test_bids_json(layout_7t_trt): def test_get_return_type_dir(layout_7t_trt): - res_relpath = layout_7t_trt.get(target='sub', return_type='dir') + res_relpath = layout_7t_trt.get(target='subject', return_type='dir') target_relpath = ["sub-{:02d}".format(i) for i in range(1, 11)] assert all([tp in res_relpath for tp in target_relpath]) @@ -322,7 +322,7 @@ def test_get_val_enum_any_optional(layout_7t_trt, layout_ds005): def test_get_return_sorted(layout_7t_trt): - paths = layout_7t_trt.get(target='sub', return_type='file') + paths = layout_7t_trt.get(target='subject', return_type='file') assert natural_sort(paths) == paths From 0cc4bdd09ec77d10df63a794baef1da33c76aeba Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Fri, 10 Feb 2023 20:22:24 -0600 Subject: [PATCH 11/12] Use file's schema --- bids/layout/layout.py | 2 +- bids/layout/models.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bids/layout/layout.py b/bids/layout/layout.py index 6eb10b41c..3462570fd 100644 --- a/bids/layout/layout.py +++ b/bids/layout/layout.py @@ -622,7 +622,7 @@ def get(self, return_type: str = 'object', target: str = None, scope: str = None if return_type == "object": if result: result = natural_sort( - [BIDSFile(res, schema=self.schema) for res in result], + [BIDSFile(res) for res in result], "path" ) return result diff --git a/bids/layout/models.py b/bids/layout/models.py index 5a02cf4c4..bfd3067ba 100644 --- a/bids/layout/models.py +++ b/bids/layout/models.py @@ -37,13 +37,14 @@ def from_filename(cls, filename): break return cls(path) - def __init__(self, file_ref: Union[str, os.PathLike, Artifact], schema): + def __init__(self, file_ref: Union[str, os.PathLike, Artifact], schema = None): self._path = None self._artifact = None - self._schema = schema # Adding this to be able to convert metadata + self._schema = schema if isinstance(file_ref, (str, os.PathLike)): self._path = Path(file_ref) elif isinstance(file_ref, Artifact): + self._schema = file_ref.get_schema() self._artifact = file_ref @property From 9ef1672ef7fc82c6a556efdf77bc2bcd12e52bcd Mon Sep 17 00:00:00 2001 From: Alejandro de la Vega Date: Mon, 27 Feb 2023 14:27:38 -0600 Subject: [PATCH 12/12] Add adelavega/ancpbids as req --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index e9ce6e00e..877dd16af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ dependencies = [ "bids-validator", "num2words", "click >=8.0", + "ancpbids @ git+https://github.com/adelavega/ancp-bids.git", ] dynamic = ["version"]