OSCR

Contusions bias cortical thickness estimates after traumatic brain injury: A TRACK-TBI study.

Code ↔ Paper

1 match between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 1 match
  1. [1] § Methods › Cortical thickness estimation ↔ antsnetct_postproc_thickness.py, lines 1–59 · score 0.53 · post processing, ANTsNetCT, maps, segmentation, Cortical thickness, ROI

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 628 lines · 21 KB · no license · 1 match

  1. #!/usr/bin/env python3
  2. """Post-process ANTsNetCT cortical thickness inputs.
  3. This script recomputes ANTs KellyKapowski thickness from the ANTsNetCT
  4. segmentation and posterior probability maps. User provides the derivatives
  5. root, subject, and session, and the script discovers matching ANTsNetCT
  6. outputs beneath that location. If contusion-mask subtraction is requested,
  7. the user must also provide either ``--contusion-mask`` or ``--bids-dir`` so
  8. the mask can be found.
  9. Assumptions
  10. -----------
  11. The script assumes the ANTsNetCT derivative layout and filenames used by the
  12. official package and by this repository's submit wrappers:
  13. <derivatives-dir>/<antsnetct-dataset>/<sub>/<ses>/anat/
  14. <prefix>_dseg.nii.gz
  15. <prefix>_label-WM_probseg.nii.gz
  16. <prefix>_label-SGM_probseg.nii.gz
  17. <prefix>_label-CGM_probseg.nii.gz
  18. where ``<prefix>`` ends in ``_seg-antsnetct``. By default
  19. ``<antsnetct-dataset>`` is ``output_longi``.
  20. The ANTsNetCT labels are assumed to be:
  21. WM = 2, CGM = 8, SGM = 9
  22. Post-processing follows the legacy TRACK workflow: SGM is relabeled as WM in
  23. the discrete segmentation, and the WM posterior supplied to KellyKapowski is
  24. WM + SGM. If a contusion mask is enabled, it is binarized, resampled to the
  25. ANTsNetCT segmentation grid, optionally dilated there, then removed from the
  26. segmentation and GM/WM posteriors before KellyKapowski is run.
  27. """
  28. from __future__ import annotations
  29. import argparse
  30. import glob
  31. import os
  32. import sys
  33. from dataclasses import dataclass
  34. from textwrap import dedent
  35. from typing import Any, Iterable, List, Optional, Sequence, Tuple
  36. DEFAULT_ANTSNETCT_DATASET = "output_longi"
  37. DEFAULT_OUTPUT_DESC = "thicknessPostproc"
  38. DEFAULT_CONTUSION_OUTPUT_DESC = "thicknessPostprocNoContusion"
  39. DEFAULT_PREPARED_MASK_DESC = "contusionMaskPostproc"
  40. GM_LABEL = 8
  41. WM_LABEL = 2
  42. SGM_LABEL = 9
  43. DEFAULT_CONTUSION_PATTERNS = (
  44. "*_label-contusion*.nii.gz",
  45. "*_label-lesion_roi.nii.gz",
  46. "*_label-lesion*.nii.gz",
  47. )
  48. @dataclass(frozen=True)
  49. class AntsNetCtTarget:
  50. """File group needed to run KellyKapowski for one ANTsNetCT prefix."""
  51. prefix: str
  52. anat_dir: str
  53. dseg_path: str
  54. wm_probseg_path: str
  55. sgm_probseg_path: str
  56. cgm_probseg_path: str
  57. output_path: str
  58. contusion_output_path: str
  59. prepared_contusion_mask_path: str
  60. @property
  61. def required_paths(self) -> Tuple[str, str, str, str]:
  62. return (
  63. self.dseg_path,
  64. self.wm_probseg_path,
  65. self.sgm_probseg_path,
  66. self.cgm_probseg_path,
  67. )
  68. def normalize_bids_label(value: str, prefix: str) -> str:
  69. """Return a BIDS-like label with the requested prefix."""
  70. return value if value.startswith(prefix) else f"{prefix}{value}"
  71. def bool_from_int(value: int) -> bool:
  72. """Convert legacy 0/1 argparse values to bool."""
  73. return value == 1
  74. def positive_or_zero_int(value: str) -> int:
  75. """Argparse type for non-negative integer options."""
  76. parsed = int(value)
  77. if parsed < 0:
  78. raise argparse.ArgumentTypeError("value must be >= 0")
  79. return parsed
  80. def antsnetct_anat_dir(derivatives_dir: str, dataset: str, sub: str, ses: str) -> str:
  81. """Return the expected subject/session anat directory."""
  82. return os.path.join(derivatives_dir, dataset, sub, ses, "anat")
  83. def strip_nii_gz_suffix(path: str) -> str:
  84. """Return a filename stem while preserving ordinary dots in the prefix."""
  85. filename = os.path.basename(path)
  86. suffix = ".nii.gz"
  87. return filename[: -len(suffix)] if filename.endswith(suffix) else os.path.splitext(filename)[0]
  88. def discover_prefixes(anat_dir: str, requested_prefix: Optional[str]) -> List[str]:
  89. """Find ANTsNetCT filename prefixes in an anat directory.
  90. ``requested_prefix`` may be either a basename prefix or a full path to a
  91. ``*_dseg.nii.gz`` file. Discovery is otherwise based on the ANTsNetCT
  92. ``*_seg-antsnetct_dseg.nii.gz`` suffix rather than a project-specific
  93. acquisition label.
  94. """
  95. if requested_prefix is not None:
  96. prefix = strip_nii_gz_suffix(requested_prefix)
  97. if prefix.endswith("_dseg"):
  98. prefix = prefix[: -len("_dseg")]
  99. return [prefix]
  100. pattern = os.path.join(anat_dir, "*_seg-antsnetct_dseg.nii.gz")
  101. prefixes: List[str] = []
  102. for dseg_path in sorted(glob.glob(pattern)):
  103. filename = os.path.basename(dseg_path)
  104. prefixes.append(filename[: -len("_dseg.nii.gz")])
  105. return deduplicate(prefixes)
  106. def deduplicate(values: Iterable[str]) -> List[str]:
  107. """Return values in their first-seen order with duplicates removed."""
  108. seen = set()
  109. unique: List[str] = []
  110. for value in values:
  111. if value not in seen:
  112. seen.add(value)
  113. unique.append(value)
  114. return unique
  115. def target_for_prefix(
  116. anat_dir: str,
  117. prefix: str,
  118. output_desc: str,
  119. contusion_output_desc: str,
  120. prepared_mask_desc: str,
  121. ) -> AntsNetCtTarget:
  122. """Build all file paths associated with one ANTsNetCT prefix."""
  123. return AntsNetCtTarget(
  124. prefix=prefix,
  125. anat_dir=anat_dir,
  126. dseg_path=os.path.join(anat_dir, f"{prefix}_dseg.nii.gz"),
  127. wm_probseg_path=os.path.join(anat_dir, f"{prefix}_label-WM_probseg.nii.gz"),
  128. sgm_probseg_path=os.path.join(anat_dir, f"{prefix}_label-SGM_probseg.nii.gz"),
  129. cgm_probseg_path=os.path.join(anat_dir, f"{prefix}_label-CGM_probseg.nii.gz"),
  130. output_path=os.path.join(anat_dir, f"{prefix}_desc-{output_desc}.nii.gz"),
  131. contusion_output_path=os.path.join(anat_dir, f"{prefix}_desc-{contusion_output_desc}.nii.gz"),
  132. prepared_contusion_mask_path=os.path.join(anat_dir, f"{prefix}_desc-{prepared_mask_desc}_mask.nii.gz"),
  133. )
  134. def missing_required_paths(target: AntsNetCtTarget) -> List[str]:
  135. """Return required ANTsNetCT inputs that are absent on disk."""
  136. return [path for path in target.required_paths if not os.path.exists(path)]
  137. def discover_contusion_masks(
  138. bids_dir: str,
  139. sub: str,
  140. ses: str,
  141. patterns: Sequence[str],
  142. ) -> List[str]:
  143. """Find candidate BIDS-space contusion masks for a subject/session."""
  144. anat_dir = os.path.join(bids_dir, sub, ses, "anat")
  145. matches: List[str] = []
  146. for pattern in patterns:
  147. matches.extend(sorted(glob.glob(os.path.join(anat_dir, pattern))))
  148. return deduplicate(matches)
  149. def choose_contusion_mask(
  150. explicit_mask: Optional[str],
  151. bids_dir: Optional[str],
  152. sub: str,
  153. ses: str,
  154. patterns: Sequence[str],
  155. ) -> Tuple[Optional[str], List[str]]:
  156. """Resolve the contusion mask path and return all discovered candidates."""
  157. if explicit_mask is not None:
  158. return explicit_mask, [explicit_mask]
  159. if bids_dir is None:
  160. return None, []
  161. candidates = discover_contusion_masks(bids_dir, sub, ses, patterns)
  162. return (candidates[0] if candidates else None), candidates
  163. def prepare_contusion_mask(
  164. ants: Any,
  165. mask_path: str,
  166. reference_image: Any,
  167. dilation_radius: int,
  168. dilation_shape: str,
  169. ) -> Any:
  170. """Load, binarize, resample, and optionally dilate a contusion mask.
  171. Dilation is performed after resampling so the radius is in voxels of the
  172. ANTsNetCT output grid. The returned mask is binary-valued on that grid.
  173. """
  174. mask = ants.threshold_image(ants.image_read(mask_path), low_thresh=0.1, inval=1, outval=0)
  175. mask = ants.resample_image_to_target(mask, reference_image, interp_type="nearestNeighbor")
  176. mask = ants.threshold_image(mask, low_thresh=0.5, inval=1, outval=0)
  177. if dilation_radius > 0:
  178. mask = ants.morphology(
  179. mask,
  180. operation="dilate",
  181. radius=dilation_radius,
  182. mtype="binary",
  183. value=1,
  184. shape=dilation_shape,
  185. )
  186. mask = ants.threshold_image(mask, low_thresh=0.5, inval=1, outval=0)
  187. return mask
  188. def subtract_mask(image: Any, mask: Optional[Any]) -> Any:
  189. """Remove masked voxels from an ANTs image while preserving image metadata."""
  190. if mask is None:
  191. return image
  192. return image - (image * mask)
  193. def run_kelly_kapowski(
  194. ants: Any,
  195. target: AntsNetCtTarget,
  196. contusion_mask: Optional[Any],
  197. kk_iterations: int,
  198. kk_r: float,
  199. kk_m: float,
  200. ) -> Any:
  201. """Run KellyKapowski thickness after ANTsNetCT-specific label handling."""
  202. kk_seg = ants.image_read(target.dseg_path)
  203. # KellyKapowski expects a GM label and WM label. ANTsNetCT keeps SGM as a
  204. # separate class, so this post-processing treats SGM as WM for thickness.
  205. kk_seg[kk_seg == SGM_LABEL] = WM_LABEL
  206. kk_seg = subtract_mask(kk_seg, contusion_mask)
  207. wm_posterior = ants.image_read(target.wm_probseg_path)
  208. sgm_posterior = ants.image_read(target.sgm_probseg_path)
  209. cgm_posterior = ants.image_read(target.cgm_probseg_path)
  210. kk_wm_posterior = subtract_mask(wm_posterior + sgm_posterior, contusion_mask)
  211. kk_gm_posterior = subtract_mask(cgm_posterior, contusion_mask)
  212. return ants.kelly_kapowski(
  213. s=kk_seg,
  214. g=kk_gm_posterior,
  215. w=kk_wm_posterior,
  216. its=kk_iterations,
  217. r=kk_r,
  218. m=kk_m,
  219. gm_label=GM_LABEL,
  220. wm_label=WM_LABEL,
  221. )
  222. def build_arg_parser() -> argparse.ArgumentParser:
  223. """Build the command-line interface used by ``main`` and ``--help``."""
  224. parser = argparse.ArgumentParser(
  225. description="Recompute KellyKapowski thickness from ANTsNetCT outputs.",
  226. formatter_class=argparse.RawDescriptionHelpFormatter,
  227. epilog=dedent(
  228. f"""\
  229. Examples:
  230. python antsnetct_postproc_thickness.py \\
  231. --derivatives-dir /path/to/derivatives --sub sub-001 --ses ses-2WK
  232. python antsnetct_postproc_thickness.py \\
  233. --derivatives-dir /path/to/derivatives --sub 001 --ses 2WK \\
  234. --bids-dir /path/to/bids --use-contusion-mask 1 \\
  235. --contusion-dilation-radius 2
  236. python antsnetct_postproc_thickness.py \\
  237. --derivatives-dir /path/to/derivatives --sub sub-001 --ses ses-2WK \\
  238. --contusion-mask /path/to/sub-001_ses-2WK_label-contusion_roi.nii.gz
  239. Notes:
  240. * Required inputs are --derivatives-dir, --sub, and --ses.
  241. * For contusion-mask discovery, also provide --bids-dir unless
  242. supplying --contusion-mask directly.
  243. * Default ANTsNetCT dataset: {DEFAULT_ANTSNETCT_DATASET}
  244. * Default primary output desc: {DEFAULT_OUTPUT_DESC}
  245. * --use-lesion-mask is retained as a deprecated alias for
  246. --use-contusion-mask to keep older submit wrappers working.
  247. """
  248. ),
  249. )
  250. parser.add_argument(
  251. "--derivatives-dir",
  252. required=True,
  253. help="Derivatives root that contains the ANTsNetCT dataset directory.",
  254. )
  255. parser.add_argument("--sub", required=True, help="Subject ID, e.g. sub-141048 or 141048.")
  256. parser.add_argument("--ses", required=True, help="Session ID, e.g. ses-2WK or 2WK.")
  257. parser.add_argument(
  258. "--antsnetct-dataset",
  259. default=DEFAULT_ANTSNETCT_DATASET,
  260. help="Dataset directory inside --derivatives-dir, usually output_longi or output_cross.",
  261. )
  262. parser.add_argument(
  263. "--prefix",
  264. help=(
  265. "Optional ANTsNetCT filename prefix to process. By default all "
  266. "*_seg-antsnetct_dseg.nii.gz prefixes in the subject/session anat "
  267. "directory are processed."
  268. ),
  269. )
  270. parser.add_argument(
  271. "--output-desc",
  272. default=DEFAULT_OUTPUT_DESC,
  273. help="BIDS desc value for the primary thickness output.",
  274. )
  275. parser.add_argument(
  276. "--contusion-output-desc",
  277. default=DEFAULT_CONTUSION_OUTPUT_DESC,
  278. help="Additional BIDS desc value written when a contusion mask is applied.",
  279. )
  280. parser.add_argument(
  281. "--bids-dir",
  282. help=(
  283. "BIDS root used to discover contusion masks when --contusion-mask "
  284. "is not provided. Required when --use-contusion-mask 1 is set "
  285. "without --contusion-mask."
  286. ),
  287. )
  288. parser.add_argument(
  289. "--contusion-mask",
  290. help="Explicit contusion/lesion mask path. Providing this enables contusion masking.",
  291. )
  292. parser.add_argument(
  293. "--contusion-mask-pattern",
  294. action="append",
  295. help=(
  296. "Glob pattern searched under <bids-dir>/<sub>/<ses>/anat. May be "
  297. "specified more than once. Defaults cover common contusion/lesion "
  298. "BIDS names."
  299. ),
  300. )
  301. parser.add_argument(
  302. "--use-contusion-mask",
  303. "--use-lesion-mask",
  304. dest="use_contusion_mask",
  305. type=int,
  306. choices=[0, 1],
  307. default=0,
  308. help="If 1, subtract a contusion mask before KellyKapowski. Legacy alias: --use-lesion-mask.",
  309. )
  310. parser.add_argument(
  311. "--require-contusion-mask",
  312. type=int,
  313. choices=[0, 1],
  314. default=0,
  315. help="If 1, fail when contusion masking is requested but no mask is found.",
  316. )
  317. parser.add_argument(
  318. "--contusion-dilation-radius",
  319. type=positive_or_zero_int,
  320. default=0,
  321. help=(
  322. "Binary morphology dilation radius in ANTsNetCT target-grid voxels. "
  323. "Use 0 to disable dilation."
  324. ),
  325. )
  326. parser.add_argument(
  327. "--contusion-dilation-shape",
  328. choices=["ball", "box", "cross", "annulus", "polygon"],
  329. default="ball",
  330. help="Structuring-element shape for contusion-mask dilation.",
  331. )
  332. parser.add_argument(
  333. "--write-prepared-contusion-mask",
  334. type=int,
  335. choices=[0, 1],
  336. default=0,
  337. help="If 1, save the resampled/dilated contusion mask beside the thickness output.",
  338. )
  339. parser.add_argument(
  340. "--overwrite",
  341. type=int,
  342. choices=[0, 1],
  343. default=0,
  344. help="If 1, overwrite existing primary post-processing outputs.",
  345. )
  346. parser.add_argument(
  347. "--dry-run",
  348. type=int,
  349. choices=[0, 1],
  350. default=0,
  351. help="If 1, print discovered inputs and planned outputs without loading ANTs.",
  352. )
  353. parser.add_argument(
  354. "--kk-iterations",
  355. type=positive_or_zero_int,
  356. default=45,
  357. help="KellyKapowski iterations passed as ants.kelly_kapowski(..., its=...).",
  358. )
  359. parser.add_argument(
  360. "--kk-r",
  361. type=float,
  362. default=0.025,
  363. help="KellyKapowski gradient-step parameter passed as r.",
  364. )
  365. parser.add_argument(
  366. "--kk-m",
  367. type=float,
  368. default=1.5,
  369. help="KellyKapowski smoothing/regularization parameter passed as m.",
  370. )
  371. return parser
  372. def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
  373. """Parse CLI arguments."""
  374. return build_arg_parser().parse_args(argv)
  375. def print_target_plan(targets: Sequence[AntsNetCtTarget], contusion_mask_path: Optional[str]) -> None:
  376. """Print the dry-run processing plan."""
  377. print("Discovered ANTsNetCT targets:")
  378. for target in targets:
  379. print(f" prefix: {target.prefix}")
  380. print(f" dseg: {target.dseg_path}")
  381. print(f" WM: {target.wm_probseg_path}")
  382. print(f" SGM: {target.sgm_probseg_path}")
  383. print(f" CGM: {target.cgm_probseg_path}")
  384. print(f" out: {target.output_path}")
  385. if contusion_mask_path is not None:
  386. print(f" contusion out: {target.contusion_output_path}")
  387. print(f" prepared mask: {target.prepared_contusion_mask_path}")
  388. def main(argv: Optional[Sequence[str]] = None) -> int:
  389. """Run the ANTsNetCT post-processing workflow."""
  390. args = parse_args(argv)
  391. sub = normalize_bids_label(args.sub, "sub-")
  392. ses = normalize_bids_label(args.ses, "ses-")
  393. anat_dir = antsnetct_anat_dir(args.derivatives_dir, args.antsnetct_dataset, sub, ses)
  394. prefixes = discover_prefixes(anat_dir, args.prefix)
  395. if not prefixes:
  396. print(
  397. f"ERROR: no ANTsNetCT dseg files found in {anat_dir} "
  398. "(expected '*_seg-antsnetct_dseg.nii.gz').",
  399. file=sys.stderr,
  400. )
  401. return 1
  402. targets = [
  403. target_for_prefix(
  404. anat_dir=anat_dir,
  405. prefix=prefix,
  406. output_desc=args.output_desc,
  407. contusion_output_desc=args.contusion_output_desc,
  408. prepared_mask_desc=DEFAULT_PREPARED_MASK_DESC,
  409. )
  410. for prefix in prefixes
  411. ]
  412. use_contusion_mask = bool_from_int(args.use_contusion_mask) or args.contusion_mask is not None
  413. contusion_patterns = tuple(args.contusion_mask_pattern or DEFAULT_CONTUSION_PATTERNS)
  414. contusion_mask_path: Optional[str] = None
  415. contusion_candidates: List[str] = []
  416. if use_contusion_mask:
  417. if args.contusion_mask is None and args.bids_dir is None:
  418. print(
  419. "ERROR: --bids-dir is required to discover a contusion mask "
  420. "when --use-contusion-mask 1 is set without --contusion-mask.",
  421. file=sys.stderr,
  422. )
  423. return 1
  424. contusion_mask_path, contusion_candidates = choose_contusion_mask(
  425. explicit_mask=args.contusion_mask,
  426. bids_dir=args.bids_dir,
  427. sub=sub,
  428. ses=ses,
  429. patterns=contusion_patterns,
  430. )
  431. if args.contusion_mask is not None and not os.path.exists(args.contusion_mask):
  432. print(f"ERROR: explicit contusion mask does not exist: {args.contusion_mask}", file=sys.stderr)
  433. return 1
  434. if contusion_mask_path is None:
  435. message = (
  436. f"No contusion mask found for {sub} {ses}. "
  437. "Provide --contusion-mask or --bids-dir with matching --contusion-mask-pattern."
  438. )
  439. if bool_from_int(args.require_contusion_mask):
  440. print(f"ERROR: {message}", file=sys.stderr)
  441. return 1
  442. print(f"WARNING: {message} Continuing without contusion subtraction.", file=sys.stderr)
  443. use_contusion_mask = False
  444. elif len(contusion_candidates) > 1:
  445. print(f"WARNING: found {len(contusion_candidates)} contusion-mask candidates; using first:")
  446. print(f" {contusion_mask_path}")
  447. if bool_from_int(args.dry_run):
  448. print(f"Subject/session: {sub} {ses}")
  449. print(f"ANTsNetCT anat dir: {anat_dir}")
  450. if use_contusion_mask:
  451. print(f"Contusion mask: {contusion_mask_path}")
  452. print(f"Contusion dilation radius: {args.contusion_dilation_radius}")
  453. print(f"Contusion dilation shape: {args.contusion_dilation_shape}")
  454. else:
  455. print("Contusion mask: disabled")
  456. print_target_plan(targets, contusion_mask_path if use_contusion_mask else None)
  457. return 0
  458. try:
  459. import ants
  460. except ImportError as exc:
  461. print(
  462. "ERROR: could not import ants. Run this script in an ANTsPy/ANTsNetCT environment.",
  463. file=sys.stderr,
  464. )
  465. print(f"Import error: {exc}", file=sys.stderr)
  466. return 1
  467. processed = 0
  468. skipped = 0
  469. failures = 0
  470. for target in targets:
  471. print(f"\n[{sub} {ses}] {target.prefix}")
  472. if not bool_from_int(args.overwrite) and os.path.exists(target.output_path):
  473. print(f"Skipping existing output: {target.output_path}")
  474. skipped += 1
  475. continue
  476. missing = missing_required_paths(target)
  477. if missing:
  478. print(f"ERROR: missing required files for prefix {target.prefix}:", file=sys.stderr)
  479. for path in missing:
  480. print(f" {path}", file=sys.stderr)
  481. failures += 1
  482. continue
  483. contusion_mask = None
  484. if use_contusion_mask and contusion_mask_path is not None:
  485. print(f"Using contusion mask: {contusion_mask_path}")
  486. print(
  487. "Preparing contusion mask "
  488. f"(dilation radius={args.contusion_dilation_radius}, shape={args.contusion_dilation_shape})"
  489. )
  490. reference = ants.image_read(target.dseg_path)
  491. contusion_mask = prepare_contusion_mask(
  492. ants=ants,
  493. mask_path=contusion_mask_path,
  494. reference_image=reference,
  495. dilation_radius=args.contusion_dilation_radius,
  496. dilation_shape=args.contusion_dilation_shape,
  497. )
  498. if bool_from_int(args.write_prepared_contusion_mask):
  499. ants.image_write(contusion_mask, filename=target.prepared_contusion_mask_path)
  500. print(f"Wrote prepared contusion mask: {target.prepared_contusion_mask_path}")
  501. try:
  502. kk = run_kelly_kapowski(
  503. ants=ants,
  504. target=target,
  505. contusion_mask=contusion_mask,
  506. kk_iterations=args.kk_iterations,
  507. kk_r=args.kk_r,
  508. kk_m=args.kk_m,
  509. )
  510. ants.image_write(kk, filename=target.output_path)
  511. print(f"Wrote primary thickness output: {target.output_path}")
  512. if contusion_mask is not None:
  513. ants.image_write(kk, filename=target.contusion_output_path)
  514. print(f"Wrote contusion-specific thickness output: {target.contusion_output_path}")
  515. processed += 1
  516. except Exception as exc: # noqa: BLE001 - report target-level failures in batch runs.
  517. failures += 1
  518. print(f"ERROR: failed while processing {target.prefix}: {exc}", file=sys.stderr)
  519. print(f"\nSummary: processed={processed}, skipped={skipped}, failures={failures}")
  520. return 1 if failures else 0
  521. if __name__ == "__main__":
  522. raise SystemExit(main())

antsnetct_postproc_thickness.py at commit 464b866, no license · at the source

Overview

Authors: Daniel Brennan1, Andrea L.C. Schneider1,2, Russell Taki Shinohara2,3,4, Ramon Diaz-Arrastia1, Philip A. Cook5, James C. Gee5, James J. Gugger1,6
ORCID iDs: Daniel Brennan
  1. University of Pennsylvania, Department of Neurology, Philadelphia, PA, United States
  2. University of Pennsylvania, Department of Biostatistics, Epidemiology, and Informatics, Philadelphia, PA, United States
  3. Penn Statistics in Imaging and Visualization Center, Department of Biostatistics, Epidemiology, and Informatics, University of Pennsylvania, Philadelphia, PA, United States
  4. Center For AI And Data Science For Integrated Diagnostics, United States
  5. University of Pennsylvania, Department of Radiology, Philadelphia, PA, United States
  6. University of Rochester, Department of Neurology, Rochester, NY, United States
Institutions: University of Pennsylvania (United States); University of Rochester Medicine (United States); University of Rochester (United States)
Journal: NeuroImage. Clinical, volume 50, article 104003
Dates: received 12 January 2026; accepted 5 May 2026; published online 6 May 2026; in print 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1016/j.nicl.2026.104003 · PMID 42114207 · PMCID PMC13191617 · OpenAlex W7160379009
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), traumatic brain injury (population)
Methods: Connectivity, Statistics, Smoothing, state filtering, decompositions, fMRI & imaging, Preprocessing
Keywords: Traumatic brain injury, Cortical thickness, Contusion, Lesion masking, Bias
MeSH: Brain Cortical Thickness*, Brain Injuries, Traumatic*, Cerebral Cortex*, Adult, Cross-Sectional Studies, Female, Humans, Image Processing, Computer-Assisted, Longitudinal Studies, Magnetic Resonance Imaging, Male, Middle Aged, Young Adult (* major topic)
Topic: Traumatic Brain Injury Research (Epidemiology, Medicine), according to OpenAlex
Citations: not cited yet (Europe PMC); 39 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.

dbrennan44/ANTsNetCT-Cortical-Thickness-with-Contusion-Post-Processing

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 464b866d6252abcd2562825594b60f7dae48b1ce, 10 July 2026
Languages: Python (1)
Size: 3 files, 1 script
Software Heritage: not archived
Found in: “Data availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: ANTs (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
2 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 1 script, each with its path and the digest of its content;
  • 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1016/j.nicl.2026.104003.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 7 authors, 5 keywords, 13 MeSH terms, 3 funders, 39 references.

Cite

This paper

Brennan, D., Schneider, A. L., Shinohara, R. T., Diaz-Arrastia, R., Cook, P. A., Gee, J. C., & Gugger, J. J. (2026). Contusions bias cortical thickness estimates after traumatic brain injury: A TRACK-TBI study. NeuroImage. Clinical, 50, 104003. https://doi.org/10.1016/j.nicl.2026.104003

BibTeX

@article{brennan2026contusions,
author = {Brennan, Daniel and Schneider, Andrea L.C. and Shinohara, Russell Taki and Diaz-Arrastia, Ramon and Cook, Philip A. and Gee, James C. and Gugger, James J.},
title = {{Contusions bias cortical thickness estimates after traumatic brain injury: A TRACK-TBI study}},
journal = {NeuroImage. Clinical},
year = {2026},
month = may,
volume = {50},
pages = {104003},
publisher = {Elsevier},
issn = {2213-1582},
doi = {10.1016/j.nicl.2026.104003},
url = {https://doi.org/10.1016/j.nicl.2026.104003},
pmid = {42114207},
pmcid = {PMC13191617}
}

RIS

TY - JOUR
AU - Brennan, Daniel
AU - Schneider, Andrea L.C.
AU - Shinohara, Russell Taki
AU - Diaz-Arrastia, Ramon
AU - Cook, Philip A.
AU - Gee, James C.
AU - Gugger, James J.
TI - Contusions bias cortical thickness estimates after traumatic brain injury: A TRACK-TBI study
T2 - NeuroImage. Clinical
J2 - Neuroimage Clin
PY - 2026
DA - 2026/05/06
VL - 50
SP - 104003
SN - 2213-1582
PB - Elsevier
DO - 10.1016/j.nicl.2026.104003
UR - https://doi.org/10.1016/j.nicl.2026.104003
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.nicl.2026.104003",
"type": "article-journal",
"title": "Contusions bias cortical thickness estimates after traumatic brain injury: A TRACK-TBI study",
"container-title": "NeuroImage. Clinical",
"author": [
{
"family": "Brennan",
"given": "Daniel"
},
{
"family": "Schneider",
"given": "Andrea L.C."
},
{
"family": "Shinohara",
"given": "Russell Taki"
},
{
"family": "Diaz-Arrastia",
"given": "Ramon"
},
{
"family": "Cook",
"given": "Philip A."
},
{
"family": "Gee",
"given": "James C."
},
{
"family": "Gugger",
"given": "James J."
}
],
"container-title-short": "Neuroimage Clin",
"volume": "50",
"page": "104003",
"DOI": "10.1016/j.nicl.2026.104003",
"PMID": "42114207",
"PMCID": "PMC13191617",
"ISSN": "2213-1582",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.nicl.2026.104003",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
6
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41598-026-55397-w [code]
Fast surface reconstruction of human brain MRI: benchmarking deep-learning based morphometry tools.
Journal: Scientific reports
In common: ANTs, structural MRI / diffusion, 2 references
[2] doi:10.3389/fonc.2026.1886395 [code]
Resources are associated with functional outcome and brain morphometry in childhood cancer survivors.
Journal: Frontiers in oncology
In common: ANTs, structural MRI / diffusion, 2 references
[3] doi:10.1038/s41398-026-04143-x [code]
Neighborhood opportunity, white matter, and cognition in a large pediatric neuroimaging study.
Journal: Translational psychiatry
In common: ANTs, structural MRI / diffusion, 2 references
[4] doi:10.1162/imag.a.1366 [code]
MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: ANTs, structural MRI / diffusion, 1 reference
[5] doi:10.1038/s41598-026-56778-x [code]
Chiari malformation type 1 is associated with a smaller fourth ventricle volume - a multi-cohort replication study.
Journal: Scientific reports
In common: ANTs, structural MRI / diffusion, 1 reference
[6] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: ANTs, 1 reference
[7] doi:10.1002/epi.70296 [code]
Fully automated three-dimensional deep learning-based magnetic resonance imaging segmentation of brain cavities in epilepsy surgery.
Journal: Epilepsia
In common: ANTs, structural MRI / diffusion, 1 reference
[8] doi:10.1162/imag.a.1352 [code]
Brain-age in ultra-low-field MRI: How well does it work?
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: ANTs, structural MRI / diffusion, 1 reference
[9] doi:10.3389/fnins.2026.1843319 [code]
Humanized APOE mouse brain volume increases over age irrespective of sex and APOE genotype: implications for translational validity to the human.
Journal: Frontiers in neuroscience
In common: ANTs, structural MRI / diffusion, 1 reference
[10] doi:10.1073/pnas.2604111123 [code]
Multiscale characterization of the human claustrum from histology to MRI.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: ANTs, structural MRI / diffusion, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.