OSCR

Transient Elevation and Subsequent Normalization of Brain Age Gap During the Postpartum Period.

Code ↔ Paper

3 matches 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 3 matches
  1. [1] § Methods › Brain Age Prediction Model Development ↔ postpartum_brain_age_model.py, lines 158–280 · score 0.80 · healthy women, 22–36, Transfer learning, brain age predictions, freezing, convolutional
  2. [2] § Methods › Brain Age Prediction Model Development ↔ postpartum_brain_age_model.py, lines 1–25 · score 0.68 · attention module, convolutional feature, probability maps, brain age prediction, model, channel
  3. [3] § Methods › Brain Age Prediction Model Development ↔ postpartum_brain_age_model.py, lines 287–309 · score 0.58 · bias corrected, chronological age, Predicted brain age, errors

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 · 340 lines · 13 KB · MIT · 3 matches

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Postpartum Brain Age Prediction Model
  5. =====================================
  6. Conceptual PyTorch implementation of the brain age prediction model described in:
  7. "A biphasic brain aging trajectory during the postpartum period: Longitudinal evidence for initial advancement and subsequent normalization"
  8. The model:
  9. - Uses three tissue probability maps (grey matter, white matter, CSF) as input channels
  10. - Applies a channel-wise attention module (Squeeze-and-Excitation style)
  11. - Extracts 3D convolutional features
  12. - Predicts brain age via a fully connected regression head
  13. - Is designed for pre-training on HCP women (HCP-YA + < 40y HCP-A) and fine-tuning on
  14. postpartum and nulliparous control women
  15. """
  16. from dataclasses import dataclass
  17. from typing import Optional
  18. import torch
  19. import torch.nn as nn
  20. import torch.nn.functional as F
  21. # -------------------------------------------------------------------------
  22. # Configuration dataclasses
  23. # -------------------------------------------------------------------------
  24. @dataclass
  25. class BrainAgeModelConfig:
  26. """Configuration for the 3D brain age prediction network."""
  27. in_channels: int = 3 # GM / WM / CSF tissue probability maps
  28. num_filters: tuple = (32, 64, 128, 256)
  29. se_reduction: int = 4 # Reduction ratio for Squeeze-and-Excitation
  30. dropout_prob: float = 0.3
  31. use_channel_attention: bool = True
  32. n_additional_features: int = 0 # e.g., could be site or health covariates; *sex is NOT used*
  33. fc_dims: tuple = (256, 128, 64) # Fully-connected layer sizes
  34. @dataclass
  35. class TransferLearningConfig:
  36. """Configuration for transfer learning / fine-tuning strategy."""
  37. freeze_backbone: bool = True # Freeze 3D conv feature extractor
  38. train_regressor_only: bool = True # Train only FC regression head
  39. learning_rate_backbone: float = 1e-4
  40. learning_rate_regressor: float = 1e-3
  41. # -------------------------------------------------------------------------
  42. # Core building blocks
  43. # -------------------------------------------------------------------------
  44. class ChannelSE3D(nn.Module):
  45. """
  46. Channel-wise Squeeze-and-Excitation module for 3D feature maps.
  47. Intended to operate on the tissue-channel dimension of the input:
  48. (B, C, D, H, W) where C = 3 for GM / WM / CSF probability maps.
  49. """
  50. def __init__(self, channels: int, reduction: int = 4):
  51. super().__init__()
  52. if channels <= reduction:
  53. reduction = max(1, channels // 2) or 1
  54. self.avg_pool = nn.AdaptiveAvgPool3d(1)
  55. self.fc = nn.Sequential(
  56. nn.Linear(channels, channels // reduction, bias=True),
  57. nn.ReLU(inplace=True),
  58. nn.Linear(channels // reduction, channels, bias=True),
  59. nn.Sigmoid(),
  60. )
  61. def forward(self, x: torch.Tensor) -> torch.Tensor:
  62. b, c, _, _, _ = x.shape
  63. # Global average pooling over spatial dimensions
  64. y = self.avg_pool(x).view(b, c)
  65. # Channel-wise excitation weights
  66. y = self.fc(y).view(b, c, 1, 1, 1)
  67. return x * y
  68. class ConvBlock3D(nn.Module):
  69. """3D Conv -> BatchNorm -> ReLU -> (optional) MaxPool."""
  70. def __init__(self, in_ch: int, out_ch: int, use_pool: bool = True):
  71. super().__init__()
  72. self.conv = nn.Conv3d(in_ch, out_ch, kernel_size=3, padding=1, bias=False)
  73. self.bn = nn.BatchNorm3d(out_ch)
  74. self.relu = nn.ReLU(inplace=True)
  75. self.pool = nn.MaxPool3d(kernel_size=2, stride=2) if use_pool else None
  76. def forward(self, x: torch.Tensor) -> torch.Tensor:
  77. x = self.conv(x)
  78. x = self.bn(x)
  79. x = self.relu(x)
  80. if self.pool is not None:
  81. x = self.pool(x)
  82. return x
  83. class BrainAgeBackbone3D(nn.Module):
  84. """
  85. 3D convolutional feature extractor.
  86. Stacks ConvBlock3D layers with progressively increasing filters:
  87. 32 -> 64 -> 128 -> 256, followed by global average pooling to obtain
  88. a compact feature representation.
  89. """
  90. def __init__(self, in_channels: int, num_filters: tuple):
  91. super().__init__()
  92. assert len(num_filters) >= 2, "num_filters should have at least two elements."
  93. layers = []
  94. prev_ch = in_channels
  95. for i, f in enumerate(num_filters):
  96. use_pool = True # pool after every block; can be adjusted if desired
  97. layers.append(ConvBlock3D(prev_ch, f, use_pool=use_pool))
  98. prev_ch = f
  99. self.conv_blocks = nn.Sequential(*layers)
  100. self.global_pool = nn.AdaptiveAvgPool3d(1)
  101. self.out_channels = num_filters[-1]
  102. def forward(self, x: torch.Tensor) -> torch.Tensor:
  103. x = self.conv_blocks(x)
  104. x = self.global_pool(x) # (B, C, 1, 1, 1)
  105. x = x.view(x.size(0), -1) # (B, C)
  106. return x
  107. class RegressionHead(nn.Module):
  108. """Fully connected regression module for brain age prediction."""
  109. def __init__(self, in_features: int, fc_dims: tuple, dropout_prob: float = 0.3):
  110. super().__init__()
  111. layers = []
  112. prev = in_features
  113. for i, h in enumerate(fc_dims):
  114. layers.append(nn.Linear(prev, h))
  115. layers.append(nn.ReLU(inplace=True))
  116. if dropout_prob > 0:
  117. layers.append(nn.Dropout(dropout_prob))
  118. prev = h
  119. # Final scalar output: predicted brain age
  120. layers.append(nn.Linear(prev, 1))
  121. self.net = nn.Sequential(*layers)
  122. def forward(self, x: torch.Tensor) -> torch.Tensor:
  123. return self.net(x)
  124. # -------------------------------------------------------------------------
  125. # Full model
  126. # -------------------------------------------------------------------------
  127. class PostpartumBrainAgeModel(nn.Module):
  128. """
  129. 3D multi-channel brain age prediction model for postpartum and control women.
  130. - Input: three tissue probability maps (GM, WM, CSF) preprocessed with CAT12
  131. and normalized to MNI space (1.0mm^3, 157 x 189 x 156 voxels, or equivalent).
  132. - Channel-wise SE attention emphasizes tissue-specific information.
  133. - 3D CNN backbone extracts compact brain features.
  134. - Regression head outputs predicted brain age (in years).
  135. - Designed for:
  136. * Pre-training on 639 healthy women (HCP-YA 22–36y, HCP-A 36–39y)
  137. * Fine-tuning on postpartum and nulliparous control women using minimal
  138. transfer learning (regression head only; backbone frozen).
  139. NOTE: The model is *sex-specific* (women only) and **does not use sex as an input
  140. covariate**. If you need additional covariates (e.g., site), pass them via
  141. `n_additional_features` and the `additional_features` argument in `forward`.
  142. """
  143. def __init__(self, config: Optional[BrainAgeModelConfig] = None):
  144. super().__init__()
  145. if config is None:
  146. config = BrainAgeModelConfig()
  147. self.config = config
  148. # Optional channel-wise attention on GM/WM/CSF input channels
  149. self.use_channel_attention = config.use_channel_attention
  150. if self.use_channel_attention:
  151. self.channel_attention = ChannelSE3D(
  152. channels=config.in_channels,
  153. reduction=config.se_reduction,
  154. )
  155. else:
  156. self.channel_attention = nn.Identity()
  157. # 3D convolutional backbone
  158. self.backbone = BrainAgeBackbone3D(
  159. in_channels=config.in_channels,
  160. num_filters=config.num_filters,
  161. )
  162. # Regression head (optionally includes non-imaging covariates, NOT sex)
  163. in_features = self.backbone.out_channels
  164. if config.n_additional_features > 0:
  165. in_features += config.n_additional_features
  166. self.regressor = RegressionHead(
  167. in_features=in_features,
  168. fc_dims=config.fc_dims,
  169. dropout_prob=config.dropout_prob,
  170. )
  171. # ------------------------------------------------------------------
  172. # Transfer learning utilities
  173. # ------------------------------------------------------------------
  174. def freeze_backbone(self) -> None:
  175. """Freeze convolutional backbone parameters."""
  176. for p in self.backbone.parameters():
  177. p.requires_grad = False
  178. def unfreeze_backbone(self) -> None:
  179. """Unfreeze convolutional backbone parameters."""
  180. for p in self.backbone.parameters():
  181. p.requires_grad = True
  182. def parameters_for_transfer_learning(self, tl_config: TransferLearningConfig):
  183. """
  184. Convenience method to obtain parameter groups for optimizer
  185. according to the specified transfer-learning strategy.
  186. """
  187. if tl_config.freeze_backbone and tl_config.train_regressor_only:
  188. self.freeze_backbone()
  189. return [
  190. {"params": self.regressor.parameters(), "lr": tl_config.learning_rate_regressor}
  191. ]
  192. else:
  193. # Fine-tune both backbone and regressor with potentially different LRs
  194. self.unfreeze_backbone()
  195. return [
  196. {"params": self.backbone.parameters(), "lr": tl_config.learning_rate_backbone},
  197. {"params": self.regressor.parameters(), "lr": tl_config.learning_rate_regressor},
  198. ]
  199. # ------------------------------------------------------------------
  200. # Forward
  201. # ------------------------------------------------------------------
  202. def forward(
  203. self,
  204. x: torch.Tensor,
  205. additional_features: Optional[torch.Tensor] = None,
  206. ) -> torch.Tensor:
  207. """
  208. Forward pass.
  209. Args:
  210. x: Tensor of shape (B, 3, D, H, W) containing GM/WM/CSF maps.
  211. additional_features: Optional tensor of shape (B, n_additional_features)
  212. for non-imaging covariates (e.g., site). Sex must **not** be included.
  213. Returns:
  214. Tensor of shape (B, 1): predicted brain age (in years).
  215. """
  216. # Channel-wise attention on tissue maps
  217. x = self.channel_attention(x)
  218. # Extract imaging features
  219. feat = self.backbone(x) # (B, C)
  220. # Concatenate additional covariates if provided
  221. if self.config.n_additional_features > 0:
  222. if additional_features is None:
  223. raise ValueError(
  224. f"Model configured with n_additional_features={self.config.n_additional_features}, "
  225. "but no additional_features were provided to forward()."
  226. )
  227. if additional_features.ndim != 2:
  228. raise ValueError("additional_features must be a 2D tensor of shape (B, F).")
  229. feat = torch.cat([feat, additional_features], dim=1)
  230. # Regress to brain age
  231. age_pred = self.regressor(feat)
  232. return age_pred
  233. # -------------------------------------------------------------------------
  234. # Helper: bias correction (conceptual)
  235. # -------------------------------------------------------------------------
  236. def apply_linear_bias_correction(
  237. predicted_age: torch.Tensor,
  238. chronological_age: torch.Tensor,
  239. slope: float,
  240. intercept: float,
  241. ) -> torch.Tensor:
  242. """
  243. Apply linear bias correction to predicted brain age.
  244. In practice, `slope` and `intercept` should be estimated from a validation
  245. set by regressing predicted age on chronological age:
  246. predicted_age = slope * chronological_age + intercept
  247. The bias-corrected age is then:
  248. corrected = predicted_age - (slope * chronological_age + intercept) + chronological_age
  249. This function expects already-estimated slope and intercept.
  250. """
  251. if predicted_age.shape != chronological_age.shape:
  252. raise ValueError("predicted_age and chronological_age must have the same shape.")
  253. bias = slope * chronological_age + intercept
  254. corrected = predicted_age - bias + chronological_age
  255. return corrected
  256. def compute_bag(
  257. bias_corrected_predicted_age: torch.Tensor,
  258. chronological_age: torch.Tensor,
  259. ) -> torch.Tensor:
  260. """
  261. Compute Brain Age Gap (BAG) = bias-corrected predicted age - chronological age.
  262. """
  263. if bias_corrected_predicted_age.shape != chronological_age.shape:
  264. raise ValueError("bias_corrected_predicted_age and chronological_age must have the same shape.")
  265. return bias_corrected_predicted_age - chronological_age
  266. # -------------------------------------------------------------------------
  267. # Quick self-test
  268. # -------------------------------------------------------------------------
  269. if __name__ == "__main__":
  270. # Minimal smoke test to verify shapes and forward pass
  271. cfg = BrainAgeModelConfig(
  272. in_channels=3,
  273. num_filters=(32, 64, 128, 256),
  274. n_additional_features=0, # IMPORTANT: sex is NOT used; keep this 0 unless you add other covariates
  275. )
  276. model = PostpartumBrainAgeModel(cfg)
  277. # Example input: batch of 2 subjects, 3 tissue maps, downsampled spatial dims
  278. x = torch.randn(2, 3, 64, 64, 64)
  279. y = model(x)
  280. print("Output shape:", y.shape) # Expected: torch.Size([2, 1])

postpartum_brain_age_model.py at commit d0265ae, under MIT · at the source

Overview

Authors: Chang‐hyun Park1, Yunjin Bak2, Sanghoon Han2, Seung‐Koo Lee3, Na‐Young Shin3,4
  1. Research Institute for AI Mind, Yonsei University, Seoul, Republic of Korea
  2. Department of Psychology, Yonsei University, Seoul, Republic of Korea
  3. Department of Radiology and Research Institute of Radiological Science and Center for Clinical Imaging Data Science, Yonsei University College of Medicine, Seoul, Republic of Korea
  4. Yonsei Institute for Digital Health, Yonsei University, Seoul, Republic of Korea
Institutions: Yonsei University (South Korea)
Journal: Human brain mapping, volume 47, issue 13, article e70608
Dates: received 29 January 2026; accepted 6 July 2026; published online 31 August 2026; in print September 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1002/hbm.70608 · PMID 42675020 · PMCID PMC13529808 · OpenAlex W7204855580
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), cognitive (subfield)
Methods: Connectivity, Statistics, Machine learning
Keywords: brain age gap, brain age prediction, maternal brain, postpartum, Shapley additive explanations
MeSH: Aging*, Brain*, Postpartum Period*, Adult, Female, Humans, Longitudinal Studies, Machine Learning, Magnetic Resonance Imaging, Memory, Short-Term, Young Adult (* major topic)
Topic: Neuroendocrine regulation and behavior (Social Psychology, Psychology), according to OpenAlex
Funding: National Research Foundation of Korea (RS-2025-00562956, RS‐2025‐00562956, NRF‐2014R1A1A2055116, RS-2023-00240457, NRF-2014R1A1A2055116, RS‐2023‐00240457)
Citations: not cited yet (Europe PMC); 40 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 3 matches between paragraphs and lines of code.

chang-hyun.park/postpartum-brain-age-prediction

License: MIT
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: d0265ae061e3e4ed6f0aa4ed7b9f0435f1b4962f, 10 November 2025
Languages: Python (1)
Size: 7 files, 1 script
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, license file, environment (requirements.txt)
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: PyTorch (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
3 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;
  • 3 matches 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.1002/hbm.70608.

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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 5 keywords, 11 MeSH terms, 1 funder, 36 references.

Cite

This paper

Park, C., Bak, Y., Han, S., Lee, S., & Shin, N. (2026). Transient Elevation and Subsequent Normalization of Brain Age Gap During the Postpartum Period. Human brain mapping, 47(13), e70608. https://doi.org/10.1002/hbm.70608

BibTeX

@article{park2026transient,
author = {Park, Chang‐hyun and Bak, Yunjin and Han, Sanghoon and Lee, Seung‐Koo and Shin, Na‐Young},
title = {{Transient Elevation and Subsequent Normalization of Brain Age Gap During the Postpartum Period}},
journal = {Human brain mapping},
year = {2026},
month = sep,
volume = {47},
number = {13},
pages = {e70608},
publisher = {Wiley},
issn = {1065-9471},
doi = {10.1002/hbm.70608},
url = {https://doi.org/10.1002/hbm.70608},
pmid = {42675020},
pmcid = {PMC13529808}
}

RIS

TY - JOUR
AU - Park, Chang‐hyun
AU - Bak, Yunjin
AU - Han, Sanghoon
AU - Lee, Seung‐Koo
AU - Shin, Na‐Young
TI - Transient Elevation and Subsequent Normalization of Brain Age Gap During the Postpartum Period
T2 - Human brain mapping
J2 - Hum Brain Mapp
PY - 2026
DA - 2026/09/01
VL - 47
IS - 13
SP - e70608
SN - 1065-9471
PB - Wiley
DO - 10.1002/hbm.70608
UR - https://doi.org/10.1002/hbm.70608
LA - en
ER -

CSL-JSON

{
"id": "10.1002/hbm.70608",
"type": "article-journal",
"title": "Transient Elevation and Subsequent Normalization of Brain Age Gap During the Postpartum Period",
"container-title": "Human brain mapping",
"author": [
{
"family": "Park",
"given": "Chang‐hyun"
},
{
"family": "Bak",
"given": "Yunjin"
},
{
"family": "Han",
"given": "Sanghoon"
},
{
"family": "Lee",
"given": "Seung‐Koo"
},
{
"family": "Shin",
"given": "Na‐Young"
}
],
"container-title-short": "Hum Brain Mapp",
"volume": "47",
"issue": "13",
"page": "e70608",
"DOI": "10.1002/hbm.70608",
"PMID": "42675020",
"PMCID": "PMC13529808",
"ISSN": "1065-9471",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/hbm.70608",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
1
]
]
}
}

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.21203/rs.3.rs-9235100/v1
Structural Brain Changes Across the Female Lifespan: Puberty, Pregnancy, and Menopause Show Shared and Distinct Trajectories
Journal: Research Square (preprint)
In common: 6 references
[2] doi:10.1007/s00737-026-01707-0
Maternal depression symptoms as a context for longitudinal changes in the perinatal brain and infant emotion outcomes.
Journal: Archives of women's mental health
In common: 5 references
[3] doi:10.1002/hbm.70425 [code]
Brain Age Estimation on T2-FLAIR Scans for Application to Multiple Sclerosis.
Journal: Human brain mapping
In common: PyTorch, structural MRI / diffusion, 5 references
[4] 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: PyTorch, structural MRI / diffusion, 4 references
[5] doi:10.1038/s41586-026-10509-4
Dopamine drives persistent remodelling of the maternal brain.
Journal: Nature
In common: 4 references
[6] doi:10.1038/s41467-026-74274-8 [code]
Regional sex differences in human cortical anatomy vary in their morphometric bases and overlap with sex chromosomal and gonadal influences.
Journal: Nature communications
In common: structural MRI / diffusion, 5 references
[7] doi:10.7554/elife.109461 [code]
The effect of physical activity on brain structure and cognitive function in the population-based cohort of LIFE-Adult Study.
Journal: eLife
In common: structural MRI / diffusion, cognitive, 3 references
[8] doi:10.1038/s43856-026-01722-3 [code]
Local and global patterns support medical imaging as a biomarker of ageing.
Journal: Communications medicine
In common: PyTorch, 3 references
[9] doi:10.1002/hbm.70627 [code]
Investigating the Contribution of Molecular-Enriched Functional Connectivity to Brain-Age Analysis.
Journal: Human brain mapping
In common: structural MRI / diffusion, 3 references
[10] doi:10.1371/journal.pmed.1004860 [code]
Brain aging patterns among nine neurological disorders: A case-control study.
Journal: PLoS medicine
In common: structural MRI / diffusion, 3 references

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.