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,288 @@
|
|
|
1
|
+
"""Formatting helpers for loader info output.
|
|
2
|
+
|
|
3
|
+
Last updated: 2025-12-30
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Mapping, Sequence
|
|
9
|
+
import textwrap
|
|
10
|
+
from typing import Any, Optional, List, Dict
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ...core import formatter as formatter_core
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_value(value: Any, *, float_decimals: Optional[int] = None) -> str:
|
|
18
|
+
"""Format values into a human-readable string.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
value: Value to format.
|
|
22
|
+
float_decimals: Decimal precision for floats.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
String representation of the value.
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(value, Mapping):
|
|
28
|
+
return ", ".join(
|
|
29
|
+
f"{k}={_format_value(v, float_decimals=float_decimals)}"
|
|
30
|
+
for k, v in value.items()
|
|
31
|
+
)
|
|
32
|
+
if isinstance(value, np.ndarray):
|
|
33
|
+
if float_decimals is not None and np.issubdtype(value.dtype, np.floating):
|
|
34
|
+
return str(np.round(value, float_decimals).tolist())
|
|
35
|
+
return str(value.tolist())
|
|
36
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
|
37
|
+
return "[" + ", ".join(_format_value(v, float_decimals=float_decimals) for v in value) + "]"
|
|
38
|
+
if float_decimals is not None and isinstance(value, (float, np.floating)):
|
|
39
|
+
return f"{value:.{float_decimals}f}"
|
|
40
|
+
return str(value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _kv_rows(data: Mapping[str, Any], *, float_decimals: Optional[int]) -> List[Dict[str, str]]:
|
|
44
|
+
"""Convert a mapping into table row dicts.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
data: Source mapping.
|
|
48
|
+
float_decimals: Decimal precision for floats.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of row dictionaries with field/value keys.
|
|
52
|
+
"""
|
|
53
|
+
return [
|
|
54
|
+
{"field": str(key), "value": _format_value(val, float_decimals=float_decimals)}
|
|
55
|
+
for key, val in data.items()
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_info_tables(
|
|
60
|
+
info: Mapping[str, Any],
|
|
61
|
+
*,
|
|
62
|
+
width: int = 80,
|
|
63
|
+
indent: int = 0,
|
|
64
|
+
scan_indent: int = 2,
|
|
65
|
+
reco_indent: int = 4,
|
|
66
|
+
scan_transpose: bool = False,
|
|
67
|
+
float_decimals: Optional[int] = None,
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Format study/subject/scan info into nested tables.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
info: Mapping containing Study/Subject/Scan(s) blocks.
|
|
73
|
+
width: Total table width for layout.
|
|
74
|
+
indent: Base indent for the first-level tables.
|
|
75
|
+
scan_indent: Extra indent for scan blocks.
|
|
76
|
+
reco_indent: Extra indent for reco blocks.
|
|
77
|
+
scan_transpose: If True, print scan fields in a transposed layout.
|
|
78
|
+
float_decimals: Decimal precision for float values.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Rendered table string.
|
|
82
|
+
"""
|
|
83
|
+
blocks: List[str] = []
|
|
84
|
+
study = info.get("Study")
|
|
85
|
+
if isinstance(study, Mapping) and study:
|
|
86
|
+
blocks.append(
|
|
87
|
+
formatter_core.format_table(
|
|
88
|
+
"Study",
|
|
89
|
+
("field", "value"),
|
|
90
|
+
_kv_rows(study, float_decimals=float_decimals),
|
|
91
|
+
width=width,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
subject = info.get("Subject")
|
|
95
|
+
if isinstance(subject, Mapping) and subject:
|
|
96
|
+
blocks.append(
|
|
97
|
+
formatter_core.format_table(
|
|
98
|
+
"Subject",
|
|
99
|
+
("field", "value"),
|
|
100
|
+
_kv_rows(subject, float_decimals=float_decimals),
|
|
101
|
+
width=width,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
scans = info.get("Scan(s)", {})
|
|
105
|
+
if isinstance(scans, Mapping):
|
|
106
|
+
scan_blocks: List[str] = []
|
|
107
|
+
if scans:
|
|
108
|
+
scan_blocks.append("[ Scan(s) ]")
|
|
109
|
+
scan_items = list(scans.items())
|
|
110
|
+
for idx, (scan_id, scan_data) in enumerate(scan_items):
|
|
111
|
+
if not isinstance(scan_data, Mapping):
|
|
112
|
+
continue
|
|
113
|
+
scan_fields = {k: v for k, v in scan_data.items() if k != "Reco(s)"}
|
|
114
|
+
if scan_transpose:
|
|
115
|
+
scan_blocks.extend(
|
|
116
|
+
_format_scan_transposed(
|
|
117
|
+
scan_id,
|
|
118
|
+
scan_fields,
|
|
119
|
+
width=width,
|
|
120
|
+
indent=indent + scan_indent,
|
|
121
|
+
float_decimals=float_decimals,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
scan_fields = {"ScanID": scan_id, **scan_fields}
|
|
126
|
+
scan_table = formatter_core.format_table(
|
|
127
|
+
"",
|
|
128
|
+
("field", "value"),
|
|
129
|
+
_kv_rows(scan_fields, float_decimals=float_decimals),
|
|
130
|
+
width=width,
|
|
131
|
+
)
|
|
132
|
+
scan_blocks.append(textwrap.indent(scan_table, " " * (indent + scan_indent)))
|
|
133
|
+
|
|
134
|
+
recos = scan_data.get("Reco(s)", {})
|
|
135
|
+
if isinstance(recos, Mapping) and recos:
|
|
136
|
+
reco_rows = []
|
|
137
|
+
for reco_id, reco_data in recos.items():
|
|
138
|
+
if isinstance(reco_data, Mapping):
|
|
139
|
+
value = _format_value(
|
|
140
|
+
reco_data.get("Type", reco_data),
|
|
141
|
+
float_decimals=float_decimals,
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
value = _format_value(reco_data, float_decimals=float_decimals)
|
|
145
|
+
reco_rows.append(
|
|
146
|
+
{"RecoID": {"value": str(reco_id), "align": "center"}, "value": value}
|
|
147
|
+
)
|
|
148
|
+
reco_table = formatter_core.format_table(
|
|
149
|
+
"Reco(s)",
|
|
150
|
+
("RecoID", "value"),
|
|
151
|
+
reco_rows,
|
|
152
|
+
width=width,
|
|
153
|
+
)
|
|
154
|
+
scan_blocks.append(textwrap.indent(reco_table, " " * (indent + reco_indent)))
|
|
155
|
+
if idx < len(scan_items) - 1:
|
|
156
|
+
scan_blocks.append("")
|
|
157
|
+
if scan_blocks:
|
|
158
|
+
blocks.append("\n".join(scan_blocks))
|
|
159
|
+
return "\n\n".join(blocks)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _format_scan_transposed(
|
|
163
|
+
scan_id: Any,
|
|
164
|
+
scan_fields: Mapping[str, Any],
|
|
165
|
+
*,
|
|
166
|
+
width: int,
|
|
167
|
+
indent: int,
|
|
168
|
+
float_decimals: Optional[int],
|
|
169
|
+
) -> List[str]:
|
|
170
|
+
"""Render scan fields as a transposed table layout.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
scan_id: Scan identifier.
|
|
174
|
+
scan_fields: Mapping of scan field names to values.
|
|
175
|
+
width: Total table width.
|
|
176
|
+
indent: Base indent for the table.
|
|
177
|
+
float_decimals: Decimal precision for floats.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of formatted table blocks.
|
|
181
|
+
"""
|
|
182
|
+
columns = [str(key) for key in scan_fields.keys()]
|
|
183
|
+
values = {
|
|
184
|
+
str(key): _format_value(val, float_decimals=float_decimals)
|
|
185
|
+
for key, val in scan_fields.items()
|
|
186
|
+
}
|
|
187
|
+
grouped = _chunk_columns(columns, values, width=width, gap=2, base_cols=["ScanID"])
|
|
188
|
+
blocks: List[str] = []
|
|
189
|
+
scan_id_width = max(len("ScanID"), len(str(scan_id)))
|
|
190
|
+
for idx, group in enumerate(grouped):
|
|
191
|
+
if idx == 0:
|
|
192
|
+
title = ""
|
|
193
|
+
cols = ["ScanID"] + group
|
|
194
|
+
row: Dict[str, Any] = {"ScanID": {"value": str(scan_id), "align": "center"}}
|
|
195
|
+
else:
|
|
196
|
+
title = ""
|
|
197
|
+
cols = group
|
|
198
|
+
row = {}
|
|
199
|
+
row.update({name: values.get(name, "") for name in group})
|
|
200
|
+
table = formatter_core.format_table(
|
|
201
|
+
title,
|
|
202
|
+
cols,
|
|
203
|
+
[row],
|
|
204
|
+
width=width,
|
|
205
|
+
wrap_last=False,
|
|
206
|
+
)
|
|
207
|
+
lines = table.splitlines()
|
|
208
|
+
if idx != 0:
|
|
209
|
+
if lines and lines[0].startswith("["):
|
|
210
|
+
lines = lines[1:]
|
|
211
|
+
table = "\n".join(lines)
|
|
212
|
+
extra_indent = scan_id_width + 2
|
|
213
|
+
blocks.append(textwrap.indent(table, " " * (indent + extra_indent)))
|
|
214
|
+
else:
|
|
215
|
+
blocks.append(textwrap.indent(table, " " * indent))
|
|
216
|
+
return blocks
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _chunk_columns(
|
|
220
|
+
columns: List[str],
|
|
221
|
+
values: Mapping[str, str],
|
|
222
|
+
*,
|
|
223
|
+
width: int,
|
|
224
|
+
gap: int,
|
|
225
|
+
base_cols: List[str],
|
|
226
|
+
) -> List[List[str]]:
|
|
227
|
+
"""Group columns so each table fits within the width.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
columns: Column names to group.
|
|
231
|
+
values: Mapping of column values used for width estimation.
|
|
232
|
+
width: Maximum table width.
|
|
233
|
+
gap: Spacing between columns.
|
|
234
|
+
base_cols: Columns always included in width calculations.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
List of grouped column name lists.
|
|
238
|
+
"""
|
|
239
|
+
groups: List[List[str]] = []
|
|
240
|
+
current: List[str] = []
|
|
241
|
+
for col in columns:
|
|
242
|
+
candidate = current + [col]
|
|
243
|
+
if _fits_width(candidate, values, width=width, gap=gap, base_cols=base_cols):
|
|
244
|
+
current = candidate
|
|
245
|
+
else:
|
|
246
|
+
if current:
|
|
247
|
+
groups.append(current)
|
|
248
|
+
current = [col]
|
|
249
|
+
if current:
|
|
250
|
+
groups.append(current)
|
|
251
|
+
return groups
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _fits_width(
|
|
255
|
+
columns: List[str],
|
|
256
|
+
values: Mapping[str, str],
|
|
257
|
+
*,
|
|
258
|
+
width: int,
|
|
259
|
+
gap: int,
|
|
260
|
+
base_cols: List[str],
|
|
261
|
+
) -> bool:
|
|
262
|
+
"""Check if columns fit within a target width.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
columns: Column names to measure.
|
|
266
|
+
values: Mapping of column values used for width estimation.
|
|
267
|
+
width: Maximum table width.
|
|
268
|
+
gap: Spacing between columns.
|
|
269
|
+
base_cols: Columns always included in width calculations.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
True if the columns fit within the width.
|
|
273
|
+
"""
|
|
274
|
+
cols = base_cols + columns
|
|
275
|
+
total = 0
|
|
276
|
+
for idx, col in enumerate(cols):
|
|
277
|
+
value_len = len(values.get(col, "")) if col in values else 0
|
|
278
|
+
col_width = max(len(col), value_len)
|
|
279
|
+
total += col_width
|
|
280
|
+
if idx < len(cols) - 1:
|
|
281
|
+
total += gap
|
|
282
|
+
return total <= width
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
__all__ = ["format_info_tables"]
|
|
286
|
+
|
|
287
|
+
def __dir__() -> List[str]:
|
|
288
|
+
return sorted(__all__)
|