brkraw-viewer 0.2.5__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_viewer/__init__.py +4 -0
- brkraw_viewer/apps/__init__.py +0 -0
- brkraw_viewer/apps/config.py +90 -0
- brkraw_viewer/apps/convert.py +1689 -0
- brkraw_viewer/apps/hooks.py +36 -0
- brkraw_viewer/apps/viewer.py +5316 -0
- brkraw_viewer/assets/icon.ico +0 -0
- brkraw_viewer/assets/icon.png +0 -0
- brkraw_viewer/frames/__init__.py +2 -0
- brkraw_viewer/frames/params_panel.py +80 -0
- brkraw_viewer/frames/viewer_canvas.py +340 -0
- brkraw_viewer/frames/viewer_config.py +101 -0
- brkraw_viewer/plugin.py +125 -0
- brkraw_viewer/registry.py +258 -0
- brkraw_viewer/snippets/context_map/basic.yaml +4 -0
- brkraw_viewer/snippets/context_map/enum-map.yaml +5 -0
- brkraw_viewer/snippets/rule/basic.yaml +10 -0
- brkraw_viewer/snippets/rule/when-contains.yaml +10 -0
- brkraw_viewer/snippets/spec/basic.yaml +5 -0
- brkraw_viewer/snippets/spec/list-source.yaml +5 -0
- brkraw_viewer/snippets/spec/with-default.yaml +5 -0
- brkraw_viewer/utils/__init__.py +2 -0
- brkraw_viewer/utils/orientation.py +17 -0
- brkraw_viewer-0.2.5.dist-info/METADATA +170 -0
- brkraw_viewer-0.2.5.dist-info/RECORD +28 -0
- brkraw_viewer-0.2.5.dist-info/WHEEL +5 -0
- brkraw_viewer-0.2.5.dist-info/entry_points.txt +2 -0
- brkraw_viewer-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Mapping
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import zipfile
|
|
9
|
+
import datetime as dt
|
|
10
|
+
|
|
11
|
+
from brkraw.apps.loader import BrukerLoader
|
|
12
|
+
from brkraw.core.fs import DatasetFS
|
|
13
|
+
from brkraw.dataclasses.study import Study
|
|
14
|
+
|
|
15
|
+
from .frames.viewer_config import registry_path
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("brkraw.viewer")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class RegistryEntry:
|
|
22
|
+
path: str
|
|
23
|
+
basename: str
|
|
24
|
+
study: Dict[str, Any]
|
|
25
|
+
num_scans: int
|
|
26
|
+
kind: str
|
|
27
|
+
added_at: str
|
|
28
|
+
last_seen: str
|
|
29
|
+
|
|
30
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
31
|
+
return {
|
|
32
|
+
"path": self.path,
|
|
33
|
+
"basename": self.basename,
|
|
34
|
+
"study": _json_safe(self.study),
|
|
35
|
+
"num_scans": self.num_scans,
|
|
36
|
+
"kind": self.kind,
|
|
37
|
+
"added_at": self.added_at,
|
|
38
|
+
"last_seen": self.last_seen,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _now_iso() -> str:
|
|
43
|
+
from datetime import datetime, timezone
|
|
44
|
+
|
|
45
|
+
return datetime.now(timezone.utc).isoformat()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_path(path: Path) -> str:
|
|
49
|
+
path = path.expanduser()
|
|
50
|
+
try:
|
|
51
|
+
return str(path.resolve())
|
|
52
|
+
except FileNotFoundError:
|
|
53
|
+
return str(path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _ensure_registry_path(root: Optional[Path] = None) -> Path:
|
|
57
|
+
reg_path = registry_path(root)
|
|
58
|
+
reg_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
if not reg_path.exists():
|
|
60
|
+
reg_path.write_text("", encoding="utf-8")
|
|
61
|
+
return reg_path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_registry(root: Optional[Path] = None) -> List[Dict[str, Any]]:
|
|
65
|
+
reg_path = registry_path(root)
|
|
66
|
+
if not reg_path.exists():
|
|
67
|
+
return []
|
|
68
|
+
entries: List[Dict[str, Any]] = []
|
|
69
|
+
for line in reg_path.read_text(encoding="utf-8").splitlines():
|
|
70
|
+
text = line.strip()
|
|
71
|
+
if not text:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
payload = json.loads(text)
|
|
75
|
+
except json.JSONDecodeError:
|
|
76
|
+
logger.warning("Skipping invalid registry line: %s", text)
|
|
77
|
+
continue
|
|
78
|
+
if isinstance(payload, dict):
|
|
79
|
+
entries.append(payload)
|
|
80
|
+
return entries
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def write_registry(entries: Iterable[Dict[str, Any]], root: Optional[Path] = None) -> None:
|
|
84
|
+
reg_path = _ensure_registry_path(root)
|
|
85
|
+
lines = [json.dumps(_json_safe(entry), ensure_ascii=True) for entry in entries]
|
|
86
|
+
reg_path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _discover_study_paths(path: Path) -> List[Path]:
|
|
90
|
+
fs = DatasetFS.from_path(path)
|
|
91
|
+
studies = Study.discover(fs)
|
|
92
|
+
if not studies:
|
|
93
|
+
return []
|
|
94
|
+
if len(studies) == 1:
|
|
95
|
+
study = studies[0]
|
|
96
|
+
return [fs.root / study.relroot if study.relroot else fs.root]
|
|
97
|
+
if fs._mode == "dir":
|
|
98
|
+
return [fs.root / study.relroot if study.relroot else fs.root for study in studies]
|
|
99
|
+
raise ValueError("Multiple studies found in archive; extract or point to a single study.")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _discover_dataset_paths(path: Path) -> List[Path]:
|
|
103
|
+
logger.info("Scanning for datasets under %s", path)
|
|
104
|
+
if _is_archive(path):
|
|
105
|
+
logger.debug("Found archive dataset: %s", path)
|
|
106
|
+
return [path]
|
|
107
|
+
if path.is_dir():
|
|
108
|
+
direct = _discover_study_paths(path)
|
|
109
|
+
if direct:
|
|
110
|
+
logger.info("Found study dataset: %s", path)
|
|
111
|
+
return direct
|
|
112
|
+
discovered: List[Path] = []
|
|
113
|
+
for child in sorted(path.iterdir()):
|
|
114
|
+
if child.name.startswith("."):
|
|
115
|
+
continue
|
|
116
|
+
if _is_archive(child):
|
|
117
|
+
logger.debug("Found archive dataset: %s", child)
|
|
118
|
+
discovered.append(child)
|
|
119
|
+
continue
|
|
120
|
+
if child.is_dir():
|
|
121
|
+
try:
|
|
122
|
+
studies = _discover_study_paths(child)
|
|
123
|
+
except ValueError as exc:
|
|
124
|
+
logger.warning("Skipping %s: %s", child, exc)
|
|
125
|
+
continue
|
|
126
|
+
if studies:
|
|
127
|
+
for study in studies:
|
|
128
|
+
logger.info("Found study dataset: %s", study)
|
|
129
|
+
discovered.extend(studies)
|
|
130
|
+
logger.info("Discovery complete: %d dataset(s) under %s", len(discovered), path)
|
|
131
|
+
return discovered
|
|
132
|
+
return []
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _is_archive(path: Path) -> bool:
|
|
136
|
+
return path.is_file() and zipfile.is_zipfile(path)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _load_study_info(loader: BrukerLoader) -> Dict[str, Any]:
|
|
140
|
+
info: Dict[str, Any] = {}
|
|
141
|
+
try:
|
|
142
|
+
subject = loader.subject
|
|
143
|
+
if isinstance(subject, dict):
|
|
144
|
+
study = subject.get("Study", {})
|
|
145
|
+
if isinstance(study, dict):
|
|
146
|
+
info = dict(study)
|
|
147
|
+
except Exception:
|
|
148
|
+
info = {}
|
|
149
|
+
return info
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _json_safe(value: Any) -> Any:
|
|
153
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
154
|
+
return value
|
|
155
|
+
if isinstance(value, dict):
|
|
156
|
+
return {str(k): _json_safe(v) for k, v in value.items()}
|
|
157
|
+
if isinstance(value, (list, tuple)):
|
|
158
|
+
return [_json_safe(v) for v in value]
|
|
159
|
+
if isinstance(value, dt.datetime):
|
|
160
|
+
return value.date().isoformat()
|
|
161
|
+
if isinstance(value, dt.date):
|
|
162
|
+
return value.isoformat()
|
|
163
|
+
return str(value)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def build_entry(path: Path) -> RegistryEntry:
|
|
167
|
+
norm = normalize_path(path)
|
|
168
|
+
basename = path.name
|
|
169
|
+
kind = "archive" if _is_archive(path) else "study"
|
|
170
|
+
loader = BrukerLoader(path)
|
|
171
|
+
study_info = _load_study_info(loader)
|
|
172
|
+
num_scans = len(loader.avail)
|
|
173
|
+
timestamp = _now_iso()
|
|
174
|
+
return RegistryEntry(
|
|
175
|
+
path=norm,
|
|
176
|
+
basename=basename,
|
|
177
|
+
study=study_info,
|
|
178
|
+
num_scans=num_scans,
|
|
179
|
+
kind=kind,
|
|
180
|
+
added_at=timestamp,
|
|
181
|
+
last_seen=timestamp,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _merge_entries(
|
|
186
|
+
existing: Dict[str, Dict[str, Any]],
|
|
187
|
+
new_entries: Iterable[RegistryEntry],
|
|
188
|
+
) -> Tuple[Dict[str, Dict[str, Any]], int]:
|
|
189
|
+
added = 0
|
|
190
|
+
for entry in new_entries:
|
|
191
|
+
current = existing.get(entry.path)
|
|
192
|
+
if current is None:
|
|
193
|
+
existing[entry.path] = entry.as_dict()
|
|
194
|
+
added += 1
|
|
195
|
+
else:
|
|
196
|
+
updated = dict(current)
|
|
197
|
+
updated.update(entry.as_dict())
|
|
198
|
+
updated["added_at"] = current.get("added_at", entry.added_at)
|
|
199
|
+
existing[entry.path] = updated
|
|
200
|
+
return existing, added
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def register_paths(paths: Iterable[Path], root: Optional[Path] = None) -> List[RegistryEntry]:
|
|
204
|
+
entries: List[RegistryEntry] = []
|
|
205
|
+
for raw in paths:
|
|
206
|
+
expanded = raw.expanduser()
|
|
207
|
+
if not expanded.exists():
|
|
208
|
+
raise FileNotFoundError(expanded)
|
|
209
|
+
logger.debug("Registering dataset path: %s", expanded)
|
|
210
|
+
try:
|
|
211
|
+
dataset_paths = _discover_dataset_paths(expanded)
|
|
212
|
+
except ValueError as exc:
|
|
213
|
+
raise exc
|
|
214
|
+
if not dataset_paths:
|
|
215
|
+
raise ValueError(f"No Paravision study found under {expanded}")
|
|
216
|
+
for study_path in dataset_paths:
|
|
217
|
+
logger.info("Building registry entry: %s", study_path)
|
|
218
|
+
entries.append(build_entry(study_path))
|
|
219
|
+
existing = {entry["path"]: entry for entry in load_registry(root)}
|
|
220
|
+
merged, _ = _merge_entries(existing, entries)
|
|
221
|
+
write_registry(merged.values(), root=root)
|
|
222
|
+
return entries
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def unregister_paths(paths: Iterable[Path], root: Optional[Path] = None) -> int:
|
|
226
|
+
normalized = {normalize_path(path) for path in paths}
|
|
227
|
+
entries = load_registry(root)
|
|
228
|
+
kept = [entry for entry in entries if entry.get("path") not in normalized]
|
|
229
|
+
removed = len(entries) - len(kept)
|
|
230
|
+
write_registry(kept, root=root)
|
|
231
|
+
return removed
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def registry_status(root: Optional[Path] = None) -> List[Dict[str, Any]]:
|
|
235
|
+
entries = load_registry(root)
|
|
236
|
+
for entry in entries:
|
|
237
|
+
path = entry.get("path", "")
|
|
238
|
+
entry["missing"] = not Path(str(path)).exists()
|
|
239
|
+
return entries
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def resolve_entry_value(entry: Mapping[str, Any], key: str) -> str:
|
|
243
|
+
if key == "basename":
|
|
244
|
+
return str(entry.get("basename", ""))
|
|
245
|
+
if key == "path":
|
|
246
|
+
return str(entry.get("path", ""))
|
|
247
|
+
if key == "num_scans":
|
|
248
|
+
return str(entry.get("num_scans", ""))
|
|
249
|
+
if key == "kind":
|
|
250
|
+
return str(entry.get("kind", ""))
|
|
251
|
+
if key == "missing":
|
|
252
|
+
return "Yes" if entry.get("missing") else "No"
|
|
253
|
+
if key.startswith("Study."):
|
|
254
|
+
study = entry.get("study", {})
|
|
255
|
+
if isinstance(study, dict):
|
|
256
|
+
field = key.split(".", 1)[1]
|
|
257
|
+
return str(study.get(field, ""))
|
|
258
|
+
return str(entry.get(key, ""))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import nibabel as nib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def reorient_to_ras(data: np.ndarray, affine: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
8
|
+
data = np.asarray(data)
|
|
9
|
+
affine = np.asarray(affine, dtype=float)
|
|
10
|
+
|
|
11
|
+
ornt = nib.orientations.io_orientation(affine)
|
|
12
|
+
ras_ornt = np.array([[0, 1], [1, 1], [2, 1]]) # RAS
|
|
13
|
+
transform = nib.orientations.ornt_transform(ornt, ras_ornt)
|
|
14
|
+
new_data = nib.orientations.apply_orientation(data, transform)
|
|
15
|
+
new_affine = affine @ nib.orientations.inv_ornt_aff(transform, data.shape)
|
|
16
|
+
return new_data, new_affine
|
|
17
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brkraw-viewer
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: BrkRaw scan viewer plugin for brkraw CLI.
|
|
5
|
+
Author: BrkRaw
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: brkraw>=0.5.0rc1
|
|
13
|
+
Requires-Dist: nibabel>=5.0
|
|
14
|
+
Requires-Dist: pillow>=10.0
|
|
15
|
+
Provides-Extra: docs
|
|
16
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=7.4; extra == "test"
|
|
19
|
+
|
|
20
|
+
<h1 align="left">
|
|
21
|
+
<picture>
|
|
22
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/brkraw-viewer-logo-dark.svg">
|
|
23
|
+
<img alt="BrkRaw Viewer" src="docs/assets/brkraw-viewer-logo-light.svg" width="410">
|
|
24
|
+
</picture>
|
|
25
|
+
</h1>
|
|
26
|
+
|
|
27
|
+
BrkRaw Viewer is an interactive dataset viewer implemented as a
|
|
28
|
+
separate CLI plugin for the `brkraw` command.
|
|
29
|
+
|
|
30
|
+
The viewer is intentionally maintained outside the BrkRaw core to
|
|
31
|
+
enable independent development and community contributions around
|
|
32
|
+
user-facing interfaces.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Scope and intent
|
|
37
|
+
|
|
38
|
+
BrkRaw Viewer is designed for **interactive inspection** of Bruker
|
|
39
|
+
Paravision datasets. It focuses on quick exploration and validation
|
|
40
|
+
rather than data conversion or analysis.
|
|
41
|
+
|
|
42
|
+
The goal is to provide practical, researcher-focused features that are
|
|
43
|
+
useful in everyday workflows, such as quick dataset triage, metadata
|
|
44
|
+
checks, and lightweight visual QC.
|
|
45
|
+
|
|
46
|
+
Typical use cases include:
|
|
47
|
+
|
|
48
|
+
- Browsing studies, scans, and reconstructions
|
|
49
|
+
- Verifying scan and reconstruction IDs
|
|
50
|
+
- Inspecting acquisition metadata before conversion
|
|
51
|
+
- Lightweight visual sanity checks
|
|
52
|
+
|
|
53
|
+
All data conversion and reproducible workflows are handled by the
|
|
54
|
+
BrkRaw CLI and Python API.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Why these features exist
|
|
59
|
+
|
|
60
|
+
**Viewer**
|
|
61
|
+
The Viewer tab makes it easy to confirm the right scan and orientation before
|
|
62
|
+
running a larger workflow.
|
|
63
|
+
|
|
64
|
+
**Registry**
|
|
65
|
+
The Registry reduces repeated filesystem navigation and lets you re-open the
|
|
66
|
+
current session with a single menu action.
|
|
67
|
+
|
|
68
|
+
**Extensions/hooks**
|
|
69
|
+
Extensions allow modality-specific panels (MRS, BIDS, etc.) to live outside the
|
|
70
|
+
core viewer so the default install stays lightweight.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Design goal: shared extensibility
|
|
75
|
+
|
|
76
|
+
brkraw-viewer keeps the BrkRaw design philosophy: extend the ecosystem
|
|
77
|
+
without changing core logic. The viewer uses the same rules/spec/layout
|
|
78
|
+
system as the CLI and Python API, and it exposes UI extensions via the
|
|
79
|
+
`brkraw.viewer.hook` entry point so new tabs can be added with standalone
|
|
80
|
+
packages. Viewer hooks can coexist with converter hooks and CLI hooks,
|
|
81
|
+
so modality-specific logic can flow from conversion into UI without
|
|
82
|
+
patching the viewer itself.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## UI direction
|
|
87
|
+
|
|
88
|
+
The default viewer targets a **tkinter-based** implementation.
|
|
89
|
+
|
|
90
|
+
This choice is intentional: we want a lightweight tool that can be
|
|
91
|
+
used directly on scanner consoles or constrained environments with
|
|
92
|
+
minimal dependencies.
|
|
93
|
+
|
|
94
|
+
More modern GUI frameworks are welcome, but should be developed as
|
|
95
|
+
separate CLI extensions to keep the default viewer small and easy to
|
|
96
|
+
install.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Viewer hooks
|
|
101
|
+
|
|
102
|
+
Viewer extensions are implemented as hooks discovered through
|
|
103
|
+
`brkraw.viewer.hook`. Each hook can register a new tab and provide
|
|
104
|
+
dataset callbacks, enabling feature panels to live outside the core
|
|
105
|
+
viewer while staying compatible with BrkRaw rules, specs, and converter
|
|
106
|
+
hooks. See `docs/dev/hooks.md` for the hook interface and entry point
|
|
107
|
+
setup.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Installation
|
|
112
|
+
|
|
113
|
+
For development and testing, install in editable mode:
|
|
114
|
+
|
|
115
|
+
pip install -e .
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
Launch the viewer via the BrkRaw CLI:
|
|
122
|
+
|
|
123
|
+
brkraw viewer /path/to/bruker/study
|
|
124
|
+
|
|
125
|
+
Optional arguments allow opening a specific scan or slice:
|
|
126
|
+
|
|
127
|
+
brkraw viewer /path/to/bruker/study \
|
|
128
|
+
--scan 3 \
|
|
129
|
+
--reco 1
|
|
130
|
+
|
|
131
|
+
The viewer can also open `.zip` or Paravision-exported `.PvDatasets`
|
|
132
|
+
archives using `Load` (folder or archive file).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Update
|
|
137
|
+
|
|
138
|
+
Recent updates:
|
|
139
|
+
|
|
140
|
+
- Open folders or archives (`.zip` / `.PvDatasets`)
|
|
141
|
+
- Viewer: `Space` (`raw/scanner/subject_ras`), nibabel RAS display, click-to-set `X/Y/Z`, optional crosshair + zoom,
|
|
142
|
+
slicepack/frame sliders only when needed
|
|
143
|
+
- Info: rule + spec selection (installed or file), parameter search, lazy Viewer refresh on tab focus
|
|
144
|
+
- Registry: add the current session from the `+` menu when a dataset is loaded
|
|
145
|
+
- Convert: BrkRaw layout engine, template + suffix defaults from `~/.brkraw/config.yaml`, keys browser (click to add),
|
|
146
|
+
optional config `layout_entries`
|
|
147
|
+
- Config: edit `~/.brkraw/config.yaml` in-app; basic focus/icon UX
|
|
148
|
+
|
|
149
|
+
This update keeps dependencies minimal and preserves compatibility with
|
|
150
|
+
the core BrkRaw rule/spec/hook system.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Contributing
|
|
155
|
+
|
|
156
|
+
We welcome contributions related to:
|
|
157
|
+
|
|
158
|
+
- New viewer hooks that add modality-specific panels or workflows
|
|
159
|
+
- Alternative UI implementations delivered as separate CLI extensions
|
|
160
|
+
- fMRI/MRS/BIDS-focused visualization or QC helpers built on hooks
|
|
161
|
+
- Multi-dataset session management and registry enhancements
|
|
162
|
+
- Performance and memory improvements for large datasets
|
|
163
|
+
|
|
164
|
+
Contributions should prefer designs where new hooks extend the viewer
|
|
165
|
+
implicitly through shared BrkRaw abstractions, and where richer UIs are
|
|
166
|
+
provided as optional CLI extensions rather than increasing the default
|
|
167
|
+
dependency footprint.
|
|
168
|
+
|
|
169
|
+
If you are interested in contributing, please start a discussion or
|
|
170
|
+
open an issue describing your use case and goals.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
brkraw_viewer/__init__.py,sha256=0-N1hhUaTQikf7pDU65gb6LyYAd3zqXCzPH4H7Se-uE,84
|
|
2
|
+
brkraw_viewer/plugin.py,sha256=hwmRF9x20j8EbSk6m8f1sB7cXCBKLhV5NWic545tuQc,4004
|
|
3
|
+
brkraw_viewer/registry.py,sha256=fvLve_dbc4pBKr_akZN87oI9OJJP1Zm39KsO1ZHxH0M,8350
|
|
4
|
+
brkraw_viewer/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
brkraw_viewer/apps/config.py,sha256=1y_1j-PePQ38jfM9OcVgeViUz6MtcvvEH5vOzWRDS_0,4412
|
|
6
|
+
brkraw_viewer/apps/convert.py,sha256=Uq0BhTaDTtZN-7XUEbycXnUF7Fi5-EvxxLgqI0Ly59s,73707
|
|
7
|
+
brkraw_viewer/apps/hooks.py,sha256=o12A5exietCdGLss9Ia2I7-83Bs6P3XV3iKaQsgUdEc,872
|
|
8
|
+
brkraw_viewer/apps/viewer.py,sha256=lSHxtW6fy2WHG_jworgQqK9Ta7TVdj7CtIxiwxziab8,219026
|
|
9
|
+
brkraw_viewer/assets/icon.ico,sha256=QUW0dBcnKbNO7MLh58PeLosoveBasMzlHt8UuH6xmKs,372
|
|
10
|
+
brkraw_viewer/assets/icon.png,sha256=Ag-rdTHDnN_LKI6TxRRrP2OWzqoEKFQR0mrxmmcQZkI,769
|
|
11
|
+
brkraw_viewer/frames/__init__.py,sha256=j0U2uMfovQBYuAC3ZzJlN7ilIxcp9J5KQXQm425fNCA,45
|
|
12
|
+
brkraw_viewer/frames/params_panel.py,sha256=AoNskdln3bX_0x00s6f2BuWaDZhZI8_ARnswj_ykQwM,2949
|
|
13
|
+
brkraw_viewer/frames/viewer_canvas.py,sha256=j8RnE-oWqEbsYnQxL1OQP6OAybi4U9s1xFgPuuxEGZ4,13456
|
|
14
|
+
brkraw_viewer/frames/viewer_config.py,sha256=CMQzB_Naexio6WRWLZYTM67rQ-XY51cFOrXuWlddHtQ,3283
|
|
15
|
+
brkraw_viewer/snippets/context_map/basic.yaml,sha256=GoHvSueX7-5ViFS-cwv12rjah9jf_4mHYUh54sRPJtk,65
|
|
16
|
+
brkraw_viewer/snippets/context_map/enum-map.yaml,sha256=LlX-fKt3XOxQmeuY8NQQyjvxCHkHHvqNGhUJuOPMApw,86
|
|
17
|
+
brkraw_viewer/snippets/rule/basic.yaml,sha256=Qy16CDa0Yc0AB2cWzxrnh0dxN-Yc5dDQMhBUUYWOiK4,183
|
|
18
|
+
brkraw_viewer/snippets/rule/when-contains.yaml,sha256=89CQv6AcaAWdHO0xfyB6om0qPXgivLtDqDl6TbmhYSU,213
|
|
19
|
+
brkraw_viewer/snippets/spec/basic.yaml,sha256=1bhSHRcshvDNL8eIoMbGj5KxlbXsVeQbQivExDc9RP8,99
|
|
20
|
+
brkraw_viewer/snippets/spec/list-source.yaml,sha256=toqz2vEjpxV11Q4Sfkn7fPjaxuXYsQqQDSCygQnU680,86
|
|
21
|
+
brkraw_viewer/snippets/spec/with-default.yaml,sha256=Dvx-LdErlwF0dnP_ocFQzR68eQUGoC0zB2s_BWVhNgk,79
|
|
22
|
+
brkraw_viewer/utils/__init__.py,sha256=syfKEnHaaFrSNCiPTAIHbZpVrEEfllA9qV1XrD_GCj8,46
|
|
23
|
+
brkraw_viewer/utils/orientation.py,sha256=GulLdN5HtF1IjQLfFYSAJiGSSCzCK3AnSZ8HM-DRn8g,596
|
|
24
|
+
brkraw_viewer-0.2.5.dist-info/METADATA,sha256=xwH8n4Y_3Gr56sN8jBkCO8ndSAGcrFdqa4DcJlXfSEs,5403
|
|
25
|
+
brkraw_viewer-0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
brkraw_viewer-0.2.5.dist-info/entry_points.txt,sha256=zd9043V-472NISOxavHd2f4Er7SH_CSSRkDl_Bq7jyY,52
|
|
27
|
+
brkraw_viewer-0.2.5.dist-info/top_level.txt,sha256=pyOwjVrot5Cg6GSh3jbhMag2coTkKAlCT3-c20RuC-M,14
|
|
28
|
+
brkraw_viewer-0.2.5.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brkraw_viewer
|