Revisiting intensity normalization in brain [<sup>18</sup>F]FDG PET: What is reliable in autoimmune encephalitis?
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 · 190 lines · 6.5 KB · MIT
- import os
- import nibabel as nib
- import json
- import pydicom
- import pandas as pd
- import argparse
- import math
- def time_to_seconds(tstr):
- # DICOM time format HHMMSS.frac
- if '.' in tstr:
- tstr, frac = tstr.split('.')
- else:
- frac = '0'
- h = int(tstr[0:2])
- m = int(tstr[2:4])
- s = int(tstr[4:6])
- return h*3600 + m*60 + s + float("0."+frac)
- def find_first_dcmslice(dicom_dir, nifti_path):
- if nifti_path.endswith(".nii.gz"):
- series_uid = os.path.basename(nifti_path[:-7])
- elif nifti_path.endswith(".nii"):
- series_uid = os.path.basename(nifti_path[:-4])
- else:
- raise ValueError("Unsupported file extension. Only .nii and .nii.gz are supported.")
- for dirpath, dirnames, filenames in os.walk(dicom_dir):
- for filename in filenames:
- filepath = os.path.join(dirpath, filename)
- try:
- ds = pydicom.dcmread(filepath, stop_before_pixels=True)
- if ds.get("SeriesInstanceUID") == series_uid:
- return ds
- except Exception:
- continue
- return None
- def suv_conversion_parameters(ds):
- patient_weight_kg = ds.get("PatientWeight", None)
- if patient_weight_kg is None:
- raise ValueError("PatientWeight missing")
- patient_weight_g = patient_weight_kg * 1000
- # Radiopharmaceutical info sequence (usually only one)
- radsq = ds.RadiopharmaceuticalInformationSequence[0]
- injected_dose_Bq = radsq.get("RadionuclideTotalDose", None)
- if injected_dose_Bq is None:
- raise ValueError("Injected dose missing")
- half_life_s = radsq.get("RadionuclideHalfLife", None)
- if half_life_s is None:
- raise ValueError("Half-life missing")
- injection_time_str = radsq.get("RadiopharmaceuticalStartTime", None)
- acq_time_str = ds.get("AcquisitionTime", None)
- inj_sec = time_to_seconds(injection_time_str)
- acq_sec = time_to_seconds(acq_time_str)
- delta_t = acq_sec - inj_sec
- if delta_t < 0: # If acquisition is after midnight, adjust accordingly
- delta_t += 24*3600
- decay_factor = math.exp(-math.log(2) / half_life_s * delta_t)
- return patient_weight_g, injected_dose_Bq, decay_factor
- def identification(ds, info_dict):
- series_uid = ds.get("SeriesInstanceUID", None)
- name = ds.get("PatientName", None)
- patient_id = ds.get("PatientID", None)
- modality = ds.get("Modality", None)
- dob = ds.get("PatientBirthDate", None)
- sex = ds.get("PatientSex", None)
- dos = ds.get("StudyDate", None)
- weight = ds.get("PatientWeight", None)
- size = ds.get("PatientSize", None)
- injected_dose_Bq = None
- if "RadiopharmaceuticalInformationSequence" in ds:
- radsq = ds.RadiopharmaceuticalInformationSequence[0]
- injected_dose_Bq = radsq.get("RadionuclideTotalDose", None)
- recon_element = ds.get((0x0054, 0x1103), None)
- recon_method = recon_element.value if recon_element else None
- series_description = ds.get("SeriesDescription", None)
- # Update the passed dict with these key-value pairs
- info_dict.update({
- "SeriesUID": str(series_uid),
- "PatientName": str(name) if name else None,
- "PatientID": patient_id,
- "Modality": modality,
- "DateOfBirth": dob,
- "Sex": sex,
- "StudyDate": dos,
- "PatientWeight": weight,
- "PatientSize": size,
- "InjectedDose_Bq": injected_dose_Bq,
- "ReconstructionMethod": str(recon_method) if recon_method else None,
- "SeriesDescription": series_description
- })
- def compute_age(row):
- try:
- dob = pd.to_datetime(row['DateOfBirth'], format='%Y%m%d')
- study_date = pd.to_datetime(row['StudyDate'], format='%Y%m%d')
- age = (study_date - dob).days / 365.25
- return round(age, 1)
- except Exception:
- return None
- def convert_bqml_to_suv(bqml_dir, suv_dir, dicom_dir):
- parent_folder = os.path.abspath(bqml_dir).split(os.sep)[-2]
- info_list = []
- for filename in os.listdir(bqml_dir):
- if filename.endswith(".nii.gz"):
- source_path = os.path.join(bqml_dir, filename)
- json_path = source_path[:-7] + ".json"
- target_filename = filename[:-7] + ".nii"
- target_path = os.path.join(suv_dir, target_filename)
- elif filename.endswith(".nii"):
- source_path = os.path.join(bqml_dir, filename)
- json_path = source_path[:-4] + ".json"
- target_path = os.path.join(suv_dir, filename)
- else:
- continue
- try:
- with open(json_path, 'r') as f:
- meta = json.load(f)
- except FileNotFoundError:
- print(f" ⚠️ JSON metadata not found for {source_path}. Skipping.")
- continue
- units = meta.get("Units", "")
- if units != "BQML":
- print(f"⚠️ Skipping {source_path}: Data is not in BQML units — cannot apply SUV formula.")
- continue
- print(f"⚙️ Processing {source_path}...")
- img = nib.load(source_path)
- bqml_data = img.get_fdata()
- dcm_file = find_first_dcmslice(dicom_dir, source_path)
- if dcm_file is None:
- print(f" ⚠️ No matching DICOM slice found for {source_path}. Skipping.")
- continue
- try:
- weight, dose, decay = suv_conversion_parameters(dcm_file)
- suv_data = (bqml_data * weight) / (dose * decay)
- suv_img = nib.Nifti1Image(suv_data, img.affine, img.header)
- nib.save(suv_img, target_path)
- print(f" ✅ SUV conversion")
- info_dict = {}
- identification(dcm_file, info_dict=info_dict)
- info_list.append(info_dict)
- except ValueError as e:
- print(f" ❌ Skipping {source_path}: {e}")
- # Save the info as Excel
- if info_list:
- df = pd.DataFrame(info_list)
- df['Sex_binary'] = df['Sex'].map({'M': 0, 'F': 1})
- df['Age'] = df.apply(compute_age, axis=1)
- dfpath = os.path.join(parent_folder, "infos.xlsx")
- df.to_excel(dfpath, index=False)
- print(f"📄 Info saved to {dfpath}")
- def main():
- parser = argparse.ArgumentParser(description="Convert BQML NIfTI files to SUV using DICOM metadata.")
- parser.add_argument("bqml_dir", help="Directory containing BQML .nii.gz files")
- parser.add_argument("suv_dir", help="Output directory for SUV .nii files")
- parser.add_argument("dicom_dir", help="Directory containing DICOM files")
- args = parser.parse_args()
- convert_bqml_to_suv(args.bqml_dir, args.suv_dir, args.dicom_dir)
- if __name__ == "__main__":
- main()
bqml2suv.py at commit 984cc08, under MIT · at the source
Overview
- Department of Nuclear Medicine, Pitié-Salpêtrière Hospital, Assistance Publique-Hôpitaux de Paris (AP-HP), Sorbonne Université, Paris, France
- Department of Nuclear Medicine, Institut Curie, 92210, Saint-Cloud, France
- Laboratoire d'Imagerie Biomédicale, Sorbonne Université, INSERM, CNRS, Paris, France
- Department of Nuclear Medicine, Saint-Louis Hospital, Assistance Publique-Hôpitaux de Paris (AP-HP), Sorbonne Université, Paris, France
- Department of Neuro-Oncology, DMU Neurosciences, Pitié-Salpêtrière Hospital, AP-HP, Sorbonne Université. Paris Reference Centre on Paraneoplastic Neurological Syndromes and Autoimmune Encephalitis (Sorbonne Université, UMR S 1127, Inserm U 1127, CNRS UMR 7225, ICM-Paris Brain Institute, Paris, France
- FranceMeLiS Institute -UCBL-CNRS UMR 5284 - INSERM U1314, Université Claude Bernard Lyon 1 (French Reference Centre on Paraneoplastic Neurological Syndromes and Autoimmune Encephalitis, Hospices Civils de Lyon, Hôpital Neurologique, Bron, France
- AP-HP, Department of Neurology, Epilepsy Unit, Reference Center of rare epilepsies, ERN EPicare, Pitié-Salpêtrière University Hospital, and Paris Brain Institute (Inserm, CNRS, Sorbonne Université), Paris, France
- Department of Neurology, Pitié-Salpêtrière Hospital, AP-HP, Sorbonne Université, Paris, France
Abstract
Purpose: Data-driven intensity normalization has emerged as an alternative to proportional scaling (PS) and reference-region methods in brain [18F]FDG PET. However, no consensus exists for autoimmune encephalitis (AE), whose variable metabolic patterns and lack of a reliable disease-free reference region complicate normalization. We compared three methods; PS, iterative PS (iPS), and pons-based reference-region (RR) normalization, in patients with definite AE (n = 29, 42 ± 20 years) and healthy controls (HC; n = 53, 46 ± 15 y/
Methods: Three sets of normalized [18F]FDG PET images (2 MBq/
Results: PS and iPS revealed basal ganglia and mesiotemporal hypermetabolism in AE vs. HC that RR failed to detect (p < 0.05 FWE corrected). All methods identified cortical hypometabolism, slightly more extensive with RR. PS and iPS yielded broadly similar results. Follow-up showed that iPS and PS better captured initial abnormalities than RR.
Conclusion: PS and iterative PS may provide more robust normalization than RR in AE. As interest in neuroinflammatory disorders grows, standardized normalization protocols are needed to ensure consistency across studies.
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above.
rogeau/fdg-pet-intensitynorm-ae
984cc083dd369cf23a3917696e69f2906ddca4d5, 30 June 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
18 files
- bqml2suv.py, Python, 190 lines
- center_mass.m, MATLAB, 47 lines
- convert_format_unit.sh, Shell, 54 lines
- create_individual_masks.
m , MATLAB, 98 lines - extract_plot_rois.sh, Shell, 102 lines
- extract_rois.py, Python, 114 lines
- extract_rois_GM.py, Python, 130 lines
- iter_intensity_norm.m, MATLAB, 82 lines
- iter_smooth.m, MATLAB, 27 lines
- normalize_iterative_inte
nsity.sh , Shell, 38 lines - normalize_space_intensit
y.sh , Shell, 32 lines - plot_rois.py, Python, 68 lines
- smooth_iterative_intensi
ty.sh , Shell, 21 lines - spatial_norm.m, MATLAB, 59 lines
- std_intensity_norm.m, MATLAB, 158 lines
- std_smooth.m, MATLAB, 22 lines
- LICENSE, License, 21 lines
- README.md, Text, 61 lines
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;
- 16 scripts, each with its path and the digest of its content;
- no match between paragraphs and code yet;
- 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.
Data availability
The full processing pipeline is available as a unified workflow in a publicly accessible repository (https://
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
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, pages, dates, 10 authors, 5 keywords, 13 MeSH terms, 35 references.
Cite
This paper
Kuijper, F. M., Rogeau, A., Rostand, H., Picca, A., Birzu, C., Navarro, V., Cousyn, L., Davy, V., Psimaras, D., & Kas, A. (2026). Revisiting intensity normalization in brain [&
BibTeX
@article{kuijper2026revi
author = {Kuijper, Fiene Marie and Rogeau, Antoine and Rostand, Hélène and Picca, Alberto and Birzu, Cristina and Navarro, Vincent and Cousyn, Louis and Davy, Vincent and Psimaras, Dimitri and Kas, Aurélie},
title = {{Revisiting intensity normalization in brain [\&
journal = {NeuroImage. Clinical},
year = {2026},
month = jul,
volume = {51},
pages = {104037},
publisher = {Elsevier},
issn = {2213-1582},
doi = {10.1016/
url = {https://
pmid = {42485846},
pmcid = {PMC13396930}
}
RIS
TY - JOUR
AU - Kuijper, Fiene Marie
AU - Rogeau, Antoine
AU - Rostand, Hélène
AU - Picca, Alberto
AU - Birzu, Cristina
AU - Navarro, Vincent
AU - Cousyn, Louis
AU - Davy, Vincent
AU - Psimaras, Dimitri
AU - Kas, Aurélie
TI - Revisiting intensity normalization in brain [&
T2 - NeuroImage. Clinical
J2 - Neuroimage Clin
PY - 2026
DA - 2026/
VL - 51
SP - 104037
SN - 2213-1582
PB - Elsevier
DO - 10.1016/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1016/
"type": "article-journal",
"title": "Revisiting intensity normalization in brain [&
"container-title": "NeuroImage. Clinical",
"author": [
{
"family": "Kuijper",
"given": "Fiene Marie"
},
{
"family": "Rogeau",
"given": "Antoine"
},
{
"family": "Rostand",
"given": "Hélène"
},
{
"family": "Picca",
"given": "Alberto"
},
{
"family": "Birzu",
"given": "Cristina"
},
{
"family": "Navarro",
"given": "Vincent"
},
{
"family": "Cousyn",
"given": "Louis"
},
{
"family": "Davy",
"given": "Vincent"
},
{
"family": "Psimaras",
"given": "Dimitri"
},
{
"family": "Kas",
"given": "Aurélie"
}
],
"container-title-short":
"volume": "51",
"page": "104037",
"DOI": "10.1016/
"PMID": "42485846",
"PMCID": "PMC13396930",
"ISSN": "2213-1582",
"publisher": "Elsevier",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
19
]
]
}
}
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.1002/hbm.70602 [code]
- Neuroimaging Correlates of Post-Stroke Pain After Ischemic Stroke: Secondary Analysis of the INSPiRE-TMS Trial.Journal: Human brain mappingIn common: pydicom, SPM, Image Processing Toolbox, 5 other tools, other condition
- [2] doi:10.1038/s41586-026-10631-3 [code]
- A prognostic human brain network for diffuse midline glioma.Journal: NatureIn common: pydicom, SPM, Image Processing Toolbox, 5 other tools, other condition
- [3] doi:10.1111/ene.70678 [code]
- Who Falls After a Stroke? Evidence From a Prospective Stroke Cohort.Journal: European journal of neurologyIn common: pydicom, SPM, Image Processing Toolbox, 5 other tools
- [4] doi:10.1016/j.celrep.2026.117404 [code]
- Action and rest tremor map to distinct networks within the primary motor cortex.Journal: Cell reportsIn common: pydicom, SPM, Image Processing Toolbox, 5 other tools
- [5] doi:10.1002/ana.78206 [code]
- Multimodal Image Guidance in Subthalamic Deep Brain Stimulation for Parkinson's Disease.Journal: Annals of neurologyIn common: pydicom, SPM, Image Processing Toolbox, 5 other tools
- [6] doi:10.1162/imag.a.1262 [code]
- Frame-wise multi-echo distortion correction for superior functional MRI.Journal: Imaging neuroscience (Cambridge, Mass.)In common: pydicom, SPM, Image Processing Toolbox, 5 other tools
- [7] doi:10.1038/s41597-026-07184-5 [code]
- A Multiple Sclerosis MRI Dataset with Tri-Mask Annotations for Lesion Segmentation.Journal: Scientific dataIn common: dcm2niix, pydicom, NiBabel, 4 other tools
- [8] doi:10.1038/s41598-026-54528-7 [code]
- Enhanced sensorimotor cortex responsiveness to nonplegic hand stimulation and motor network assembly during recovery after spinal cord injury in primates.Journal: Scientific reportsIn common: pydicom, SPM, Image Processing Toolbox, 4 other tools, other condition
- [9] doi:10.1038/s41597-026-07676-4 [code]
- A neuroimaging dataset combining movie-watching, eye-tracking, sensorimotor mapping, and cognitive tasks.Journal: Scientific dataIn common: dcm2niix, SPM, NiBabel, 4 other tools
- [10] doi:10.1038/s41467-026-71918-7 [code]
- Developmental disinhibition gates language lateralization in childhood.Journal: Nature communicationsIn common: dcm2niix, SPM, NiBabel, 4 other tools
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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 16 scripts, and 0 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:4d96d33293596fcd…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
