Skip to content

Commit bcddeb1

Browse files
committed
ENH: Start concrete implementations
1 parent 8ca4ee7 commit bcddeb1

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

bids_ng/dataset.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import os
2+
import re
3+
import typing as ty
4+
from functools import cached_property
5+
from pathlib import Path
6+
7+
import bidsschematools as bst # type: ignore[import]
8+
import bidsschematools.schema # type: ignore[import]
9+
import bidsschematools.types # type: ignore[import]
10+
11+
from . import types as bt
12+
13+
14+
class BIDSValidationError(ValueError):
15+
"""Error arising from invalid files or values in a BIDS dataset"""
16+
17+
18+
class Schema:
19+
schema: bst.types.Namespace
20+
21+
def __init__(
22+
self,
23+
schema: ty.Union[bst.types.Namespace, None] = None,
24+
):
25+
if schema is None:
26+
# Bundled
27+
schema = bst.schema.load_schema()
28+
self.schema = schema
29+
30+
@classmethod
31+
def from_spec(cls, schema_spec: str) -> "Schema":
32+
return cls(bst.schema.load_schema(schema_spec))
33+
34+
# Conveniences to avoid `schema.schema` pattern
35+
@property
36+
def objects(self) -> bst.types.Namespace:
37+
return self.schema.objects
38+
39+
@property
40+
def rules(self) -> bst.types.Namespace:
41+
return self.schema.rules
42+
43+
@property
44+
def meta(self) -> bst.types.Namespace:
45+
return self.schema.meta
46+
47+
48+
default_schema = Schema()
49+
50+
51+
class File(bt.File[Schema]):
52+
"""Generic file holder
53+
54+
This serves as a base class for :class:`BIDSFile` and can represent
55+
non-BIDS files.
56+
"""
57+
58+
def __init__(
59+
self,
60+
path: ty.Union[os.PathLike, str],
61+
dataset: ty.Optional["BIDSDataset"] = None,
62+
):
63+
self.path = Path(path)
64+
self.dataset = dataset
65+
66+
67+
class BIDSFile(File, bt.BIDSFile[Schema]):
68+
"""BIDS file"""
69+
70+
pattern = re.compile(
71+
r"""
72+
(?:(?P<entities>(?:[a-z]+-[a-zA-Z0-9]+(?:_[a-z]+-[a-zA-Z0-9]+)*))_)?
73+
(?P<suffix>[a-zA-Z0-9]+)
74+
(?P<extension>\.[^/\\]+)$
75+
""",
76+
re.VERBOSE,
77+
)
78+
79+
def __init__(
80+
self,
81+
path: ty.Union[os.PathLike, str],
82+
dataset: ty.Optional["BIDSDataset"] = None,
83+
):
84+
super().__init__(path, dataset)
85+
self.entities = {}
86+
self.datatype = None
87+
self.suffix = None
88+
self.extension = None
89+
90+
schema = default_schema if dataset is None else dataset.schema
91+
92+
if self.path.parent.name in schema.objects.datatypes:
93+
self.datatype = self.path.parent.name
94+
95+
matches = self.pattern.match(self.path.name)
96+
if matches is None:
97+
return
98+
99+
entities, self.suffix, self.extension = matches.groups()
100+
101+
if entities:
102+
found_entities = dict(ent.split("-") for ent in entities.split("_"))
103+
self.entities = {
104+
key: bt.Index(value) if entity.format == "index" else value
105+
for key, entity in schema.rules.entities.items()
106+
if (value := found_entities.get(entity.name)) is not None
107+
}
108+
109+
@cached_property
110+
def metadata(self) -> dict[str, ty.Any]:
111+
"""Sidecar metadata aggregated according to inheritance principle"""
112+
if not self.dataset:
113+
raise ValueError
114+
# TODO
115+
return {}
116+
117+
118+
class BIDSDataset(bt.BIDSDataset[Schema]):
119+
...

0 commit comments

Comments
 (0)