Flex-search report: one self-contained HTML record per flex-search run.
Built on :mod:tit.reporting.html.components (ARCHITECTURE.md §14). Everything is read from the
run folder: flex_meta.json (goal, ROI, currents, electrode, every restart's value),
candidate_history/NN/ (the accepted candidate's ROI and background means and currents, and
the manifest's metric definitions), electrode_positions.json, the ROI confirmation
(roi.tetravox.json), the final per-channel simulations and the run's own figures
(valid_skin_region.png, roi.png, embedded as WebP). Rules come from
tit.reporting.qc_rules.RULES["opt"].
Flex electrodes sit at free scalp positions, so they are listed by coordinate, not drawn on a cap.
tit.opt.flex writes one when a run finishes. Rebuild one (nothing is re-optimised)::
simnibs_python -m tit.reporting.generators.flex_search <project> <subject> <run folder> [--out DIR]
goal_words
(what the goal maximises, what the score means, the score as a user reads it).
The optimiser minimises, so every score is the negative of the quantity it maximises.
Source code in tit/reporting/generators/flex_search.py
| def goal_words(meta: dict) -> tuple[str, str, float]:
"""``(what the goal maximises, what the score means, the score as a user reads it)``.
The optimiser minimises, so every score is the negative of the quantity it maximises.
"""
goal, v = meta.get("goal"), -float(meta["result"]["best_value"])
w = float(meta.get("intensity_weight") or 0.0)
if goal == "mean":
return "the mean field in the target", f"mean target field {v:.3f} V/m", v
if goal == "max":
return (
"the peak (99.9th percentile) field in the target",
f"peak target field {v:.3f} V/m",
v,
)
if goal == "focality":
return (
"focality by SimNIBS's threshold-based ROC measure",
f"ROC focality score {v:.2f} (100 × (√2 − ROC distance); higher is more focal)",
v,
)
power = "" if not w else f"<sup>{1 + w:g}</sup>"
return (
"the target's mean field relative to everywhere else",
f"target mean{power} {v:.2f}× the background mean",
v,
)
|
{file name: WebP bytes} for each of :data:_RUN_FIGURES the run wrote.
Source code in tit/reporting/generators/flex_search.py
| def run_figures(run_dir: Path) -> dict[str, bytes]:
"""``{file name: WebP bytes}`` for each of :data:`_RUN_FIGURES` the run wrote."""
import io
from PIL import Image
out = {}
for name, width in _RUN_FIGURES.items():
if (run_dir / name).is_file():
image = Image.open(run_dir / name)
image.thumbnail((width, width))
buf = io.BytesIO()
image.save(buf, "WEBP", quality=80, method=6)
out[name] = buf.getvalue()
return out
|
collect
Everything the report shows, from one flex-search run folder.
Source code in tit/reporting/generators/flex_search.py
| def collect(run_dir: str | Path) -> dict:
"""Everything the report shows, from one flex-search run folder."""
run_dir = Path(run_dir)
meta = json.loads((run_dir / "flex_meta.json").read_text())
pos_file = run_dir / "electrode_positions.json"
positions = json.loads(pos_file.read_text()) if pos_file.is_file() else {}
carriers = []
for d in sorted(run_dir.glob("final_sim_*")):
f = d / "fields_summary.txt"
if f.is_file():
carriers += parse_fields_summary(f.read_text())
logs = sorted(run_dir.glob("simnibs_optimization_*.log"))
simnibs = None
if logs:
m = re.search(r"simnibs (\d[\w.]*)", logs[-1].read_text(errors="replace"))
simnibs = m.group(1) if m else None
summary = run_dir / "summary.txt"
calib = (
re.search(
r"calibration error exceeded 10%! Estimated error value: ([\d.]+)%",
summary.read_text(),
)
if summary.is_file()
else None
)
rec = {
"run": run_dir.name,
"meta": meta,
"restarts": _best_rows(run_dir),
"positions": positions.get("optimized_positions") or [],
"channel_array": positions.get("channel_array_indices") or [],
"carriers": carriers,
"roi": common.roi_summary(run_dir),
"simnibs": simnibs,
"calibration_error_pct": float(calib.group(1)) if calib else None,
"figures": run_figures(run_dir),
}
config = (
rec["restarts"][0]["manifest"].get("config") if rec["restarts"] else None
) or {}
rec["config"] = config
return rec
|
currents
Per-channel currents of the accepted montage (the searched split when there is one).
Source code in tit/reporting/generators/flex_search.py
| def currents(rec: dict) -> list[float]:
"""Per-channel currents of the accepted montage (the searched split when there is one)."""
meta = rec["meta"]
best = (
rec["restarts"][meta["result"].get("best_run_index") or 0]["row"]
if rec["restarts"]
else {}
)
if best.get("current_ch1_mA"):
return [float(best["current_ch1_mA"]), float(best["current_ch2_mA"])]
if meta.get("current_split"):
return [float(v) for v in meta["current_split"]]
return [float(meta["current_mA"])] * 2
|
create_flex_search_report
Write the report for one flex-search run folder; into the project's reports unless out_dir.
Source code in tit/reporting/generators/flex_search.py
| def create_flex_search_report(
project_dir: str | Path,
subject_id: str,
run_dir: str | Path,
out_dir: str | Path | None = None,
) -> Path:
"""Write the report for one flex-search run folder; into the project's reports unless *out_dir*."""
t0 = time.time()
rec = collect(run_dir)
return common.write_report(
build_html(rec, subject_id),
project_dir,
subject_id,
REPORT_PREFIX,
SIZE_BUDGET,
out_dir,
t0,
label="Flex-search report",
)
|