dfc-kit 1.0.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.
- dfc_kit-1.0.0.dist-info/METADATA +184 -0
- dfc_kit-1.0.0.dist-info/RECORD +71 -0
- dfc_kit-1.0.0.dist-info/WHEEL +5 -0
- dfc_kit-1.0.0.dist-info/entry_points.txt +2 -0
- dfc_kit-1.0.0.dist-info/licenses/LICENSE +29 -0
- dfc_kit-1.0.0.dist-info/top_level.txt +1 -0
- dfckit/__init__.py +11 -0
- dfckit/_arrays.py +13 -0
- dfckit/_preprocessing.py +46 -0
- dfckit/_validation.py +87 -0
- dfckit/artifacts/__init__.py +36 -0
- dfckit/artifacts/_fields.py +67 -0
- dfckit/artifacts/_json.py +92 -0
- dfckit/artifacts/_numpy.py +55 -0
- dfckit/artifacts/models.py +624 -0
- dfckit/artifacts/state_alignment.py +106 -0
- dfckit/artifacts/state_results.py +353 -0
- dfckit/artifacts/state_scoring.py +509 -0
- dfckit/artifacts/state_stability.py +207 -0
- dfckit/cli.py +105 -0
- dfckit/commands/__init__.py +1 -0
- dfckit/commands/parser.py +354 -0
- dfckit/commands/reporting.py +146 -0
- dfckit/commands/source.py +245 -0
- dfckit/commands/stability.py +288 -0
- dfckit/commands/states.py +266 -0
- dfckit/connectivity/__init__.py +68 -0
- dfckit/connectivity/_edge_products.py +130 -0
- dfckit/connectivity/correlation.py +61 -0
- dfckit/connectivity/instantaneous.py +253 -0
- dfckit/connectivity/leida.py +204 -0
- dfckit/connectivity/lowrank.py +384 -0
- dfckit/connectivity/partition.py +291 -0
- dfckit/connectivity/windows.py +83 -0
- dfckit/data.py +229 -0
- dfckit/inference/__init__.py +55 -0
- dfckit/inference/endpoints.py +194 -0
- dfckit/inference/hc3.py +207 -0
- dfckit/inference/matching.py +300 -0
- dfckit/inference/multiple_testing.py +52 -0
- dfckit/inference/nbs.py +586 -0
- dfckit/inference/paired.py +157 -0
- dfckit/inference/state_metrics.py +277 -0
- dfckit/information/__init__.py +45 -0
- dfckit/information/_artifact.py +440 -0
- dfckit/information/estimators.py +536 -0
- dfckit/information/fixed.py +660 -0
- dfckit/information/summary.py +89 -0
- dfckit/io/__init__.py +23 -0
- dfckit/io/xcpd.py +470 -0
- dfckit/reference.py +350 -0
- dfckit/segments.py +44 -0
- dfckit/states/__init__.py +97 -0
- dfckit/states/alignment.py +364 -0
- dfckit/states/cap.py +62 -0
- dfckit/states/cross_validation.py +114 -0
- dfckit/states/data.py +387 -0
- dfckit/states/hmm.py +387 -0
- dfckit/states/interpretation.py +273 -0
- dfckit/states/kmeans.py +340 -0
- dfckit/states/metrics.py +94 -0
- dfckit/states/scoring.py +114 -0
- dfckit/states/selection.py +516 -0
- dfckit/states/stability.py +183 -0
- dfckit/states/streaming.py +859 -0
- dfckit/states/streaming_hmm.py +436 -0
- dfckit/storage/__init__.py +29 -0
- dfckit/storage/_statistics.py +80 -0
- dfckit/storage/builders.py +330 -0
- dfckit/storage/store.py +672 -0
- dfckit/storage/summary.py +140 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Machine-readable persistence for aligned repeated-fit state stability."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.typing import NDArray
|
|
10
|
+
|
|
11
|
+
from .._validation import validated_nonnegative_integer as _seed
|
|
12
|
+
from ..states.alignment import StateAlignment
|
|
13
|
+
from ..states.stability import RunStateStability
|
|
14
|
+
from ._json import write_json_atomic
|
|
15
|
+
|
|
16
|
+
FORMAT_NAME = "dfckit-state-stability"
|
|
17
|
+
FORMAT_VERSION = 3
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _optional_float(value: float | None) -> float | None:
|
|
21
|
+
if value is None:
|
|
22
|
+
return None
|
|
23
|
+
output = float(value)
|
|
24
|
+
if not np.isfinite(output) or output <= 0.0:
|
|
25
|
+
raise ValueError("sample_interval_seconds must be finite and positive")
|
|
26
|
+
return output
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _json_values(values: NDArray[np.float64]) -> object:
|
|
30
|
+
array = np.asarray(values, dtype=float)
|
|
31
|
+
if np.isinf(array).any():
|
|
32
|
+
raise ValueError("state stability metrics cannot contain infinite values")
|
|
33
|
+
if array.ndim == 0:
|
|
34
|
+
return None if np.isnan(array) else float(array)
|
|
35
|
+
return [_json_values(item) for item in array]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _aggregate(values: NDArray[np.float64]) -> dict[str, object]:
|
|
39
|
+
array = np.asarray(values, dtype=float)
|
|
40
|
+
if array.ndim < 1 or array.shape[0] < 2:
|
|
41
|
+
raise ValueError("state stability aggregation requires at least two fitted values")
|
|
42
|
+
if np.isinf(array).any():
|
|
43
|
+
raise ValueError("state stability metrics cannot contain infinite values")
|
|
44
|
+
finite = np.isfinite(array)
|
|
45
|
+
counts = finite.sum(axis=0)
|
|
46
|
+
total = np.where(finite, array, 0.0).sum(axis=0)
|
|
47
|
+
mean = np.divide(
|
|
48
|
+
total,
|
|
49
|
+
counts,
|
|
50
|
+
out=np.full(counts.shape, np.nan, dtype=float),
|
|
51
|
+
where=counts > 0,
|
|
52
|
+
)
|
|
53
|
+
centered = np.where(finite, array - mean, 0.0)
|
|
54
|
+
variance = np.divide(
|
|
55
|
+
np.square(centered).sum(axis=0),
|
|
56
|
+
counts,
|
|
57
|
+
out=np.full(counts.shape, np.nan, dtype=float),
|
|
58
|
+
where=counts > 1,
|
|
59
|
+
)
|
|
60
|
+
standard_deviation = np.sqrt(variance)
|
|
61
|
+
return {
|
|
62
|
+
"by_fit": _json_values(array),
|
|
63
|
+
"mean": _json_values(mean),
|
|
64
|
+
"standard_deviation": _json_values(standard_deviation),
|
|
65
|
+
"valid_fit_count": np.asarray(counts, dtype=int).tolist(),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _fit_records(
|
|
70
|
+
*,
|
|
71
|
+
n_states: int,
|
|
72
|
+
reference_seed: int,
|
|
73
|
+
candidate_seeds: Sequence[int],
|
|
74
|
+
alignments: Sequence[StateAlignment],
|
|
75
|
+
) -> list[dict[str, object]]:
|
|
76
|
+
seeds = (
|
|
77
|
+
_seed(reference_seed, "reference_seed"),
|
|
78
|
+
*(_seed(value, "candidate_seed") for value in candidate_seeds),
|
|
79
|
+
)
|
|
80
|
+
alignments = tuple(alignments)
|
|
81
|
+
if len(seeds) < 2:
|
|
82
|
+
raise ValueError("state stability requires at least two fits")
|
|
83
|
+
if len(alignments) != len(seeds) - 1:
|
|
84
|
+
raise ValueError("state stability alignment count does not match candidate fits")
|
|
85
|
+
records = [
|
|
86
|
+
{
|
|
87
|
+
"fit_index": 0,
|
|
88
|
+
"role": "reference",
|
|
89
|
+
"model_seed": seeds[0],
|
|
90
|
+
"candidate_to_reference": list(range(n_states)),
|
|
91
|
+
"matched_costs": [0.0] * n_states,
|
|
92
|
+
"cost_matrix": None,
|
|
93
|
+
"alignment_metric": "identity",
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
for index, (seed, alignment) in enumerate(
|
|
97
|
+
zip(seeds[1:], alignments, strict=True),
|
|
98
|
+
start=1,
|
|
99
|
+
):
|
|
100
|
+
if len(alignment.candidate_to_reference) != n_states:
|
|
101
|
+
raise ValueError("state stability alignment does not match n_states")
|
|
102
|
+
if alignment.reference_seed != seeds[0] or alignment.candidate_seed != seed:
|
|
103
|
+
raise ValueError("state stability alignment seed identity is inconsistent")
|
|
104
|
+
records.append(
|
|
105
|
+
{
|
|
106
|
+
"fit_index": index,
|
|
107
|
+
"role": "candidate",
|
|
108
|
+
"model_seed": seed,
|
|
109
|
+
"candidate_to_reference": alignment.candidate_to_reference.tolist(),
|
|
110
|
+
"matched_costs": alignment.matched_costs.tolist(),
|
|
111
|
+
"cost_matrix": alignment.cost_matrix.tolist(),
|
|
112
|
+
"alignment_metric": alignment.metric,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
return records
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _state_stability_payload(
|
|
119
|
+
runs: Sequence[RunStateStability],
|
|
120
|
+
*,
|
|
121
|
+
model_kind: str,
|
|
122
|
+
reference_seed: int,
|
|
123
|
+
candidate_seeds: Sequence[int],
|
|
124
|
+
alignments: Sequence[StateAlignment],
|
|
125
|
+
source_contract: str,
|
|
126
|
+
sample_interval_seconds: float | None,
|
|
127
|
+
allow_fit_subjects: bool,
|
|
128
|
+
) -> dict[str, object]:
|
|
129
|
+
"""Build a JSON-safe report with reference-numbered metrics for every fit."""
|
|
130
|
+
runs = tuple(runs)
|
|
131
|
+
if not runs:
|
|
132
|
+
raise ValueError("state stability report requires at least one run")
|
|
133
|
+
if model_kind not in {"kmeans-state", "gaussian-hmm-state"}:
|
|
134
|
+
raise ValueError("state stability model_kind is invalid")
|
|
135
|
+
if not str(source_contract).strip():
|
|
136
|
+
raise ValueError("state stability source_contract must be non-empty")
|
|
137
|
+
if not isinstance(allow_fit_subjects, (bool, np.bool_)):
|
|
138
|
+
raise TypeError("allow_fit_subjects must be boolean")
|
|
139
|
+
interval = _optional_float(sample_interval_seconds)
|
|
140
|
+
n_fits = runs[0].n_fits
|
|
141
|
+
n_states = runs[0].n_states
|
|
142
|
+
if any(run.n_fits != n_fits or run.n_states != n_states for run in runs):
|
|
143
|
+
raise ValueError("state stability runs must have identical fit and state counts")
|
|
144
|
+
fit_records = _fit_records(
|
|
145
|
+
n_states=n_states,
|
|
146
|
+
reference_seed=reference_seed,
|
|
147
|
+
candidate_seeds=candidate_seeds,
|
|
148
|
+
alignments=alignments,
|
|
149
|
+
)
|
|
150
|
+
if len(fit_records) != n_fits:
|
|
151
|
+
raise ValueError("state stability fit identities do not match metric fit count")
|
|
152
|
+
run_records = []
|
|
153
|
+
for run in runs:
|
|
154
|
+
run_records.append(
|
|
155
|
+
{
|
|
156
|
+
"subject": run.subject,
|
|
157
|
+
"session": run.session,
|
|
158
|
+
"acquisition_id": run.acquisition_id,
|
|
159
|
+
"n_samples": run.n_samples,
|
|
160
|
+
"n_sequences": run.n_sequences,
|
|
161
|
+
"occupancy": _aggregate(run.occupancy_by_fit),
|
|
162
|
+
"mean_dwell_samples": _aggregate(run.mean_dwell_samples_by_fit),
|
|
163
|
+
"mean_dwell_seconds": _aggregate(run.mean_dwell_seconds_by_fit),
|
|
164
|
+
"switch_rate": _aggregate(run.switch_rate_by_fit),
|
|
165
|
+
"transition_probabilities": _aggregate(
|
|
166
|
+
run.transition_probabilities_by_fit
|
|
167
|
+
),
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
subjects = list(dict.fromkeys(run.subject for run in runs))
|
|
171
|
+
return {
|
|
172
|
+
"format": FORMAT_NAME,
|
|
173
|
+
"format_version": FORMAT_VERSION,
|
|
174
|
+
"model_kind": model_kind,
|
|
175
|
+
"reference_seed": fit_records[0]["model_seed"],
|
|
176
|
+
"state_numbering": "reference-model state indices",
|
|
177
|
+
"dispersion_standard_deviation": (
|
|
178
|
+
"population standard deviation across fits (ddof=0); null when fewer "
|
|
179
|
+
"than two finite values are available"
|
|
180
|
+
),
|
|
181
|
+
"n_fits": n_fits,
|
|
182
|
+
"n_states": n_states,
|
|
183
|
+
"n_runs": len(runs),
|
|
184
|
+
"n_sequences": sum(run.n_sequences for run in runs),
|
|
185
|
+
"n_samples": sum(run.n_samples for run in runs),
|
|
186
|
+
"subjects": subjects,
|
|
187
|
+
"source_contract": str(source_contract),
|
|
188
|
+
"sample_interval_seconds": interval,
|
|
189
|
+
"allow_fit_subjects": bool(allow_fit_subjects),
|
|
190
|
+
"fits": fit_records,
|
|
191
|
+
"runs": run_records,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def write_state_stability(
|
|
196
|
+
runs: Sequence[RunStateStability],
|
|
197
|
+
path: str | Path,
|
|
198
|
+
**metadata: object,
|
|
199
|
+
) -> Path:
|
|
200
|
+
"""Atomically write an aligned repeated-fit stability report as strict JSON."""
|
|
201
|
+
payload = _state_stability_payload(runs, **metadata)
|
|
202
|
+
return write_json_atomic(path, payload)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
__all__ = [
|
|
206
|
+
"write_state_stability",
|
|
207
|
+
]
|
dfckit/cli.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Small command-line adapters for the XCP-D-first workflows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
|
|
10
|
+
from .commands.parser import build_parser
|
|
11
|
+
from .commands.reporting import (
|
|
12
|
+
compare_state_counts as _compare_state_counts,
|
|
13
|
+
)
|
|
14
|
+
from .commands.reporting import (
|
|
15
|
+
describe_states as _describe_states,
|
|
16
|
+
)
|
|
17
|
+
from .commands.reporting import (
|
|
18
|
+
infer_endpoints as _infer_endpoints,
|
|
19
|
+
)
|
|
20
|
+
from .commands.reporting import (
|
|
21
|
+
infer_state_metrics as _infer_state_metrics,
|
|
22
|
+
)
|
|
23
|
+
from .commands.reporting import (
|
|
24
|
+
summarize_information as _summarize_information,
|
|
25
|
+
)
|
|
26
|
+
from .commands.reporting import (
|
|
27
|
+
summarize_store as _summarize_store,
|
|
28
|
+
)
|
|
29
|
+
from .commands.source import (
|
|
30
|
+
build_store as _build_store,
|
|
31
|
+
)
|
|
32
|
+
from .commands.source import (
|
|
33
|
+
fixed_information as _fixed_information,
|
|
34
|
+
)
|
|
35
|
+
from .commands.source import (
|
|
36
|
+
inspect_xcpd as _inspect,
|
|
37
|
+
)
|
|
38
|
+
from .commands.stability import (
|
|
39
|
+
align_states as _align_states,
|
|
40
|
+
)
|
|
41
|
+
from .commands.stability import (
|
|
42
|
+
summarize_stability as _summarize_stability,
|
|
43
|
+
)
|
|
44
|
+
from .commands.states import (
|
|
45
|
+
fit_states as _fit_states,
|
|
46
|
+
)
|
|
47
|
+
from .commands.states import (
|
|
48
|
+
predict_states as _predict_states,
|
|
49
|
+
)
|
|
50
|
+
from .commands.states import (
|
|
51
|
+
score_states as _score_states,
|
|
52
|
+
)
|
|
53
|
+
from .commands.states import (
|
|
54
|
+
summarize_states as _summarize_states,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _parser() -> argparse.ArgumentParser:
|
|
59
|
+
return build_parser()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
63
|
+
"""Run a CLI command and return a process-style status code."""
|
|
64
|
+
parser = _parser()
|
|
65
|
+
namespace = parser.parse_args(argv)
|
|
66
|
+
try:
|
|
67
|
+
if namespace.command == "inspect-xcpd":
|
|
68
|
+
result = _inspect(namespace)
|
|
69
|
+
elif namespace.command == "build-store":
|
|
70
|
+
result = _build_store(namespace)
|
|
71
|
+
elif namespace.command == "fixed-information":
|
|
72
|
+
result = _fixed_information(namespace)
|
|
73
|
+
elif namespace.command == "fit-states":
|
|
74
|
+
result = _fit_states(namespace)
|
|
75
|
+
elif namespace.command == "predict-states":
|
|
76
|
+
result = _predict_states(namespace)
|
|
77
|
+
elif namespace.command == "summarize-states":
|
|
78
|
+
result = _summarize_states(namespace)
|
|
79
|
+
elif namespace.command == "summarize-store":
|
|
80
|
+
result = _summarize_store(namespace)
|
|
81
|
+
elif namespace.command == "summarize-information":
|
|
82
|
+
result = _summarize_information(namespace)
|
|
83
|
+
elif namespace.command == "describe-states":
|
|
84
|
+
result = _describe_states(namespace)
|
|
85
|
+
elif namespace.command == "infer-state-metrics":
|
|
86
|
+
result = _infer_state_metrics(namespace)
|
|
87
|
+
elif namespace.command == "infer-paired-endpoints":
|
|
88
|
+
result = _infer_endpoints(namespace)
|
|
89
|
+
elif namespace.command == "score-states":
|
|
90
|
+
result = _score_states(namespace)
|
|
91
|
+
elif namespace.command == "compare-state-counts":
|
|
92
|
+
result = _compare_state_counts(namespace)
|
|
93
|
+
elif namespace.command == "align-states":
|
|
94
|
+
result = _align_states(namespace)
|
|
95
|
+
else:
|
|
96
|
+
result = _summarize_stability(namespace)
|
|
97
|
+
except (ImportError, OSError, RuntimeError, TypeError, ValueError) as error:
|
|
98
|
+
print(f"dfc-kit: error: {error}", file=sys.stderr)
|
|
99
|
+
return 2
|
|
100
|
+
print(json.dumps(result, ensure_ascii=True, sort_keys=True))
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__": # pragma: no cover
|
|
105
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal command handlers used by :mod:`dfckit.cli`."""
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Argument parser construction for the dfc-kit command line."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _add_xcpd_arguments(parser: argparse.ArgumentParser) -> None:
|
|
10
|
+
parser.add_argument("root", type=Path, help="XCP-D derivatives root")
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"--atlas",
|
|
13
|
+
action="append",
|
|
14
|
+
required=True,
|
|
15
|
+
help="atlas name; repeat for multiple atlases",
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--subject",
|
|
19
|
+
action="append",
|
|
20
|
+
help="optional BIDS subject label or value; repeat to select multiple subjects",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument("--session", help="optional BIDS session label or value")
|
|
23
|
+
parser.add_argument("--task", help="optional BIDS task label or value")
|
|
24
|
+
parser.add_argument("--space", help="optional XCP-D standard-space label")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _add_load_arguments(parser: argparse.ArgumentParser) -> None:
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--roi-selection",
|
|
30
|
+
type=Path,
|
|
31
|
+
help="JSON object mapping atlas names to ordered ROI-name arrays",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--minimum-coverage",
|
|
35
|
+
type=float,
|
|
36
|
+
default=None,
|
|
37
|
+
help="reject selected ROIs below this XCP-D coverage fraction",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument("--tr", type=float, default=None, help="run TR in seconds")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
43
|
+
"""Build the complete command-line parser."""
|
|
44
|
+
parser = argparse.ArgumentParser(
|
|
45
|
+
prog="dfc-kit",
|
|
46
|
+
description="Censor-aware XCP-D to dynamic-connectivity workflows",
|
|
47
|
+
)
|
|
48
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
49
|
+
|
|
50
|
+
inspect = subparsers.add_parser(
|
|
51
|
+
"inspect-xcpd",
|
|
52
|
+
help="list matching XCP-D acquisitions without loading time series",
|
|
53
|
+
)
|
|
54
|
+
_add_xcpd_arguments(inspect)
|
|
55
|
+
|
|
56
|
+
build = subparsers.add_parser(
|
|
57
|
+
"build-store",
|
|
58
|
+
help="load XCP-D acquisitions and stream a feature store",
|
|
59
|
+
)
|
|
60
|
+
_add_xcpd_arguments(build)
|
|
61
|
+
_add_load_arguments(build)
|
|
62
|
+
build.add_argument("output", type=Path, help="new FeatureStore directory")
|
|
63
|
+
build.add_argument(
|
|
64
|
+
"--method",
|
|
65
|
+
choices=("window-fc", "cap", "ets", "leida", "mtd"),
|
|
66
|
+
required=True,
|
|
67
|
+
)
|
|
68
|
+
build.add_argument("--chunk-size", type=int, default=128)
|
|
69
|
+
build.add_argument("--dtype", choices=("float32", "float64"), default="float64")
|
|
70
|
+
build.add_argument("--window-length", type=int, default=56)
|
|
71
|
+
build.add_argument("--window-step", type=int, default=8)
|
|
72
|
+
build.add_argument("--taper", choices=("hamming", "uniform"), default="hamming")
|
|
73
|
+
build.add_argument("--minimum-segment-length", type=int, default=20)
|
|
74
|
+
|
|
75
|
+
information = subparsers.add_parser(
|
|
76
|
+
"fixed-information",
|
|
77
|
+
help="estimate fixed-length block MI/CMI from XCP-D acquisitions",
|
|
78
|
+
)
|
|
79
|
+
_add_xcpd_arguments(information)
|
|
80
|
+
_add_load_arguments(information)
|
|
81
|
+
information.add_argument(
|
|
82
|
+
"output",
|
|
83
|
+
type=Path,
|
|
84
|
+
help="new fixed-information artifact directory",
|
|
85
|
+
)
|
|
86
|
+
information.add_argument(
|
|
87
|
+
"--information-groups",
|
|
88
|
+
type=Path,
|
|
89
|
+
required=True,
|
|
90
|
+
help="standalone JSON declaring named left, right, and conditioning ROI groups",
|
|
91
|
+
)
|
|
92
|
+
information.add_argument(
|
|
93
|
+
"--length",
|
|
94
|
+
action="append",
|
|
95
|
+
type=int,
|
|
96
|
+
required=True,
|
|
97
|
+
help="fixed retained-frame length; repeat for a sensitivity set",
|
|
98
|
+
)
|
|
99
|
+
information.add_argument("--draws", type=int, required=True)
|
|
100
|
+
information.add_argument("--sample-seed", type=int, required=True)
|
|
101
|
+
information.add_argument(
|
|
102
|
+
"--jobs",
|
|
103
|
+
type=int,
|
|
104
|
+
default=1,
|
|
105
|
+
help="validated execution parallelism (default: 1)",
|
|
106
|
+
)
|
|
107
|
+
information.add_argument(
|
|
108
|
+
"--window-schedule",
|
|
109
|
+
type=Path,
|
|
110
|
+
help="optional strict TSV freezing acquisition, length, draw, and original frame bounds",
|
|
111
|
+
)
|
|
112
|
+
information.add_argument("--k", type=int, default=3)
|
|
113
|
+
information.add_argument("--jitter", type=float, default=1e-10)
|
|
114
|
+
information.add_argument("--jitter-seed", type=int, default=20260811)
|
|
115
|
+
information.add_argument(
|
|
116
|
+
"--standardize",
|
|
117
|
+
action=argparse.BooleanOptionalAction,
|
|
118
|
+
default=True,
|
|
119
|
+
help="z-score each ROI within each fixed window (default: enabled)",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
fit = subparsers.add_parser(
|
|
123
|
+
"fit-states",
|
|
124
|
+
help="fit a portable KMeans or Gaussian HMM model from a FeatureStore",
|
|
125
|
+
)
|
|
126
|
+
fit.add_argument("store", type=Path, help="existing FeatureStore directory")
|
|
127
|
+
fit.add_argument("output", type=Path, help="new fitted-model artifact directory")
|
|
128
|
+
fit.add_argument("--method", choices=("kmeans", "hmm"), required=True)
|
|
129
|
+
fit.add_argument("--n-states", type=int, required=True)
|
|
130
|
+
fit.add_argument("--seed", type=int, required=True)
|
|
131
|
+
fit.add_argument(
|
|
132
|
+
"--subject",
|
|
133
|
+
action="append",
|
|
134
|
+
help="fit only this subject; repeat for an explicit training cohort",
|
|
135
|
+
)
|
|
136
|
+
fit.add_argument(
|
|
137
|
+
"--n-init",
|
|
138
|
+
type=int,
|
|
139
|
+
default=None,
|
|
140
|
+
help="initializations (default: 10 for KMeans, 1 for HMM)",
|
|
141
|
+
)
|
|
142
|
+
fit.add_argument(
|
|
143
|
+
"--standardize-features",
|
|
144
|
+
action=argparse.BooleanOptionalAction,
|
|
145
|
+
default=True,
|
|
146
|
+
)
|
|
147
|
+
fit.add_argument("--fitting-mode", choices=("streaming", "materialized"), default="streaming")
|
|
148
|
+
fit.add_argument("--algorithm", choices=("lloyd", "minibatch"), default="minibatch")
|
|
149
|
+
fit.add_argument("--batch-size", type=int, default=4096)
|
|
150
|
+
fit.add_argument("--max-iter", type=int, default=10)
|
|
151
|
+
fit.add_argument("--reassignment-ratio", type=float, default=0.01)
|
|
152
|
+
fit.add_argument("--init-sample-size", type=int, default=None)
|
|
153
|
+
fit.add_argument(
|
|
154
|
+
"--n-pca-components",
|
|
155
|
+
type=int,
|
|
156
|
+
default=None,
|
|
157
|
+
help="optional PCA dimension for KMeans; required for HMM",
|
|
158
|
+
)
|
|
159
|
+
fit.add_argument("--covariance-type", choices=("diag", "full"), default="diag")
|
|
160
|
+
fit.add_argument("--n-iter", type=int, default=200)
|
|
161
|
+
fit.add_argument("--tol", type=float, default=1e-3)
|
|
162
|
+
fit.add_argument("--pca-batch-size", type=int, default=4096)
|
|
163
|
+
fit.add_argument("--minimum-sequence-length", type=int, default=2)
|
|
164
|
+
|
|
165
|
+
predict = subparsers.add_parser(
|
|
166
|
+
"predict-states",
|
|
167
|
+
help="decode a FeatureStore with a saved KMeans or Gaussian HMM",
|
|
168
|
+
)
|
|
169
|
+
predict.add_argument("store", type=Path, help="FeatureStore to decode")
|
|
170
|
+
predict.add_argument("model", type=Path, help="saved fitted-model artifact")
|
|
171
|
+
predict.add_argument("output", type=Path, help="new state-prediction artifact directory")
|
|
172
|
+
predict.add_argument(
|
|
173
|
+
"--subject",
|
|
174
|
+
action="append",
|
|
175
|
+
help="decode only this subject; repeat for an explicit held-out cohort",
|
|
176
|
+
)
|
|
177
|
+
predict.add_argument(
|
|
178
|
+
"--allow-fit-subjects",
|
|
179
|
+
action="store_true",
|
|
180
|
+
help="explicitly allow decoding subjects used during model fitting",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
summarize = subparsers.add_parser(
|
|
184
|
+
"summarize-states",
|
|
185
|
+
help="write occupancy, dwell, switch, and transition metrics as JSON",
|
|
186
|
+
)
|
|
187
|
+
summarize.add_argument("predictions", type=Path, help="state-prediction artifact directory")
|
|
188
|
+
summarize.add_argument("output", type=Path, help="new state-metrics JSON file")
|
|
189
|
+
|
|
190
|
+
summarize_features = subparsers.add_parser(
|
|
191
|
+
"summarize-store",
|
|
192
|
+
help="write acquisition-level statistics for every named FeatureStore feature",
|
|
193
|
+
)
|
|
194
|
+
summarize_features.add_argument("store", type=Path, help="FeatureStore to summarize")
|
|
195
|
+
summarize_features.add_argument("output", type=Path, help="new endpoint-summary JSON file")
|
|
196
|
+
summarize_features.add_argument(
|
|
197
|
+
"--statistic",
|
|
198
|
+
action="append",
|
|
199
|
+
choices=("mean", "variance", "standard_deviation", "minimum", "maximum"),
|
|
200
|
+
default=None,
|
|
201
|
+
help="statistic to compute; repeat for multiple statistics (default: mean)",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
summarize_information = subparsers.add_parser(
|
|
205
|
+
"summarize-information",
|
|
206
|
+
help="extract acquisition-level MI and CMI endpoints from an audited artifact",
|
|
207
|
+
)
|
|
208
|
+
summarize_information.add_argument("artifact", type=Path)
|
|
209
|
+
summarize_information.add_argument("output", type=Path)
|
|
210
|
+
|
|
211
|
+
describe = subparsers.add_parser(
|
|
212
|
+
"describe-states",
|
|
213
|
+
help="describe state patterns using named ROI or edge features",
|
|
214
|
+
)
|
|
215
|
+
describe.add_argument("store", type=Path, help="FeatureStore used to fit the model")
|
|
216
|
+
describe.add_argument("model", type=Path, help="saved KMeans or Gaussian HMM artifact")
|
|
217
|
+
describe.add_argument("output", type=Path, help="new state-description JSON file")
|
|
218
|
+
describe.add_argument("--top-features", type=int, default=10)
|
|
219
|
+
describe.add_argument(
|
|
220
|
+
"--network-map",
|
|
221
|
+
type=Path,
|
|
222
|
+
help="optional JSON object mapping every model ROI to a network label",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
infer_states = subparsers.add_parser(
|
|
226
|
+
"infer-state-metrics",
|
|
227
|
+
help="run paired sign-flip inference and declared-family FDR over state metrics",
|
|
228
|
+
)
|
|
229
|
+
infer_states.add_argument("metrics", type=Path, help="state-metrics JSON file")
|
|
230
|
+
infer_states.add_argument("output", type=Path, help="new inference artifact directory")
|
|
231
|
+
infer_states.add_argument("--condition-a", required=True)
|
|
232
|
+
infer_states.add_argument("--condition-b", required=True)
|
|
233
|
+
infer_states.add_argument(
|
|
234
|
+
"--metric",
|
|
235
|
+
action="append",
|
|
236
|
+
choices=(
|
|
237
|
+
"occupancy",
|
|
238
|
+
"mean_dwell_seconds",
|
|
239
|
+
"switch_rate",
|
|
240
|
+
"transition_probabilities",
|
|
241
|
+
),
|
|
242
|
+
required=True,
|
|
243
|
+
)
|
|
244
|
+
infer_states.add_argument("--fdr-family", required=True)
|
|
245
|
+
infer_states.add_argument("--alpha", type=float, default=0.05)
|
|
246
|
+
infer_states.add_argument("--permutations", type=int, default=10_000)
|
|
247
|
+
infer_states.add_argument("--bootstrap", type=int, default=10_000)
|
|
248
|
+
infer_states.add_argument("--seed", type=int, required=True)
|
|
249
|
+
infer_states.add_argument("--exact", action="store_true")
|
|
250
|
+
|
|
251
|
+
infer_endpoints = subparsers.add_parser(
|
|
252
|
+
"infer-paired-endpoints",
|
|
253
|
+
help="run paired sign-flip inference over a named endpoint JSON artifact",
|
|
254
|
+
)
|
|
255
|
+
infer_endpoints.add_argument("endpoints", type=Path)
|
|
256
|
+
infer_endpoints.add_argument("output", type=Path)
|
|
257
|
+
infer_endpoints.add_argument("--condition-a", required=True)
|
|
258
|
+
infer_endpoints.add_argument("--condition-b", required=True)
|
|
259
|
+
infer_endpoints.add_argument("--fdr-family", required=True)
|
|
260
|
+
infer_endpoints.add_argument("--alpha", type=float, default=0.05)
|
|
261
|
+
infer_endpoints.add_argument("--permutations", type=int, default=10_000)
|
|
262
|
+
infer_endpoints.add_argument("--bootstrap", type=int, default=10_000)
|
|
263
|
+
infer_endpoints.add_argument("--seed", type=int, required=True)
|
|
264
|
+
infer_endpoints.add_argument("--exact", action="store_true")
|
|
265
|
+
|
|
266
|
+
score = subparsers.add_parser(
|
|
267
|
+
"score-states",
|
|
268
|
+
help="score a saved KMeans or HMM on held-out FeatureStore acquisitions",
|
|
269
|
+
)
|
|
270
|
+
score.add_argument("store", type=Path, help="FeatureStore to score")
|
|
271
|
+
score.add_argument("model", type=Path, help="saved fitted-model artifact")
|
|
272
|
+
score.add_argument("output", type=Path, help="new held-out score JSON file")
|
|
273
|
+
score.add_argument(
|
|
274
|
+
"--subject",
|
|
275
|
+
action="append",
|
|
276
|
+
help="score only this subject; repeat for an explicit held-out cohort",
|
|
277
|
+
)
|
|
278
|
+
score.add_argument(
|
|
279
|
+
"--allow-fit-subjects",
|
|
280
|
+
action="store_true",
|
|
281
|
+
help="explicitly allow scoring subjects used during model fitting",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
compare = subparsers.add_parser(
|
|
285
|
+
"compare-state-counts",
|
|
286
|
+
help="compare complete candidate-state sets across subject-disjoint folds",
|
|
287
|
+
)
|
|
288
|
+
compare.add_argument("output", type=Path, help="new state-count comparison JSON file")
|
|
289
|
+
compare.add_argument(
|
|
290
|
+
"--score",
|
|
291
|
+
action="append",
|
|
292
|
+
type=Path,
|
|
293
|
+
required=True,
|
|
294
|
+
help=(
|
|
295
|
+
"score-states JSON artifact; repeat for every fold, state count, "
|
|
296
|
+
"and complete-fit seed"
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
align = subparsers.add_parser(
|
|
301
|
+
"align-states",
|
|
302
|
+
help="align a candidate KMeans/HMM model and optional predictions to a reference",
|
|
303
|
+
)
|
|
304
|
+
align.add_argument("reference_model", type=Path, help="reference fitted-model artifact")
|
|
305
|
+
align.add_argument("candidate_model", type=Path, help="candidate fitted-model artifact")
|
|
306
|
+
align.add_argument("output_model", type=Path, help="new relabeled model artifact")
|
|
307
|
+
align.add_argument("alignment_output", type=Path, help="new alignment artifact directory")
|
|
308
|
+
align.add_argument(
|
|
309
|
+
"--alignment-metric",
|
|
310
|
+
choices=("auto", "euclidean", "pearson"),
|
|
311
|
+
default="auto",
|
|
312
|
+
help="state-pattern matching metric (default: auto; CAP uses pearson)",
|
|
313
|
+
)
|
|
314
|
+
align.add_argument(
|
|
315
|
+
"--predictions",
|
|
316
|
+
type=Path,
|
|
317
|
+
help="candidate state-prediction artifact to relabel",
|
|
318
|
+
)
|
|
319
|
+
align.add_argument(
|
|
320
|
+
"--predictions-output",
|
|
321
|
+
type=Path,
|
|
322
|
+
help="new aligned state-prediction artifact directory",
|
|
323
|
+
)
|
|
324
|
+
stability = subparsers.add_parser(
|
|
325
|
+
"summarize-stability",
|
|
326
|
+
help="decode, align, and summarize repeated-seed state models",
|
|
327
|
+
)
|
|
328
|
+
stability.add_argument("store", type=Path, help="FeatureStore to decode identically")
|
|
329
|
+
stability.add_argument("reference_model", type=Path, help="reference fitted-model artifact")
|
|
330
|
+
stability.add_argument("output", type=Path, help="new repeated-fit stability JSON file")
|
|
331
|
+
stability.add_argument(
|
|
332
|
+
"--candidate-model",
|
|
333
|
+
action="append",
|
|
334
|
+
type=Path,
|
|
335
|
+
required=True,
|
|
336
|
+
help="candidate fitted model; repeat for every additional seed",
|
|
337
|
+
)
|
|
338
|
+
stability.add_argument(
|
|
339
|
+
"--alignment-metric",
|
|
340
|
+
choices=("auto", "euclidean", "pearson"),
|
|
341
|
+
default="auto",
|
|
342
|
+
help="state-pattern matching metric (default: auto; CAP uses pearson)",
|
|
343
|
+
)
|
|
344
|
+
stability.add_argument(
|
|
345
|
+
"--subject",
|
|
346
|
+
action="append",
|
|
347
|
+
help="decode only this subject; repeat for an explicit held-out cohort",
|
|
348
|
+
)
|
|
349
|
+
stability.add_argument(
|
|
350
|
+
"--allow-fit-subjects",
|
|
351
|
+
action="store_true",
|
|
352
|
+
help="explicitly allow decoding subjects used during model fitting",
|
|
353
|
+
)
|
|
354
|
+
return parser
|