SPARROW: subtyping Parkinson's disease with agentic reasoning and robust omics workflow.
The 15 matches
- [1] § Methods › Model training ↔ sparrow.py, lines 353–437 · score 0.90 · device train batch, AdamW, training epochs, reward weights, prompt length, fine tune
- [2] § Methods › Evaluation ↔ benchmark_3d.py, lines 1–27 · score 0.79 · ResNet, Swin UNETR, subtype classification, deep learning, benchmarked, baseline
- [3] § Methods › Reinforcement learning for fine-tuning ↔ sparrow.py, lines 353–437 · score 0.74 · reward function, format reward, accuracy reward, fine tune, Policy, GRPO
- [4] § Methods › Diagnostic CoT reasoner ↔ utils.py, lines 41–87 · score 0.71 · R1 distilled, DeepSeek, MedGemma, OpenBio, Llama, instruction
- [5] § Results › Imaging-only baselines ↔ benchmark_3d.py, lines 1–27 · score 0.66 · ResNet, Swin UNETR, fair, benchmarked, pretrained, split
- [6] § Methods › Evaluation ↔ tricoat_benchmark/models_tricoat.py, lines 7–151 · score 0.66 · co attention, MLP, branches, tri, joint, fuse
- [7] § Results › LLM reasoners and reinforcement learning ↔ utils.py, lines 41–87 · score 0.65 · OpenBio Llama3, DeepSeek Qwen, MedGemma, LLMs, training, models
- [8] § Methods › Diagnostic CoT reasoner ↔ results.py, lines 265–331 · score 0.59 · R1 distilled, DeepSeek, Llama, instruction, SPARROW, diagnostic
- [9] § Results › LLM reasoning for interpretability ↔ orchestrator.py, lines 339–393 · score 0.59 · specialist tools, OntoGPT, single modality, MRI, LLM, clinical
- [10] § Methods › Prompt design ↔ utils.py, lines 210–269 · score 0.58 · hot encoded, utilities, preprocessing, RAG, pipeline, demographic
- [11] § Methods › Prompt design ↔ dataset.py, lines 167–198 · score 0.58 · nearest neighbors pipeline, preprocessing, RAG, training, diagnosis, patient
- [12] § Methods › Accuracy reward ↔ utils.py, lines 131–145 · score 0.54 · ground truth, accuracy reward, matches, predicted
- [13] § Results › Model performance and comparative evaluation ↔ benchmark_ml.py, lines 196–249 · score 0.53 · Random Forest, XGBoost, RF, SVM, encoders, transformer
- [14] § Methods › Evaluation ↔ benchmark_3d.py, lines 290–309 · score 0.52 · macro F1 score, balanced accuracy, BAC, classes, metrics, predictive
- [15] § Results › Dataset ↔ dataset.py, lines 18–74 · score 0.51 · patient IDs, healthy controls, fast, moderate
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 · 301 lines · 11 KB · no license · 4 matches
- from __future__ import annotations
- from typing import List, Dict, Any, Optional
- import torch, subprocess, re, json, os
- from transformers import AutoTokenizer, AutoModelForCausalLM
- from sklearn.impute import SimpleImputer
- from sklearn.preprocessing import StandardScaler, OneHotEncoder, FunctionTransformer
- from sklearn.compose import ColumnTransformer
- from sklearn.pipeline import Pipeline
- import numpy as np
- import pandas as pd
- def print_gpu_mem():
- print("GPU memory usage:")
- for i in range(torch.cuda.device_count()):
- alloc = torch.cuda.memory_allocated(i) / (1024**2)
- reserved = torch.cuda.memory_reserved(i) / (1024**2)
- print(f" GPU {i}: allocated={alloc:.2f}MB reserved={reserved:.2f}MB")
- def select_gpu(num_gpus=1,verbose=False):
- # Run the nvidia-smi command to get GPU information
- result = subprocess.run(['nvidia-smi', '--query-gpu=index,memory.used', '--format=csv,noheader'], capture_output=True, text=True)
- print(result.stdout)
- # Parse the output to get GPU index and memory usage
- gpu_info = result.stdout.strip().split('\n')
- gpu_info = [info.split(',') for info in gpu_info]
- gpu_info = [(info[0], int(info[1].split()[0])) for info in gpu_info]
- # Sort the GPU info based on memory usage
- sorted_gpu_info = sorted(gpu_info, key=lambda x: x[1])
- if verbose:
- # Print the GPU info with least memory usage
- for gpu in sorted_gpu_info:
- print(f"GPU {gpu[0]}: Memory Usage {gpu[1]} MB")
- # Select the first num_gpus GPUs with least memory usage
- selected_gpus = [gpu[0] for gpu in sorted_gpu_info[:num_gpus]]
- return selected_gpus
- def load_model(model_name, eval_mode=True):
- if model_name == "deepseek-Llama-70B":
- print("Loading DeepSeek-R1-Distill-Llama-70B model...")
- model_name = "deepseek-ai/DeepSeek-R1-Distill-Llama-70B"
- elif model_name == "Llama-3.2-11B":
- print("Loading meta-llama/Llama-3.2-11B-Vision-Instruct model...")
- model_name = "meta-llama/Llama-3.2-11B-Vision-Instruct"
- elif model_name == "Llama-3.3-70B":
- print("Loading meta-llama/Llama-3.3-70B-Instruct model...")
- model_name = "meta-llama/Llama-3.3-70B-Instruct"
- elif model_name == "deepseek-Qwen-32B":
- print("Loading DeepSeek-R1-Distill-Qwen-32B model...")
- model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
- elif model_name == "deepseek-Qwen-7B":
- print("Loading DeepSeek-R1-Distill-Qwen-7B model...")
- model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
- elif model_name == "Qwen3-30B-A3B":
- print("Loading Qwen/Qwen3-30B-A3B model...")
- model_name = "Qwen/Qwen3-30B-A3B"
- elif model_name == "ibm-fms/Bamba-9B-v1":
- print("Loading ibm-fms/Bamba-9B-v1 model...")
- model_name = "ibm-fms/Bamba-9B-v1"
- elif model_name == "openbiollm-llama3-70B":
- print("Loading aaditya/OpenBioLLM-Llama3-70B model...")
- model_name = "aaditya/OpenBioLLM-Llama3-70B"
- elif model_name == "deepseek-Qwen3-8B":
- print("Loading DeepSeek-R1-0528-Qwen3-8B...")
- model_name = "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B"
- elif model_name == "medgemma-4B-it":
- print("Loading medgemma-4B-it model...")
- model_name = "google/medgemma-4b-it"
- elif model_name == "medgemma-27B":
- print("Loading medgemma-27B model...")
- model_name = "google/medgemma-27b-text-it"
- elif model_name == "deepseek-Qwen2.5-1.5B":
- print("Loading DeepSeek-R1-Distill-Qwen2.5-1.5B model...")
- model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
- else:
- raise ValueError(f"Model {model_name} not recognized. Please choose a valid model.")
- tokenizer = AutoTokenizer.from_pretrained(model_name)
- model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")
- print_gpu_mem()
- model.eval() if eval_mode else model.train() # Set model to evaluation mode
- print('#'*40)
- print('Success model loaded!')
- return tokenizer, model
- # --------------------------------------------------------------------------- #
- # Dataset and Dataloader functions
- # --------------------------------------------------------------------------- #
- def json_collate_fn(batch, inference_mode=False):
- return [item for item in batch]
- def json_collate_fn_inference(batch):
- # batch is a list of items returned by __getitem__
- # each item is e.g. {"patient_data": {...}, "patient_info": {...}}
- # so we just re-aggregate them into lists (or leave them as-is)
- return {
- "patient_data": [item["patient_data"] for item in batch],
- "patient_info": [item["patient_info"] for item in batch],
- }
- def extract_new_context(text: str) -> str:
- """
- Extracts the content inside quotes following the 'new_context' key.
- Args:
- text (str): The long string to search within.
- Returns:
- str: The extracted context string, or an empty string if not found.
- """
- match = re.search(r'"new_context"\s*:\s*"([^"]+)"', text)
- if match:
- return match.group(1)
- else:
- return ""
- # --------------------------------------------------------------------------- #
- # Reward functions – For RL training
- # --------------------------------------------------------------------------- #
- FORMAT_PATTERN = re.compile(r"^<think>.*?</think><answer>.*?</answer>$", re.S)
- def format_reward_func(completions: List[str], **_) -> List[float]:
- """+1 if the answer respects the <think></think><answer></answer> format."""
- return [1.0 if FORMAT_PATTERN.match(c) else 0.0 for c in completions]
- def accuracy_reward_func(
- completions: List[str],
- ground_truth: List[str],
- **_
- ) -> List[float]:
- """
- +1 if the content boxed in \\boxed{…} exactly matches the ground-truth answer.
- The ground-truth list is provided automatically by the trainer (see run_rl).
- """
- extracted = [
- (re.search(r"\\boxed\{(.*?)\}", c) or re.search(r"<answer>(.*?)</answer>", c))
- for c in completions
- ]
- preds = [m.group(1).strip() if m else "" for m in extracted]
- return [1.0 if p == gt else 0.0 for p, gt in zip(preds, ground_truth)]
- # --------------------------------------------------------------------------- #
- # Utility Config Functions
- # --------------------------------------------------------------------------- #
- def load_config(config_path: str) -> Dict[str, Any]:
- """Load configuration from a JSON file."""
- try:
- with open(config_path, 'r') as f:
- return json.load(f)
- except Exception as e:
- print(f"Error loading config file: {e}")
- return {}
- def get_default_config() -> Dict[str, Any]:
- """Return default configuration dictionary."""
- return {
- "mode": "rl",
- "model_name": "deepseek-Qwen-7B",
- "input_dir": '/path/to/sparrow/results/json',
- "output_dir": '/path/to/sparrow/results/json_out_new',
- "batch_size": 1,
- "num_gpus_avail": 8,
- "gpu_nums": None,
- "learning_rate": 5e-5,
- "epochs": 1,
- "eval": False,
- "eval_only": False,
- "single_modality": None,
- "test_size": 0.1,
- "val_size": 0.1,
- "rl_config": {
- "max_prompt_length": 512,
- "max_completion_len": 128,
- "num_generations": 4,
- "reward_weights": [0.8, 0.2],
- "logging_steps": 10
- },
- "icl_config": {
- "context_save_interval": 5
- }
- }
- def merge_configs(args, base_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
- """Merge command line arguments with config file."""
- # Start with default config
- config = get_default_config()
- # Update with base_config if provided
- if base_config:
- config.update(base_config)
- # Load config from file if provided
- if hasattr(args, 'config') and args.config:
- file_config = load_config(args.config)
- config.update(file_config)
- # Override with command line arguments if provided
- for arg, value in vars(args).items():
- if arg != 'config' and value is not None:
- config[arg] = value
- return config
- # --------------------------------------------------------------------------- #
- # Utility functions for RAG
- def build_preprocessor(X_full):
- """
- Build preprocessing pipeline based on the feature types in X_full,
- with:
- - mean‐imputation (rounded to int) for numerics
- - mode‐imputation for categoricals
- """
- # Drop MRI‐report text columns
- text_cols = [c for c in X_full.columns if c.startswith("Imaging__MRI report")]
- X_full = X_full.drop(columns=text_cols, errors="ignore")
- # Categorical columns (demographics)
- categorical_cols = [
- c for c in X_full.columns
- if c.startswith("Demographic__Sex") or c.startswith("Demographic__Race")
- ]
- # Genetics → binary map + fill missing as 0
- genetics_cols = [c for c in X_full.columns if c.startswith("Genetics__")]
- for col in genetics_cols:
- X_full[col] = (
- X_full[col]
- .map({"present": 1, "not present": 0})
- .fillna(0)
- .astype(int)
- )
- # Identify remaining numeric columns
- num_cols = []
- for col in X_full.columns:
- if col in genetics_cols or col in categorical_cols or col in ('fname', 'patient_id'):
- continue
- try:
- X_full[col].astype(float)
- num_cols.append(col)
- except Exception:
- pass
- # Numeric pipeline: mean‐impute → round to int → scale
- numeric_transformer = Pipeline([
- ("imputer", SimpleImputer(strategy="mean")),
- ("round_to_int", FunctionTransformer(lambda arr: np.round(arr).astype(int), validate=False)),
- ("scaler", StandardScaler()),
- ])
- # Categorical pipeline: mode‐impute → one‐hot encode
- categorical_transformer = Pipeline([
- ("imputer", SimpleImputer(strategy="most_frequent")),
- ("onehot", OneHotEncoder(handle_unknown="ignore")),
- ])
- return ColumnTransformer(
- transformers=[
- ("num", numeric_transformer, num_cols),
- ("cat", categorical_transformer, categorical_cols),
- ],
- remainder="drop"
- )
- def load_patient_jsons(json_dir):
- """
- Reads all .json files in json_dir and returns a DataFrame
- with one row per patient and flattened columns.
- """
- records = []
- for fname in os.listdir(json_dir):
- if not fname.endswith(".json"):
- continue
- fullpath = os.path.join(json_dir, fname)
- with open(fullpath, 'r') as f:
- data = json.load(f)
- flat = {}
- for modality, subdict in data.items():
- if isinstance(subdict, dict):
- for k, v in subdict.items():
- flat[f"{modality}__{k}"] = v
- else:
- flat[modality] = subdict
- # Add diagnosis value to patient data
- base_filename = os.path.basename(fname)
- name_without_ext, _ = os.path.splitext(base_filename)
- parts = name_without_ext.split("_")
- diag_numeric = int(float(parts[2]))
- flat["Diagnosis__numeric"] = diag_numeric
- flat["fname"] = fname
- flat["patient_id"] = name_without_ext # Use filename without extension as patient ID
- records.append(flat)
- df = pd.DataFrame(records)
- return df
utils.py at commit e3c196b, no license · at the source
Overview
- Department of Biomedical Engineering, Rensselaer Polytechnic Institute, Troy, NY USA
- Center for Biotechnology and Interdisciplinary Studies, Rensselaer Polytechnic Institute, Troy, NY USA
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 15 matches between paragraphs and lines of code.
DiegoMac17/sparrow-pd
e3c196b5a0cd905dfd04db7874cb2ba5301600aa, 28 April 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
17 files
- benchmark_3d.py, Python, 526 lines, 3 matches
- benchmark_ml.py, Python, 375 lines, 1 match
- count_modalities.py, Python, 73 lines
- dataset.py, Python, 250 lines, 2 matches
- experts.py, Python, 81 lines
- orchestrator.py, Python, 393 lines, 1 match
- orchestrator_wrapper.py, Python, 106 lines
- results.py, Python, 331 lines, 1 match
- sparrow.py, Python, 504 lines, 2 matches
- tricoat_benchmark/
benchmark_tricoat.py , Python, 203 lines - tricoat_benchmark/
dataset_tricoat.py , Python, 467 lines - tricoat_benchmark/
models_tricoat.py , Python, 953 lines, 1 match - tricoat_benchmark/
test_tricoat.py , Python, 62 lines - tricoat_benchmark/
train_tricoat.py , Python, 126 lines - tricoat_benchmark/
utils_tricoat.py , Python, 156 lines - utils.py, Python, 301 lines, 4 matches
- README.md, Text, 141 lines
Code availability statement
The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: DiegoMac17/
sparrow-pd
Read it in the paper: doi.org/10.1038/s44387-026-00109-y.
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;
- 15 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.
Data availability statement
The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- no repository, dataset or request procedure was recognized in it
Read it in the paper: doi.org/10.1038/s44387-026-00109-y.
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 2, 28 September 2026
- Publisher: n/a → Springer Science+Business Media
Version 1, 28 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 2 authors, 4 keywords, 1 funder, 15 references.
Cite
This paper
Machado Reyes, D., & Yan, P. (2026). SPARROW: subtyping Parkinson's disease with agentic reasoning and robust omics workflow. NPJ artificial intelligence, 2(1), 58. https://
BibTeX
@article{machadoreyes202
author = {Machado Reyes, Diego and Yan, Pingkun},
title = {{SPARROW: subtyping Parkinson's disease with agentic reasoning and robust omics workflow}},
journal = {NPJ artificial intelligence},
year = {2026},
month = may,
volume = {2},
number = {1},
pages = {58},
publisher = {Springer Science+Business Media},
issn = {3005-1460},
doi = {10.1038/
url = {https://
pmid = {42445450},
pmcid = {PMC13357203}
}
RIS
TY - JOUR
AU - Machado Reyes, Diego
AU - Yan, Pingkun
TI - SPARROW: subtyping Parkinson's disease with agentic reasoning and robust omics workflow
T2 - NPJ artificial intelligence
J2 - NPJ Artif Intell
PY - 2026
DA - 2026/
VL - 2
IS - 1
SP - 58
SN - 3005-1460
PB - Springer Science+Business Media
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "SPARROW: subtyping Parkinson's disease with agentic reasoning and robust omics workflow",
"container-title": "NPJ artificial intelligence",
"author": [
{
"family": "Machado Reyes",
"given": "Diego"
},
{
"family": "Yan",
"given": "Pingkun"
}
],
"container-title-short":
"volume": "2",
"issue": "1",
"page": "58",
"DOI": "10.1038/
"PMID": "42445450",
"PMCID": "PMC13357203",
"ISSN": "3005-1460",
"publisher": "Springer Science+Business Media",
"URL": "https://
"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.1016/j.patter.2026.101538 [code]
- A multi-modal foundation model for brain disease diagnosis and medical imaging.Journal: Patterns (New York, N.Y.)In common: MONAI, Hugging Face Transformers, NiBabel, 4 other tools, clinical / translational
- [2] doi:10.1038/s41467-026-71555-0 [code]
- A deep representation learning model to predict response to vagus nerve stimulation.Journal: Nature communicationsIn common: MONAI, XGBoost, NiBabel, 4 other tools, clinical / translational
- [3] doi:10.1371/journal.pone.0354511 [code]
- TokenUNet: A new case for transformers integration in efficient and interpretable 3D UNets for brain imaging segmentation.Journal: PloS oneIn common: MONAI, Hugging Face Transformers, NiBabel, 4 other tools
- [4] doi:10.1186/s13244-026-02365-7 [code]
- Super-resolution MRI and 2.5D deep learning for intratumoral-peritumoral
radiomics in preoperative prediction of rectal cancer perineural invasion. Journal: Insights into imagingIn common: MONAI, XGBoost, NiBabel, 4 other tools - [5] doi:10.1371/journal.pcbi.1014555 [code]
- Body surface potential driven personalisation of electrophysiological digital twins in hypertrophic cardiomyopathy.Journal: PLoS computational biologyIn common: MONAI, XGBoost, NiBabel, 4 other tools
- [6] doi:10.1038/s41591-026-04497-1 [code]
- Health system learning enables generalist neuroimaging models.Journal: Nature medicineIn common: Hugging Face Transformers, PyTorch, pandas, 1 other tool, clinical / translational, 2 references
- [7] doi:10.1111/joa.70203 [code]
- Two-step workflow integrating automatic registration and manual refinement for the accurate alignment of serial histological sections in 3D reconstruction.Journal: Journal of anatomyIn common: MONAI, Hugging Face Transformers, NiBabel, 3 other tools
- [8] doi:10.1158/2767-9764.crc-25-0710 [code]
- MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma.Journal: Cancer research communicationsIn common: MONAI, NiBabel, PyTorch, 3 other tools, clinical / translational
- [9] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: Hugging Face Transformers, XGBoost, PyTorch, 3 other tools
- [10] doi:10.1038/s41467-026-76837-1 [code]
- Drug screen and machine learning predict neuroprotective agents in a preclinical human model of childhood dementia.Journal: Nature communicationsIn common: Hugging Face Transformers, XGBoost, PyTorch, 3 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 15 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:1af4a8e0cc93d40c…
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.
