brainfc 0.3.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.
- brainfc/__init__.py +20 -0
- brainfc/__main__.py +3 -0
- brainfc/_version.py +3 -0
- brainfc/atlases.py +89 -0
- brainfc/cli.py +184 -0
- brainfc/demo.py +77 -0
- brainfc/export.py +109 -0
- brainfc/imaging.py +376 -0
- brainfc/io.py +241 -0
- brainfc/models.py +335 -0
- brainfc/pipeline.py +383 -0
- brainfc/plotting.py +173 -0
- brainfc/preprocessing.py +179 -0
- brainfc/presets.py +192 -0
- brainfc/web/__init__.py +1 -0
- brainfc/web/app.py +586 -0
- brainfc/web/schemas.py +251 -0
- brainfc/web/static/THIRD_PARTY_NOTICES.txt +97 -0
- brainfc/web/static/app.css +1 -0
- brainfc/web/static/app.js +4287 -0
- brainfc/web/static/index.html +1 -0
- brainfc/web/static/reference/api-inventory.json +434 -0
- brainfc/web/static/reference/api-reference.html +980 -0
- brainfc/web/static/reference/cli-reference.html +132 -0
- brainfc/web/static/reference/datasets.html +55 -0
- brainfc/web/static/reference/eight-views-v02.png +0 -0
- brainfc/web/static/reference/formats.html +84 -0
- brainfc/web/static/reference/http-api.html +210 -0
- brainfc/web/static/reference/http-reference.html +2147 -0
- brainfc/web/static/reference/index.html +60 -0
- brainfc/web/static/reference/interface-preview.png +0 -0
- brainfc/web/static/reference/openapi.json +2185 -0
- brainfc/web/static/reference/outputs.html +219 -0
- brainfc/web/static/reference/presets-and-workflow.html +133 -0
- brainfc/web/static/reference/processing.html +89 -0
- brainfc/web/static/reference/python-api.html +276 -0
- brainfc/web/static/reference/quickstart.html +85 -0
- brainfc/web/static/reference/release.html +84 -0
- brainfc/web/static/reference/research.html +57 -0
- brainfc/web/static/reference/validation-v0.2.0.html +33 -0
- brainfc/web/static/reference/validation-v0.2.1.html +16 -0
- brainfc/web/static/reference/validation-v0.3.0.html +18 -0
- brainfc/web/static/reference/validation.html +42 -0
- brainfc/workflow.py +240 -0
- brainfc-0.3.0.dist-info/METADATA +97 -0
- brainfc-0.3.0.dist-info/RECORD +51 -0
- brainfc-0.3.0.dist-info/WHEEL +4 -0
- brainfc-0.3.0.dist-info/entry_points.txt +2 -0
- brainfc-0.3.0.dist-info/licenses/LICENSE +201 -0
- brainfc-0.3.0.dist-info/licenses/NOTICE +19 -0
- brainfc-0.3.0.dist-info/licenses/src/brainfc/web/static/THIRD_PARTY_NOTICES.txt +97 -0
brainfc/models.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import math
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InputError(ValueError):
|
|
10
|
+
"""Input data or declared spatial/temporal metadata are not compatible."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Config:
|
|
15
|
+
"""Single-run extraction settings, shared by Python, CLI and HTTP.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
t_r : float or None, default None
|
|
20
|
+
Repetition time in seconds. Infer from sidecar/recognized header when None;
|
|
21
|
+
conflicting explicit or inferred values raise InputError (rtol=atol=1e-4).
|
|
22
|
+
high_pass, low_pass : float or None, default None
|
|
23
|
+
Cutoffs in Hz. None disables that cutoff. Positive, high_pass < low_pass,
|
|
24
|
+
and each enabled cutoff must be below 1/(2*t_r).
|
|
25
|
+
detrend : bool, default True
|
|
26
|
+
Remove linear trends through Nilearn signal.clean.
|
|
27
|
+
standardize : bool, default True
|
|
28
|
+
Apply sample z-score (ddof=1) to cleaned ROI time series.
|
|
29
|
+
discard : int, default 0
|
|
30
|
+
Remove this many leading original frames before temporal cleaning.
|
|
31
|
+
fd_threshold : float or None, default None
|
|
32
|
+
Censor frames with framewise_displacement > this threshold in mm.
|
|
33
|
+
A missing first FD is also censored when enabled. Requires confounds.
|
|
34
|
+
min_samples : int, default 20
|
|
35
|
+
Minimum retained frames; must be >=3. Not a statistical power criterion.
|
|
36
|
+
method : {'pearson', 'spearman', 'partial'}, default 'pearson'
|
|
37
|
+
Signed Pearson, tied-average-rank Spearman, or Ledoit-Wolf partial correlation.
|
|
38
|
+
data_space, atlas_space : str or None, default None
|
|
39
|
+
Exact named spaces. Images require a declared/inferred data space; when
|
|
40
|
+
an atlas is supplied the names must match. No registration is estimated.
|
|
41
|
+
preprocessed : bool, default False
|
|
42
|
+
Required caller declaration for every image, including CIFTI/GIFTI.
|
|
43
|
+
Does not perform or automatically verify motion correction/registration.
|
|
44
|
+
confound_columns : tuple of str or None, default None
|
|
45
|
+
Explicit nonempty columns to regress. None selects motion6 plus available
|
|
46
|
+
white_matter/csf. Nonsteady-state/FD censoring is considered independently.
|
|
47
|
+
table_header : bool, default True
|
|
48
|
+
Read first non-comment text row as ROI names. Ignored for NPY/NPZ/MAT.
|
|
49
|
+
transpose : bool, default False
|
|
50
|
+
Transpose table input to time x ROI; discards text column names.
|
|
51
|
+
variable : str or None, default None
|
|
52
|
+
NPZ/MAT numeric 2D variable; None requires exactly one eligible variable.
|
|
53
|
+
|
|
54
|
+
Notes
|
|
55
|
+
-----
|
|
56
|
+
Frozen dataclass: attributes cannot be reassigned. This is not raw-image
|
|
57
|
+
preprocessing. Filtering and censoring require further input-dependent checks
|
|
58
|
+
inside extract_connectome; construction alone does not validate a dataset."""
|
|
59
|
+
|
|
60
|
+
t_r: float | None = None
|
|
61
|
+
high_pass: float | None = None
|
|
62
|
+
low_pass: float | None = None
|
|
63
|
+
detrend: bool = True
|
|
64
|
+
standardize: bool = True
|
|
65
|
+
discard: int = 0
|
|
66
|
+
fd_threshold: float | None = None
|
|
67
|
+
min_samples: int = 20
|
|
68
|
+
method: str = "pearson"
|
|
69
|
+
data_space: str | None = None
|
|
70
|
+
atlas_space: str | None = None
|
|
71
|
+
preprocessed: bool = False
|
|
72
|
+
confound_columns: tuple[str, ...] | None = None
|
|
73
|
+
table_header: bool = True
|
|
74
|
+
transpose: bool = False
|
|
75
|
+
variable: str | None = None
|
|
76
|
+
|
|
77
|
+
def __post_init__(self):
|
|
78
|
+
for key in ("t_r", "high_pass", "low_pass", "fd_threshold"):
|
|
79
|
+
value = getattr(self, key)
|
|
80
|
+
if value is not None and (isinstance(value, bool) or not math.isfinite(value) or value <= 0):
|
|
81
|
+
raise InputError(f"{key} must be a positive finite number.")
|
|
82
|
+
if self.high_pass and self.low_pass and self.high_pass >= self.low_pass:
|
|
83
|
+
raise InputError("high_pass must be smaller than low_pass.")
|
|
84
|
+
for key, minimum in (("discard", 0), ("min_samples", 3)):
|
|
85
|
+
value = getattr(self, key)
|
|
86
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
|
|
87
|
+
raise InputError(f"{key} must be an integer >= {minimum}.")
|
|
88
|
+
if self.method not in {"pearson", "spearman", "partial"}:
|
|
89
|
+
raise InputError("method must be pearson, spearman or partial (Ledoit-Wolf).")
|
|
90
|
+
for key in ("preprocessed", "detrend", "standardize", "table_header", "transpose"):
|
|
91
|
+
if not isinstance(getattr(self, key), bool):
|
|
92
|
+
raise InputError(f"{key} must be boolean.")
|
|
93
|
+
if self.confound_columns is not None:
|
|
94
|
+
if not self.confound_columns or any(
|
|
95
|
+
not isinstance(c, str) or not c for c in self.confound_columns
|
|
96
|
+
):
|
|
97
|
+
raise InputError("confound_columns must be a nonempty sequence of column names.")
|
|
98
|
+
|
|
99
|
+
def to_dict(self):
|
|
100
|
+
"""Return a new dataclass field dictionary.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
dict
|
|
105
|
+
All settings including None values. A tuple of confound columns remains
|
|
106
|
+
a tuple in Python; json.dumps serializes it as an array."""
|
|
107
|
+
return asdict(self)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class Connectome:
|
|
112
|
+
"""Result of one run, normally constructed by extract_connectome.
|
|
113
|
+
|
|
114
|
+
Attributes
|
|
115
|
+
----------
|
|
116
|
+
timeseries : numpy.ndarray, shape (T_retained, R)
|
|
117
|
+
Cleaned ROI signals in the same column order as rois.
|
|
118
|
+
connectivity : numpy.ndarray, shape (R, R)
|
|
119
|
+
Symmetric signed coefficients, diagonal 1. No display threshold applied.
|
|
120
|
+
fisher_z : numpy.ndarray, shape (R, R)
|
|
121
|
+
atanh(clip(connectivity, -1+1e-7, 1-1e-7)); diagonal 0.
|
|
122
|
+
rois : list of dict
|
|
123
|
+
ROI ID/name/label/hemisphere/network/abbreviation and coordinates or None.
|
|
124
|
+
sample_indices : numpy.ndarray, shape (T_retained,)
|
|
125
|
+
Zero-based frame positions in the original source, in increasing order.
|
|
126
|
+
qc, provenance : dict
|
|
127
|
+
Quality counts/warnings and resolved configuration, hashes and versions.
|
|
128
|
+
geometry : dict or None
|
|
129
|
+
Display-only mesh and spatial metadata; None without complete coordinates.
|
|
130
|
+
|
|
131
|
+
Notes
|
|
132
|
+
-----
|
|
133
|
+
Arrays/dictionaries are mutable. Direct construction does not validate their
|
|
134
|
+
shape or consistency; prefer extract_connectome for a validated result."""
|
|
135
|
+
|
|
136
|
+
timeseries: np.ndarray
|
|
137
|
+
connectivity: np.ndarray
|
|
138
|
+
fisher_z: np.ndarray
|
|
139
|
+
rois: list[dict]
|
|
140
|
+
sample_indices: np.ndarray
|
|
141
|
+
qc: dict
|
|
142
|
+
provenance: dict
|
|
143
|
+
geometry: dict | None = field(default=None, repr=False)
|
|
144
|
+
|
|
145
|
+
def to_dict(self, *, include_timeseries=False):
|
|
146
|
+
"""Return schema-version-1 report data with arrays converted to lists.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
include_timeseries : bool, default False
|
|
151
|
+
Include cleaned time series, which may make the payload large.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
dict
|
|
156
|
+
ROI metadata, matrices, frame indices, QC, provenance and geometry.
|
|
157
|
+
Nested metadata dictionaries are shared, not deep-copied."""
|
|
158
|
+
result = {
|
|
159
|
+
"schema_version": 1,
|
|
160
|
+
"rois": self.rois,
|
|
161
|
+
"connectivity": self.connectivity.tolist(),
|
|
162
|
+
"fisher_z": self.fisher_z.tolist(),
|
|
163
|
+
"sample_indices": self.sample_indices.tolist(),
|
|
164
|
+
"qc": self.qc,
|
|
165
|
+
"provenance": self.provenance,
|
|
166
|
+
"geometry": self.geometry,
|
|
167
|
+
}
|
|
168
|
+
if include_timeseries:
|
|
169
|
+
result["timeseries"] = self.timeseries.tolist()
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def save(self, directory: str | Path, *, figures=True, report=True):
|
|
173
|
+
"""Export one complete result to a new directory.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
directory : str or pathlib.Path
|
|
178
|
+
New destination; parents are created. Existing destinations are refused.
|
|
179
|
+
figures : bool, default True
|
|
180
|
+
Save matrix PNG/SVG/PDF and eight views when all ROIs have coordinates.
|
|
181
|
+
report : bool, default True
|
|
182
|
+
Save bundled, self-contained interactive report.html.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
pathlib.Path
|
|
187
|
+
Absolute output directory, promoted from a temporary sibling on success.
|
|
188
|
+
|
|
189
|
+
Raises
|
|
190
|
+
------
|
|
191
|
+
FileExistsError
|
|
192
|
+
Destination already exists. Temporary exports are removed on failure.
|
|
193
|
+
|
|
194
|
+
Notes
|
|
195
|
+
-----
|
|
196
|
+
Always writes numeric arrays/tables, ROI/frame tables, QC/provenance/result JSON
|
|
197
|
+
and a SHA-256 manifest. Static views use threshold=0.3 and max_edges=200.
|
|
198
|
+
ZIP creation is performed by the web service, not this method."""
|
|
199
|
+
from .export import save_result
|
|
200
|
+
|
|
201
|
+
return save_result(self, directory, figures=figures, report=report)
|
|
202
|
+
|
|
203
|
+
def plot_matrix(self, path=None):
|
|
204
|
+
"""Plot the complete signed connectivity matrix.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
path : str, pathlib.Path or None, default None
|
|
209
|
+
Optional image filename; suffix determines format. Parent must exist.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
matplotlib.figure.Figure
|
|
214
|
+
Figure, closed after saving when path is supplied. With None the caller
|
|
215
|
+
owns the figure and should close it after use.
|
|
216
|
+
|
|
217
|
+
Notes
|
|
218
|
+
-----
|
|
219
|
+
Uses Agg and a fixed [-1, 1] color range. Saves at 300 dpi. Unlike save/view,
|
|
220
|
+
plotting to an existing filename overwrites that image."""
|
|
221
|
+
from .plotting import plot_matrix
|
|
222
|
+
|
|
223
|
+
return plot_matrix(self, path)
|
|
224
|
+
|
|
225
|
+
def plot_views(
|
|
226
|
+
self, path=None, *, threshold=0.3, max_edges=200, selection=None, opacity=0.28, theme="paper"
|
|
227
|
+
):
|
|
228
|
+
"""Plot eight fixed anatomical projections using one filtered edge set.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
path : str, pathlib.Path or None, default None
|
|
233
|
+
Image filename (may overwrite) or return an unsaved figure.
|
|
234
|
+
threshold : float, default 0.3
|
|
235
|
+
Inclusive absolute coefficient cutoff in [0,1]; exact zero edges excluded.
|
|
236
|
+
max_edges : int, default 200
|
|
237
|
+
Nonnegative cap after filtering; 0 shows nodes without connections.
|
|
238
|
+
selection : dict or None, default None
|
|
239
|
+
{'kind':'node','id':roi_id} selects incident edges. Edge selection uses
|
|
240
|
+
{'kind':'edge','id':'i:j'} with zero-based row indices i < j. IDs are strings.
|
|
241
|
+
opacity : float, default 0.28
|
|
242
|
+
Display control in [0,1]; Matplotlib shell alpha is opacity*0.15.
|
|
243
|
+
theme : {'paper', 'midnight'}, default 'paper'
|
|
244
|
+
Light or dark background.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
matplotlib.figure.Figure
|
|
249
|
+
2x4 orthographic views, closed if saved. Shell is rasterized even in SVG/PDF.
|
|
250
|
+
|
|
251
|
+
Raises
|
|
252
|
+
------
|
|
253
|
+
InputError
|
|
254
|
+
Missing ROI coordinates, invalid selection or invalid display settings.
|
|
255
|
+
|
|
256
|
+
Notes
|
|
257
|
+
-----
|
|
258
|
+
Filter by threshold and selection, then stable-sort by descending absolute
|
|
259
|
+
strength and cap. Ties retain upper-triangle row-major order. Original matrices
|
|
260
|
+
are unchanged. Static materials differ from the GUI's WebGL renderer."""
|
|
261
|
+
from .plotting import plot_views
|
|
262
|
+
|
|
263
|
+
return plot_views(
|
|
264
|
+
self,
|
|
265
|
+
path,
|
|
266
|
+
threshold=threshold,
|
|
267
|
+
max_edges=max_edges,
|
|
268
|
+
selection=selection,
|
|
269
|
+
opacity=opacity,
|
|
270
|
+
theme=theme,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def view(self, path="connectome.html", *, open_browser=False):
|
|
274
|
+
"""Write a self-contained interactive HTML report.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
path : str or pathlib.Path, default 'connectome.html'
|
|
279
|
+
New HTML file. Parent directories are created; overwrite is refused.
|
|
280
|
+
open_browser : bool, default False
|
|
281
|
+
Ask the operating system browser to open the new local file.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
pathlib.Path
|
|
286
|
+
Absolute HTML path. No running web server is needed for the export.
|
|
287
|
+
|
|
288
|
+
Raises
|
|
289
|
+
------
|
|
290
|
+
FileExistsError
|
|
291
|
+
Output already exists.
|
|
292
|
+
InputError
|
|
293
|
+
Bundled frontend assets are missing.
|
|
294
|
+
|
|
295
|
+
Notes
|
|
296
|
+
-----
|
|
297
|
+
Embeds matrices and provenance including local input paths. Without coordinates
|
|
298
|
+
the report still shows matrix/QC. Requires browser WebGL for the 3D view."""
|
|
299
|
+
from .export import write_report
|
|
300
|
+
|
|
301
|
+
target = write_report(self, path)
|
|
302
|
+
if open_browser:
|
|
303
|
+
import webbrowser
|
|
304
|
+
|
|
305
|
+
webbrowser.open(target.as_uri())
|
|
306
|
+
return target
|
|
307
|
+
|
|
308
|
+
def to_hicbrain(self):
|
|
309
|
+
"""Create a connectivity BrainDataset in separately installed Hyper-Brain.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
hicbrain.BrainDataset
|
|
314
|
+
A copy of the signed matrix with ROI IDs, names, optional coordinates and
|
|
315
|
+
brainfc provenance in metadata. No hyperedges are inferred.
|
|
316
|
+
|
|
317
|
+
Raises
|
|
318
|
+
------
|
|
319
|
+
ImportError
|
|
320
|
+
The independent hicbrain import is unavailable. Hyper-Brain is optional."""
|
|
321
|
+
try:
|
|
322
|
+
from hicbrain import BrainDataset
|
|
323
|
+
except ImportError as exc:
|
|
324
|
+
raise ImportError("Install Hyper-Brain separately to use to_hicbrain().") from exc
|
|
325
|
+
coordinates = (
|
|
326
|
+
[r["coordinates"] for r in self.rois] if all(r.get("coordinates") for r in self.rois) else None
|
|
327
|
+
)
|
|
328
|
+
return BrainDataset(
|
|
329
|
+
self.connectivity.copy(),
|
|
330
|
+
"connectivity",
|
|
331
|
+
roi_ids=[r["roi_id"] for r in self.rois],
|
|
332
|
+
labels=[r["name"] for r in self.rois],
|
|
333
|
+
coordinates=coordinates,
|
|
334
|
+
metadata={"brainfc": self.provenance},
|
|
335
|
+
)
|