Generalize entities#86
Conversation
…t patterns, alone
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
…d cohort nesting working
… without descriptions
|
Thanks for the feedback, @effigies ! I think we're in pretty good shape now. I know @kaitj is clearing a few things off his plate before he evaluates the upstream PR to serialize the indexer which this depends on, then we can get his review here too. Getting there. In the meantime, would it make sense to rebase your PR from this one, to get ahead of that? |
|
Sure, if this will go in first. |
| if description_exists: | ||
| desc = _read_dataset_description(path) | ||
| if desc: | ||
| dataset_type = desc.get("DatasetType", "raw") | ||
| if dataset_type in get_all_dataset_types(): | ||
| if dataset_type == "raw": | ||
| return True | ||
| entity_types = get_entity_child_dirs(dataset_type, "root") | ||
| if entity_types: | ||
| return _contains_bids_entity_dirs(path, entity_types) | ||
| return True |
There was a problem hiding this comment.
This strikes me as overly complicated. I think you just want to verify that desc['BIDSVersion'] exists, and then it's BIDS. IMO, if someone wants to index something without a visible dataset_description.json, you can provide a force option. But I also don't have a lot of context for why you were writing these heuristics in the first place.
There was a problem hiding this comment.
I believe the context is wanting to index derivatives datasets which don't need to have a dataset_description.json file, and having those still show up as valid BIDS. Whether the dataset is raw or derived changes the entity options and ordering, so this is intended to help identify what entities can appear at the top level.
There was a problem hiding this comment.
Did a dive into the majority of the changed code, but probably want to do a second pass at it at some point to spend more time figuring out how the new changes work together.
There are a few parts of the codebase where I think it could benefit from either validation (e.g. the arguments passed to the cli) or additional warnings when used (e.g. deprecation of arguments).
Also looks like it will likely need another rebase (or the serial-index branch does)
| "--filter", | ||
| "-f", | ||
| metavar="ENTITY=PATTERN", | ||
| type=str, |
There was a problem hiding this comment.
This could probably use some validation to enforce the pattern shown so that it doesn't silently fail if an invalid entity-value pair is provided. Maybe a custom type with the validation logic.
| ) | ||
|
|
||
| # Merge deprecated --subjects into filters | ||
| if args.subjects: |
There was a problem hiding this comment.
Should probably throw a deprecation warning if something is passed to the arg (in addition to the help string).
| return fmt_obj.get("pattern", ".+") | ||
| except (KeyError, AttributeError): | ||
| pass | ||
| return ".+" |
There was a problem hiding this comment.
Should some sort of warning be thrown here? This seems like a catch-all if a pattern can't be resolved.
| rules_raw = _BIDS_SCHEMA.get("rules", {}).get("directories", {}).get(dtype, {}) | ||
| # bidsschematools may return Namespace objects; normalise to plain dict. | ||
| rules: dict[str, Any] = _ensure_dict(rules_raw) | ||
| queue: list[tuple[str, int]] = [("root", 0)] |
There was a problem hiding this comment.
I think this is minor depending on how large this stack grows, but could implement this using collections.deque instead of a list.
|
|
||
| # Normalise filters: merge include_subjects for backward compat | ||
| filters = dict(filters or {}) | ||
| if include_subjects is not None: |
There was a problem hiding this comment.
Similar to the use in __main__, probably want to throw a deprecation warning
| "dataset": dataset, | ||
| **valid_entities, | ||
| "extra_entities": extra_entities, | ||
| **(record_extras or {}), |
There was a problem hiding this comment.
I wonder if we want to keep these together with the dataset information (prior to entities)?
Co-authored-by: Jason Kai <21226986+kaitj@users.noreply.github.com>
3ed83ad to
824ac16
Compare
Downstream/On top of #85
PR Contribution Summary
This PR generalizes the indexing layer from subject-only discovery to entity-based crawling, and replaces hardcoded patterns with schema-derived logic wherever possible.
Architecture: Subject → Entity
_index_bids_subject_dir→_index_bids_entity_dir— indexes any entity directory (sub-*,tpl-*, etc.)_find_bids_subject_dirs→_find_bids_entity_dirs— discovers any entity type at a dataset root_is_bids_subject_dir→_is_bids_entity_dir— checks arbitrary entity type by name_entities.pyTemplate and Cohort Support
tpl-*andcohort-*directories are indexed alongsidesub-*_is_bids_datasetderivative checks look for subject OR template entity dirsSchema-Driven Discovery
get_entity_child_dirs(dataset_type, parent_rule)— reads valid entity subdirectories fromrules.directoriesget_file_entity_prefixes()— root-level entity name prefixes derived from schemaget_all_root_entity_types()— deduplicated root entity types across all dataset typesget_all_dataset_types()— enumerates schema-defined dataset types_BIDS_JSON_SIDECAR_EXCEPTION_SUFFIXES— derived fromrules.files(currentlycoordsystem,description)_BIDS_DATATYPE_PATTERN— built from entity names at schema init_ensure_dict()helper — centralizesbidsschematoolsNamespace→dict conversionDerivative Detection
_is_bids_dataset()and_get_dataset_type()detect derivative datasets withoutdataset_description.jsonby checking insidederivatives/for valid entity subdirectoriessub-*_ses-*directories (spec-invalid)Generic Filtering and .bidsignore
include_subjects→ generic filters dict mapping any entity name to glob patterns--filter/-fCLI argument replaces--subjects(deprecated, backward-compatible).bidsignoresupport via_is_bidsignoredwith cached upward searchbatch_index_datasetto workersDataset Metadata Columns
dataset_name,dataset_type,bids_versionadded to Arrow schema, populated fromdataset_description.jsonclear_schema_caches()exposed as public API for schema reload safetyCode Cleanup
get_all_entity_prefixes,get_required_entity_types_get_subdir_names()for oneOf expansion_read_dataset_descriptionwith@lru_cacheto deduplicate reads_resolve_entity_dirs— extracts entity discovery into_discover_entity_dirsTesting
test_derivative_detection— 5 scenarios including no-description derivatives and invalid combined entity dirstest_index_dataset_filters— single, multi-value, glob, and cross-entity AND filterstest_batch_index_dataset_filters— filter forwarding through parallel workerstest_index_dataset_bidsignore—.bidsignoreexclusion@templateflow_availabletest_is_bids_subject_dir→test_is_bids_entity_dirtest_find_bids_datasetsis now skipped (@pytest.mark.skip); therglob("dataset_description.json")baseline no longer matches the schema-correct derivative detectionImpact
We should now be less fragile in schema updates, and can correctly index derivative datasets using entity types other than subject and session (namely template and cohort), meaning this can be used across a wide range of the field's projects.