brkraw 0.3.11__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brkraw/__init__.py +9 -3
- brkraw/apps/__init__.py +12 -0
- brkraw/apps/addon/__init__.py +30 -0
- brkraw/apps/addon/core.py +35 -0
- brkraw/apps/addon/dependencies.py +402 -0
- brkraw/apps/addon/installation.py +500 -0
- brkraw/apps/addon/io.py +21 -0
- brkraw/apps/hook/__init__.py +25 -0
- brkraw/apps/hook/core.py +636 -0
- brkraw/apps/loader/__init__.py +10 -0
- brkraw/apps/loader/core.py +622 -0
- brkraw/apps/loader/formatter.py +288 -0
- brkraw/apps/loader/helper.py +797 -0
- brkraw/apps/loader/info/__init__.py +11 -0
- brkraw/apps/loader/info/scan.py +85 -0
- brkraw/apps/loader/info/scan.yaml +90 -0
- brkraw/apps/loader/info/study.py +69 -0
- brkraw/apps/loader/info/study.yaml +156 -0
- brkraw/apps/loader/info/transform.py +92 -0
- brkraw/apps/loader/types.py +220 -0
- brkraw/cli/__init__.py +5 -0
- brkraw/cli/commands/__init__.py +2 -0
- brkraw/cli/commands/addon.py +327 -0
- brkraw/cli/commands/config.py +205 -0
- brkraw/cli/commands/convert.py +903 -0
- brkraw/cli/commands/hook.py +348 -0
- brkraw/cli/commands/info.py +74 -0
- brkraw/cli/commands/init.py +214 -0
- brkraw/cli/commands/params.py +106 -0
- brkraw/cli/commands/prune.py +288 -0
- brkraw/cli/commands/session.py +371 -0
- brkraw/cli/hook_args.py +80 -0
- brkraw/cli/main.py +83 -0
- brkraw/cli/utils.py +60 -0
- brkraw/core/__init__.py +13 -0
- brkraw/core/config.py +380 -0
- brkraw/core/entrypoints.py +25 -0
- brkraw/core/formatter.py +367 -0
- brkraw/core/fs.py +495 -0
- brkraw/core/jcamp.py +600 -0
- brkraw/core/layout.py +451 -0
- brkraw/core/parameters.py +781 -0
- brkraw/core/zip.py +1121 -0
- brkraw/dataclasses/__init__.py +14 -0
- brkraw/dataclasses/node.py +139 -0
- brkraw/dataclasses/reco.py +33 -0
- brkraw/dataclasses/scan.py +61 -0
- brkraw/dataclasses/study.py +131 -0
- brkraw/default/__init__.py +3 -0
- brkraw/default/pruner_specs/deid4share.yaml +42 -0
- brkraw/default/rules/00_default.yaml +4 -0
- brkraw/default/specs/metadata_dicom.yaml +236 -0
- brkraw/default/specs/metadata_transforms.py +92 -0
- brkraw/resolver/__init__.py +7 -0
- brkraw/resolver/affine.py +539 -0
- brkraw/resolver/datatype.py +69 -0
- brkraw/resolver/fid.py +90 -0
- brkraw/resolver/helpers.py +36 -0
- brkraw/resolver/image.py +188 -0
- brkraw/resolver/nifti.py +370 -0
- brkraw/resolver/shape.py +235 -0
- brkraw/schema/__init__.py +3 -0
- brkraw/schema/context_map.yaml +62 -0
- brkraw/schema/meta.yaml +57 -0
- brkraw/schema/niftiheader.yaml +95 -0
- brkraw/schema/pruner.yaml +55 -0
- brkraw/schema/remapper.yaml +128 -0
- brkraw/schema/rules.yaml +154 -0
- brkraw/specs/__init__.py +10 -0
- brkraw/specs/hook/__init__.py +12 -0
- brkraw/specs/hook/logic.py +31 -0
- brkraw/specs/hook/validator.py +22 -0
- brkraw/specs/meta/__init__.py +5 -0
- brkraw/specs/meta/validator.py +156 -0
- brkraw/specs/pruner/__init__.py +15 -0
- brkraw/specs/pruner/logic.py +361 -0
- brkraw/specs/pruner/validator.py +119 -0
- brkraw/specs/remapper/__init__.py +27 -0
- brkraw/specs/remapper/logic.py +924 -0
- brkraw/specs/remapper/validator.py +314 -0
- brkraw/specs/rules/__init__.py +6 -0
- brkraw/specs/rules/logic.py +263 -0
- brkraw/specs/rules/validator.py +103 -0
- brkraw-0.5.0.dist-info/METADATA +81 -0
- brkraw-0.5.0.dist-info/RECORD +88 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info}/WHEEL +1 -2
- brkraw-0.5.0.dist-info/entry_points.txt +13 -0
- brkraw/lib/__init__.py +0 -4
- brkraw/lib/backup.py +0 -641
- brkraw/lib/bids.py +0 -0
- brkraw/lib/errors.py +0 -125
- brkraw/lib/loader.py +0 -1220
- brkraw/lib/orient.py +0 -194
- brkraw/lib/parser.py +0 -48
- brkraw/lib/pvobj.py +0 -301
- brkraw/lib/reference.py +0 -245
- brkraw/lib/utils.py +0 -471
- brkraw/scripts/__init__.py +0 -0
- brkraw/scripts/brk_backup.py +0 -106
- brkraw/scripts/brkraw.py +0 -744
- brkraw/ui/__init__.py +0 -0
- brkraw/ui/config.py +0 -17
- brkraw/ui/main_win.py +0 -214
- brkraw/ui/previewer.py +0 -225
- brkraw/ui/scan_info.py +0 -72
- brkraw/ui/scan_list.py +0 -73
- brkraw/ui/subj_info.py +0 -128
- brkraw-0.3.11.dist-info/METADATA +0 -25
- brkraw-0.3.11.dist-info/RECORD +0 -28
- brkraw-0.3.11.dist-info/entry_points.txt +0 -3
- brkraw-0.3.11.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import Any, List, Union, Optional, TYPE_CHECKING, cast, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from ..core.fs import DatasetFS
|
|
8
|
+
from ..core.parameters import Parameters
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..core.fs import DatasetFile
|
|
12
|
+
from ..core.zip import ZippedFile
|
|
13
|
+
|
|
14
|
+
def _is_probably_text(data: bytes) -> bool:
|
|
15
|
+
"""Heuristic to decide if data should be treated as text."""
|
|
16
|
+
if not data:
|
|
17
|
+
return True
|
|
18
|
+
sample = data[:1024]
|
|
19
|
+
if b"\x00" in sample:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
text_chars = set(range(0x20, 0x7F)) | {0x09, 0x0A, 0x0D, 0x08, 0x0C, 0x1B}
|
|
23
|
+
nontext = sum(b not in text_chars for b in sample)
|
|
24
|
+
return (nontext / len(sample)) < 0.30
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DatasetNode:
|
|
28
|
+
"""Shared utilities for dataset-backed nodes (Study, Scan, Reco)."""
|
|
29
|
+
|
|
30
|
+
fs: "DatasetFS"
|
|
31
|
+
relroot: str
|
|
32
|
+
_cache: Dict[str, Any]
|
|
33
|
+
|
|
34
|
+
def _full_path(self, name: str) -> str:
|
|
35
|
+
relroot = self.relroot.strip("/")
|
|
36
|
+
name = name.strip("/")
|
|
37
|
+
return f"{relroot}/{name}" if relroot else name
|
|
38
|
+
|
|
39
|
+
def _candidates(self, name: str) -> List[str]:
|
|
40
|
+
"""Generate possible dataset entry names for an attribute-like token."""
|
|
41
|
+
if not name:
|
|
42
|
+
return []
|
|
43
|
+
candidates = [name]
|
|
44
|
+
|
|
45
|
+
def add(candidate: str) -> None:
|
|
46
|
+
if candidate and candidate not in candidates:
|
|
47
|
+
candidates.append(candidate)
|
|
48
|
+
|
|
49
|
+
stripped = name[5:] if name.startswith("file_") else None
|
|
50
|
+
add(stripped or "")
|
|
51
|
+
|
|
52
|
+
dotted = name.replace("_", ".")
|
|
53
|
+
add(dotted if dotted != name else "")
|
|
54
|
+
|
|
55
|
+
if stripped:
|
|
56
|
+
dotted_stripped = stripped.replace("_", ".")
|
|
57
|
+
add(dotted_stripped)
|
|
58
|
+
|
|
59
|
+
return candidates
|
|
60
|
+
|
|
61
|
+
def _resolve_entry(self, relpath: str) -> Optional[Union["ZippedFile", "DatasetFile"]]:
|
|
62
|
+
"""Return the file-like entry object for a dataset-relative path."""
|
|
63
|
+
relpath = relpath.strip("/")
|
|
64
|
+
parent, _, leaf = relpath.rpartition("/")
|
|
65
|
+
dirpath = parent
|
|
66
|
+
entries = self.fs.iterdir(dirpath)
|
|
67
|
+
for entry in entries:
|
|
68
|
+
if entry.name == leaf and entry.is_file():
|
|
69
|
+
return cast(Union["ZippedFile", "DatasetFile"], entry)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def open(self, name: str):
|
|
73
|
+
"""Open a dataset-relative entry with best-effort typing.
|
|
74
|
+
|
|
75
|
+
Attempts to parse JCAMP parameters first; falls back to text or binary
|
|
76
|
+
buffers when parsing fails.
|
|
77
|
+
"""
|
|
78
|
+
if name.startswith("_"):
|
|
79
|
+
raise FileNotFoundError(name)
|
|
80
|
+
|
|
81
|
+
for candidate in self._candidates(name):
|
|
82
|
+
path = self._full_path(candidate)
|
|
83
|
+
cache_key = f"{path}"
|
|
84
|
+
cached = self._cache.get(cache_key)
|
|
85
|
+
if cached is not None:
|
|
86
|
+
return cached
|
|
87
|
+
|
|
88
|
+
entry = self._resolve_entry(path)
|
|
89
|
+
if entry is None:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
data = entry.read()
|
|
93
|
+
|
|
94
|
+
param = None
|
|
95
|
+
if Parameters._looks_like_jcamp(data):
|
|
96
|
+
try:
|
|
97
|
+
param = Parameters(data)
|
|
98
|
+
except Exception:
|
|
99
|
+
param = None
|
|
100
|
+
if param is not None:
|
|
101
|
+
self._cache[cache_key] = param
|
|
102
|
+
return param
|
|
103
|
+
|
|
104
|
+
if _is_probably_text(data):
|
|
105
|
+
try:
|
|
106
|
+
text = data.decode("utf-8")
|
|
107
|
+
obj = io.StringIO(text)
|
|
108
|
+
except UnicodeDecodeError:
|
|
109
|
+
obj = io.BytesIO(data)
|
|
110
|
+
else:
|
|
111
|
+
obj = io.BytesIO(data)
|
|
112
|
+
|
|
113
|
+
self._cache[cache_key] = obj
|
|
114
|
+
return obj
|
|
115
|
+
|
|
116
|
+
raise FileNotFoundError(name)
|
|
117
|
+
|
|
118
|
+
def __getattr__(self, name: str):
|
|
119
|
+
if name.startswith("_"):
|
|
120
|
+
raise AttributeError(name)
|
|
121
|
+
try:
|
|
122
|
+
return self.open(name)
|
|
123
|
+
except FileNotFoundError as exc:
|
|
124
|
+
raise AttributeError(name) from exc
|
|
125
|
+
|
|
126
|
+
def __getitem__(self, key: str):
|
|
127
|
+
"""Dictionary-style access to files (supports names not valid as attributes)."""
|
|
128
|
+
return self.open(str(key))
|
|
129
|
+
|
|
130
|
+
def listdir(self, relpath: str = "") -> List[str]:
|
|
131
|
+
"""List entries under this node (dirs first, then files)."""
|
|
132
|
+
target = self._full_path(relpath)
|
|
133
|
+
return self.fs.listdir(target)
|
|
134
|
+
|
|
135
|
+
def iterdir(self, relpath: str = ""):
|
|
136
|
+
"""Iterate over entries under this node as objects (dirs first, then files)."""
|
|
137
|
+
target = self._full_path(relpath)
|
|
138
|
+
for entry in self.fs.iterdir(target):
|
|
139
|
+
yield entry
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from ..core.fs import DatasetFS
|
|
5
|
+
from .node import DatasetNode
|
|
6
|
+
from typing import TYPE_CHECKING, Dict
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .scan import Scan
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Reco(DatasetNode):
|
|
13
|
+
fs: DatasetFS
|
|
14
|
+
scan_id: int
|
|
15
|
+
reco_id: int
|
|
16
|
+
relroot: str # e.g.: "3/pdata/1"
|
|
17
|
+
_cache: Dict[str, object] = field(default_factory=dict, init=False, repr=False)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_fs(cls, fs: DatasetFS, scan: "Scan", reco_id: int, relroot: str) -> "Reco":
|
|
21
|
+
return cls(fs=fs, scan_id=scan.scan_id, reco_id=reco_id, relroot=relroot)
|
|
22
|
+
|
|
23
|
+
def __repr__(self) -> str:
|
|
24
|
+
image_type = None
|
|
25
|
+
try:
|
|
26
|
+
reco_obj = getattr(self, "reco")
|
|
27
|
+
image_type = getattr(reco_obj, "RECO_image_type", None)
|
|
28
|
+
except Exception:
|
|
29
|
+
image_type = None
|
|
30
|
+
type_part = f" type={image_type!r}" if image_type is not None else ""
|
|
31
|
+
return f"Reco(scan_id={self.scan_id} id={self.reco_id} rel='/{self.relroot}'{type_part})"
|
|
32
|
+
|
|
33
|
+
__all__ = ['Reco']
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Dict, Optional, Mapping
|
|
4
|
+
|
|
5
|
+
from ..core.fs import DatasetFS
|
|
6
|
+
from .node import DatasetNode
|
|
7
|
+
from .reco import Reco
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Scan(DatasetNode):
|
|
12
|
+
fs: DatasetFS
|
|
13
|
+
scan_id: int
|
|
14
|
+
relroot: str
|
|
15
|
+
recos: Dict[int, Reco] = field(default_factory=dict)
|
|
16
|
+
_cache: Dict[str, object] = field(default_factory=dict, init=False, repr=False)
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_fs(cls, fs: DatasetFS, scan_id: int, relroot: str) -> "Scan":
|
|
20
|
+
scan = cls(fs, scan_id, relroot)
|
|
21
|
+
scan._index_recos(top=relroot)
|
|
22
|
+
return scan
|
|
23
|
+
|
|
24
|
+
def _index_recos(self, top: Optional[str] = None) -> None:
|
|
25
|
+
"""Find pdata/<reco_id> dirs under this scan and attach PVReco."""
|
|
26
|
+
import re
|
|
27
|
+
pdata_prefix = f"{self.relroot}/pdata"
|
|
28
|
+
|
|
29
|
+
for dirpath, dirnames, filenames in self.fs.walk(top=top or ""):
|
|
30
|
+
rel = self.fs.strip_anchor(dirpath)
|
|
31
|
+
if not rel.startswith(pdata_prefix):
|
|
32
|
+
continue
|
|
33
|
+
m = re.fullmatch(rf"{self.relroot}/pdata/(\d+)", rel)
|
|
34
|
+
if not m:
|
|
35
|
+
continue
|
|
36
|
+
reco_id = int(m.group(1))
|
|
37
|
+
self.recos[reco_id] = Reco.from_fs(
|
|
38
|
+
fs=self.fs,
|
|
39
|
+
scan=self,
|
|
40
|
+
reco_id=reco_id,
|
|
41
|
+
relroot=rel,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def get_reco(self, reco_id: int) -> Reco:
|
|
45
|
+
return self.recos[reco_id]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def avail(self) -> Mapping[int, Reco]:
|
|
49
|
+
return {k: self.recos[k] for k in sorted(self.recos)}
|
|
50
|
+
|
|
51
|
+
def __repr__(self) -> str:
|
|
52
|
+
method_val = None
|
|
53
|
+
try:
|
|
54
|
+
method_obj = getattr(self, "method")
|
|
55
|
+
method_val = getattr(method_obj, "Method", None)
|
|
56
|
+
except Exception:
|
|
57
|
+
method_val = None
|
|
58
|
+
method_part = f" Method={method_val!r}" if method_val is not None else ""
|
|
59
|
+
return f"Scan(id={self.scan_id} rel='/{self.relroot}'{method_part})"
|
|
60
|
+
|
|
61
|
+
__all__ = ['Scan']
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Mapping, Union, TYPE_CHECKING, List
|
|
5
|
+
|
|
6
|
+
from ..core.fs import DatasetFS
|
|
7
|
+
from .node import DatasetNode
|
|
8
|
+
from .scan import Scan
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..apps.loader.types import ScanLoader
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Study(DatasetNode):
|
|
16
|
+
fs: DatasetFS
|
|
17
|
+
relroot: str = ""
|
|
18
|
+
scans: Dict[int, Scan] = field(default_factory=dict)
|
|
19
|
+
_cache: Dict[str, object] = field(default_factory=dict, init=False, repr=False)
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_path(cls, path: Union[str, Path]) -> "Study":
|
|
23
|
+
"""Load a study rooted at path, preferring bottom-up discovery."""
|
|
24
|
+
fs = DatasetFS.from_path(path)
|
|
25
|
+
found = cls.discover(fs)
|
|
26
|
+
if not found:
|
|
27
|
+
raise ValueError(f"No Paravision study found under {path}")
|
|
28
|
+
|
|
29
|
+
anchor = fs.anchor
|
|
30
|
+
if anchor:
|
|
31
|
+
for study in found:
|
|
32
|
+
if study.relroot == anchor:
|
|
33
|
+
return study
|
|
34
|
+
|
|
35
|
+
if len(found) == 1:
|
|
36
|
+
return found[0]
|
|
37
|
+
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Multiple studies found under {path}; "
|
|
40
|
+
f"cannot choose automatically ({[s.relroot for s in found]})"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def discover(cls, fs: DatasetFS) -> List["Study"]:
|
|
45
|
+
"""Bottom-up discovery using reco markers (2dseq + visu_pars)."""
|
|
46
|
+
reco_dirs: List[str] = []
|
|
47
|
+
for dirpath, dirnames, filenames in fs.walk():
|
|
48
|
+
rel = fs.strip_anchor(dirpath)
|
|
49
|
+
names = set(filenames)
|
|
50
|
+
if "2dseq" in names and "visu_pars" in names:
|
|
51
|
+
reco_dirs.append(rel)
|
|
52
|
+
|
|
53
|
+
studies: Dict[str, Study] = {}
|
|
54
|
+
for reco_dir in reco_dirs:
|
|
55
|
+
parts = [p for p in reco_dir.split("/") if p]
|
|
56
|
+
if "pdata" not in parts:
|
|
57
|
+
continue
|
|
58
|
+
pdata_idx = parts.index("pdata")
|
|
59
|
+
if pdata_idx < 1 or pdata_idx + 1 >= len(parts):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
scan_id_part = parts[pdata_idx - 1]
|
|
63
|
+
if not scan_id_part.isdigit():
|
|
64
|
+
continue
|
|
65
|
+
scan_id = int(scan_id_part)
|
|
66
|
+
|
|
67
|
+
reco_id_part = parts[pdata_idx + 1]
|
|
68
|
+
if not reco_id_part.isdigit():
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
scan_root = "/".join(parts[:pdata_idx])
|
|
72
|
+
study_root = "/".join(parts[:pdata_idx - 1])
|
|
73
|
+
|
|
74
|
+
if not (
|
|
75
|
+
fs.exists(f"{scan_root}/method")
|
|
76
|
+
and fs.exists(f"{scan_root}/acqp")
|
|
77
|
+
and fs.exists(f"{reco_dir}/reco")
|
|
78
|
+
):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
study = studies.get(study_root)
|
|
82
|
+
if study is None:
|
|
83
|
+
study = cls(fs=fs, relroot=study_root, scans={})
|
|
84
|
+
studies[study_root] = study
|
|
85
|
+
|
|
86
|
+
if scan_id not in study.scans:
|
|
87
|
+
study.scans[scan_id] = Scan.from_fs(fs, scan_id, scan_root)
|
|
88
|
+
|
|
89
|
+
return [studies[k] for k in sorted(studies.keys())]
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def avail(self) -> Mapping[int, Union[Scan, "ScanLoader"]]:
|
|
93
|
+
return {k: self.scans[k] for k in sorted(self.scans)}
|
|
94
|
+
|
|
95
|
+
def get_scan(self, scan_id: int) -> Scan:
|
|
96
|
+
return self.scans[scan_id]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def has_subject(self) -> bool:
|
|
100
|
+
target = f"{self.relroot}/subject" if self.relroot else "subject"
|
|
101
|
+
return self.fs.exists(target)
|
|
102
|
+
|
|
103
|
+
def __repr__(self) -> str:
|
|
104
|
+
root_label = self.relroot or self.fs.root.name
|
|
105
|
+
mode = getattr(self.fs, "_mode", "dir")
|
|
106
|
+
|
|
107
|
+
subject_part = ""
|
|
108
|
+
try:
|
|
109
|
+
subj = getattr(self, "subject")
|
|
110
|
+
from ..core.parameters import Parameters # local import to avoid cycle
|
|
111
|
+
|
|
112
|
+
if isinstance(subj, Parameters):
|
|
113
|
+
sid = getattr(subj, "SUBJECT_id", None)
|
|
114
|
+
name = getattr(subj, "SUBJECT_name_string", None)
|
|
115
|
+
study_name = getattr(subj, "SUBJECT_study_name", None)
|
|
116
|
+
study_nr = getattr(subj, "SUBJECT_study_nr", None)
|
|
117
|
+
bits = []
|
|
118
|
+
if name is not None:
|
|
119
|
+
bits.append(f"name={name!r}")
|
|
120
|
+
if sid is not None:
|
|
121
|
+
bits.append(f"id={sid}")
|
|
122
|
+
if study_name is not None:
|
|
123
|
+
bits.append(f"study={study_name!r}")
|
|
124
|
+
if study_nr is not None:
|
|
125
|
+
bits.append(f"nr={study_nr}")
|
|
126
|
+
if bits:
|
|
127
|
+
subject_part = " subject(" + " ".join(bits) + ")"
|
|
128
|
+
except Exception:
|
|
129
|
+
subject_part = ""
|
|
130
|
+
|
|
131
|
+
return f"Study(root='{root_label}' mode={mode}{subject_part})"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "deid4share"
|
|
3
|
+
version: "0.2.0"
|
|
4
|
+
description: >
|
|
5
|
+
De-identification prune spec for Bruker ParaVision datasets. Drops the
|
|
6
|
+
subject file and redacts potential PII from JCAMP-DX parameters. Supports
|
|
7
|
+
template variables: $subject_id, $subject_name, $study_id.
|
|
8
|
+
category: "pruner_spec"
|
|
9
|
+
|
|
10
|
+
mode: "keep"
|
|
11
|
+
|
|
12
|
+
files:
|
|
13
|
+
- "method"
|
|
14
|
+
- "acqp"
|
|
15
|
+
- "reco"
|
|
16
|
+
- "visu_pars"
|
|
17
|
+
- "2dseq"
|
|
18
|
+
|
|
19
|
+
update_params:
|
|
20
|
+
acqp:
|
|
21
|
+
ACQ_operator: ""
|
|
22
|
+
ACQ_institution: ""
|
|
23
|
+
ACQ_station: ""
|
|
24
|
+
ACQ_time: "<00:00:00 01 Jan 2000>"
|
|
25
|
+
ACQ_abs_time: "0"
|
|
26
|
+
|
|
27
|
+
visu_pars:
|
|
28
|
+
VisuInstitution: ""
|
|
29
|
+
VisuSubjectId: "$subject_id"
|
|
30
|
+
VisuSubjectName: "$subject_name"
|
|
31
|
+
VisuStudyId: "$study_id"
|
|
32
|
+
VisuStudyDate: "<00:00:00 01 Jan 2000>"
|
|
33
|
+
VisuStudyUid: "<1.2.3.4.5>"
|
|
34
|
+
VisuSeriesUid: "<1.2.3.4.5.1>"
|
|
35
|
+
VisuFrameUid: "<1.2.3.4.5.2>"
|
|
36
|
+
VisuSystemOrderNumber: "<>"
|
|
37
|
+
VisuInstitution: "<>"
|
|
38
|
+
|
|
39
|
+
reco:
|
|
40
|
+
RECO_base_image_uid: "<1.2.3.4.5.6>"
|
|
41
|
+
|
|
42
|
+
add_root: true
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "metadata_dicom"
|
|
3
|
+
version: "0.0.1"
|
|
4
|
+
description: "DICOM metadata mapping for Bruker PvDataset."
|
|
5
|
+
category: "metadata_spec"
|
|
6
|
+
transforms_source: "metadata_transforms.py"
|
|
7
|
+
|
|
8
|
+
ImageType:
|
|
9
|
+
sources:
|
|
10
|
+
- file: visu_pars
|
|
11
|
+
key: VisuSeriesTypeId
|
|
12
|
+
transform: strip_jcamp_string
|
|
13
|
+
|
|
14
|
+
AcquisitionDateTime:
|
|
15
|
+
sources:
|
|
16
|
+
- file: visu_pars
|
|
17
|
+
key: VisuAcqDate
|
|
18
|
+
transform: strip_jcamp_string
|
|
19
|
+
|
|
20
|
+
MagneticFieldStrength:
|
|
21
|
+
sources:
|
|
22
|
+
- file: visu_pars
|
|
23
|
+
key: VisuMagneticFieldStrength
|
|
24
|
+
|
|
25
|
+
ScanningSequence:
|
|
26
|
+
sources:
|
|
27
|
+
- file: visu_pars
|
|
28
|
+
key: VisuAcqSequenceName
|
|
29
|
+
transform: strip_jcamp_string
|
|
30
|
+
|
|
31
|
+
SequenceVariant:
|
|
32
|
+
sources:
|
|
33
|
+
- file: visu_pars
|
|
34
|
+
key: VisuAcqSequenceName
|
|
35
|
+
transform: strip_jcamp_string
|
|
36
|
+
|
|
37
|
+
ScanOptions:
|
|
38
|
+
sources:
|
|
39
|
+
- file: visu_pars
|
|
40
|
+
key: VisuAcqSpectralSuppression
|
|
41
|
+
transform: strip_jcamp_string
|
|
42
|
+
|
|
43
|
+
RepetitionTime:
|
|
44
|
+
sources:
|
|
45
|
+
- file: visu_pars
|
|
46
|
+
key: VisuAcqRepetitionTime
|
|
47
|
+
|
|
48
|
+
EchoTime:
|
|
49
|
+
sources:
|
|
50
|
+
- file: visu_pars
|
|
51
|
+
key: VisuAcqEchoTime
|
|
52
|
+
|
|
53
|
+
InversionTime:
|
|
54
|
+
sources:
|
|
55
|
+
- file: visu_pars
|
|
56
|
+
key: VisuAcqInversionTime
|
|
57
|
+
|
|
58
|
+
FlipAngle:
|
|
59
|
+
sources:
|
|
60
|
+
- file: visu_pars
|
|
61
|
+
key: VisuAcqFlipAngle
|
|
62
|
+
|
|
63
|
+
SliceThickness:
|
|
64
|
+
sources:
|
|
65
|
+
- file: visu_pars
|
|
66
|
+
key: VisuCoreSliceThickness
|
|
67
|
+
|
|
68
|
+
PixelBandwidth:
|
|
69
|
+
sources:
|
|
70
|
+
- file: visu_pars
|
|
71
|
+
key: VisuAcqPixelBandwidth
|
|
72
|
+
|
|
73
|
+
InPlanePhaseEncodingDirection:
|
|
74
|
+
sources:
|
|
75
|
+
- file: visu_pars
|
|
76
|
+
key: VisuAcqGradEncoding
|
|
77
|
+
|
|
78
|
+
PercentSampling:
|
|
79
|
+
sources:
|
|
80
|
+
- file: visu_pars
|
|
81
|
+
key: VisuAcqPartialFourier
|
|
82
|
+
|
|
83
|
+
PercentPhaseFieldOfView:
|
|
84
|
+
sources:
|
|
85
|
+
- file: visu_pars
|
|
86
|
+
key: VisuCoreExtent
|
|
87
|
+
|
|
88
|
+
PixelSpacing:
|
|
89
|
+
inputs:
|
|
90
|
+
extent:
|
|
91
|
+
sources:
|
|
92
|
+
- file: visu_pars
|
|
93
|
+
key: VisuCoreExtent
|
|
94
|
+
size:
|
|
95
|
+
sources:
|
|
96
|
+
- file: visu_pars
|
|
97
|
+
key: VisuCoreSize
|
|
98
|
+
transform: pixel_spacing_from_extent
|
|
99
|
+
|
|
100
|
+
SliceThickness_FG:
|
|
101
|
+
sources:
|
|
102
|
+
- file: visu_pars
|
|
103
|
+
key: VisuCoreSliceThickness
|
|
104
|
+
|
|
105
|
+
ImagePositionPatient:
|
|
106
|
+
sources:
|
|
107
|
+
- file: visu_pars
|
|
108
|
+
key: VisuCorePosition
|
|
109
|
+
transform: as_list
|
|
110
|
+
|
|
111
|
+
ImageOrientationPatient:
|
|
112
|
+
sources:
|
|
113
|
+
- file: visu_pars
|
|
114
|
+
key: VisuCoreOrientation
|
|
115
|
+
transform: as_list
|
|
116
|
+
|
|
117
|
+
RescaleSlope_FG:
|
|
118
|
+
sources:
|
|
119
|
+
- file: visu_pars
|
|
120
|
+
key: VisuCoreDataSlope
|
|
121
|
+
|
|
122
|
+
RescaleIntercept_FG:
|
|
123
|
+
sources:
|
|
124
|
+
- file: visu_pars
|
|
125
|
+
key: VisuCoreDataOffs
|
|
126
|
+
|
|
127
|
+
RepetitionTime_FG:
|
|
128
|
+
sources:
|
|
129
|
+
- file: visu_pars
|
|
130
|
+
key: VisuAcqRepetitionTime
|
|
131
|
+
|
|
132
|
+
EchoTime_FG:
|
|
133
|
+
sources:
|
|
134
|
+
- file: visu_pars
|
|
135
|
+
key: VisuAcqEchoTime
|
|
136
|
+
|
|
137
|
+
FlipAngle_FG:
|
|
138
|
+
sources:
|
|
139
|
+
- file: visu_pars
|
|
140
|
+
key: VisuAcqFlipAngle
|
|
141
|
+
|
|
142
|
+
EchoTrainLength:
|
|
143
|
+
sources:
|
|
144
|
+
- file: visu_pars
|
|
145
|
+
key: VisuAcqEchoTrainLength
|
|
146
|
+
|
|
147
|
+
NumberOfAverages:
|
|
148
|
+
sources:
|
|
149
|
+
- file: visu_pars
|
|
150
|
+
key: VisuAcqNAverages
|
|
151
|
+
|
|
152
|
+
PartialFourier:
|
|
153
|
+
sources:
|
|
154
|
+
- file: visu_pars
|
|
155
|
+
key: VisuAcqPartialFourier
|
|
156
|
+
|
|
157
|
+
PhaseEncodingDirectionPositive:
|
|
158
|
+
sources:
|
|
159
|
+
- file: visu_pars
|
|
160
|
+
key: VisuAcqGradEncoding
|
|
161
|
+
|
|
162
|
+
DiffusionBValue_FG:
|
|
163
|
+
sources:
|
|
164
|
+
- file: visu_pars
|
|
165
|
+
key: VisuAcqDiffusionBMatrix
|
|
166
|
+
|
|
167
|
+
DiffusionGradientOrientation_FG:
|
|
168
|
+
sources:
|
|
169
|
+
- file: visu_pars
|
|
170
|
+
key: VisuAcqDiffusionGradOrient
|
|
171
|
+
transform: as_list
|
|
172
|
+
|
|
173
|
+
MRSpectroscopyAcquisitionType:
|
|
174
|
+
sources:
|
|
175
|
+
- file: visu_pars
|
|
176
|
+
key: VisuMrsAcquisitionType
|
|
177
|
+
transform: strip_jcamp_string
|
|
178
|
+
|
|
179
|
+
ResonantNucleus:
|
|
180
|
+
sources:
|
|
181
|
+
- file: visu_pars
|
|
182
|
+
key: VisuMrsResonantNuclei
|
|
183
|
+
transform: strip_jcamp_string
|
|
184
|
+
|
|
185
|
+
SpectralWidth_MRS:
|
|
186
|
+
sources:
|
|
187
|
+
- file: visu_pars
|
|
188
|
+
key: VisuMrsSpectralWidth
|
|
189
|
+
|
|
190
|
+
TransmitterFrequency_MRS:
|
|
191
|
+
sources:
|
|
192
|
+
- file: visu_pars
|
|
193
|
+
key: VisuMrsTransmitterFreq
|
|
194
|
+
|
|
195
|
+
ChemicalShiftReference:
|
|
196
|
+
sources:
|
|
197
|
+
- file: visu_pars
|
|
198
|
+
key: VisuMrsChemicalShiftRef
|
|
199
|
+
|
|
200
|
+
NumberOfZeroFills:
|
|
201
|
+
sources:
|
|
202
|
+
- file: visu_pars
|
|
203
|
+
key: VisuMrsZeroFill
|
|
204
|
+
|
|
205
|
+
KSpaceFiltering:
|
|
206
|
+
sources:
|
|
207
|
+
- file: visu_pars
|
|
208
|
+
key: VisuAcqKSpaceFiltering
|
|
209
|
+
|
|
210
|
+
PulseSequenceName_MRS:
|
|
211
|
+
sources:
|
|
212
|
+
- file: visu_pars
|
|
213
|
+
key: VisuAcqSequenceName
|
|
214
|
+
transform: strip_jcamp_string
|
|
215
|
+
|
|
216
|
+
EchoTime_MRS:
|
|
217
|
+
sources:
|
|
218
|
+
- file: visu_pars
|
|
219
|
+
key: VisuAcqEchoTime
|
|
220
|
+
|
|
221
|
+
RepetitionTime_MRS:
|
|
222
|
+
sources:
|
|
223
|
+
- file: visu_pars
|
|
224
|
+
key: VisuAcqRepetitionTime
|
|
225
|
+
|
|
226
|
+
WaterSuppression:
|
|
227
|
+
sources:
|
|
228
|
+
- file: visu_pars
|
|
229
|
+
key: VisuAcqSpectralSuppression
|
|
230
|
+
transform: strip_jcamp_string
|
|
231
|
+
|
|
232
|
+
VolumeLocalizationTechnique:
|
|
233
|
+
sources:
|
|
234
|
+
- file: visu_pars
|
|
235
|
+
key: VisuMrsLocalizationTechnique
|
|
236
|
+
transform: strip_jcamp_string
|