Nv Generate Mr Brain Finetune
NVIDIAOfficialCreated by NVIDIA MedTech Team
- Skill
- Tool integration
- Data and AI
- Apache-2.0
- Medium risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured wrapper to finetune the NV-Generate-CTMR MR-Brain v1 diffusion UNet. It automates the staging of configuration files, latent embedding generation, and training execution by delegating to upstream scripts. The skill supports various MRI modalities including T1, T2, FLAIR, SWI, and MRA. It is designed for research and experimentation, explicitly excluding clinical, regulatory, or production data approval use cases.
What it does
- Stages configuration JSONs for training and inference workflows.
- Generates latent embeddings from NIfTI training volumes.
- Executes diffusion UNet training using user-supplied data.
- Supports optional inference runs for immediate sanity checking.
- Provides a preflight mode to validate datalists without GPU training.
- Manages environment and dependency requirements for upstream script execution.
When to use it
- When you need to adapt the MR-Brain v1 model to specific MRI training data.
- When performing initial validation of a new datalist using the preflight feature.
- When you want to monitor training progress via TensorBoard.
- When you need to generate custom brain MRI volumes for qualitative research.
Advantages
- Automates complex configuration staging for upstream training scripts.
- Provides a consistent interface for both preflight validation and full GPU training.
- Allows for flexible hyperparameter tuning via standard JSON configuration files.
- Supports multiple MRI modalities within a unified workflow.
Usage examples
- “Validate my MRI T1 datalist for finetuning.”
- The agent runs the skill in preflight mode, checking the datalist and data directory structure without initiating GPU training.
- “Finetune the MR-Brain model on my T1 dataset for 2 epochs.”
- The agent executes the training script with the specified datalist, data root, and epoch count, producing a finetuned checkpoint.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/nvidia/nv-generate-mr-brain-finetune/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip nv-generate-mr-brain-finetune.zip -d .agents/skills
Documentation by NVIDIA MedTech Team
NV-Generate-MR-Brain-Finetune
Purpose
Used for finetuning the NV-Generate-CTMR
rflow-mr-brainv1 diffusion UNet from user-supplied T1, T2, FLAIR, SWI, or MRA NIfTI training volumes.Not for clinical interpretation, regulatory use, or approving synthetic data for production training.
The wrapper stages the config glue locally and delegates execution to existing upstream scripts:
scripts.diff_model_create_training_data,scripts.diff_model_train, and optionallyscripts.diff_model_infer. It does not execute the notebook.Manifest I/O: inputs are
datalistanddata_base_dir; outputs arefinetuned_checkpoint, optionalinference_outputs, andresult_json.The underlying training contract is the upstream config/env JSON (the same one driven from cell
[10]oftrain_diff_unet_tutorial.ipynb). The wrapper stages those JSON files for you and exposes the most-tuned fields as CLI flags; the sections below document the fields, their defaults, and how to monitor/tune a run.
Instructions
Read
skill_manifest.yamlbefore changing arguments, side effects, or validation gates.Run
scripts/run_mr_brain_finetune.pyfrom the Medical AI Skills repo root.If a host agent exposes
run_script, userun_script("scripts/run_mr_brain_finetune.py", args=[...]); otherwise run the Bash/Python command below.For a command-shape review, do not install packages, clone repositories, download weights, or start GPU training. Emit only the wrapper command with the supplied datalist, an explicit
--data-base-dir, an explicit--output-dir, and the requested modality.When the user explicitly asks for a training-launch command, do not silently replace it with
--preflight; include--preflightonly for a preflight request.Use
--preflightfirst when checking a new datalist; remove--preflightonly when the user explicitly wants to launch GPU finetuning.For a staged preflight input bundle directory, use
BUNDLE/preflight_datalist.jsonas the datalist andBUNDLE/preflight_datasetas--data-base-dirwhen those files are present.
Examples
Validate and stage a preflight finetune check from an input bundle (the recommended first step — no GPU, no training). This is the single canonical command; replace INPUT_BUNDLE and OUT_DIR with your paths:
export NV_GENERATE_ROOT="${NV_GENERATE_ROOT:-$HOME/.cache/nvidia-skills/upstreams/NV-Generate-CTMR-da438fe}" && \
python skills/nv-generate-mr-brain-finetune/scripts/run_mr_brain_finetune.py \
INPUT_BUNDLE/preflight_datalist.json \
--data-base-dir INPUT_BUNDLE/preflight_dataset \
--output-dir OUT_DIR \
--modality mri_t1 \
--preflightFor real GPU finetuning and other variations, see Usage below.
Command-shape review for a requested training launch (no setup or execution):
python skills/nv-generate-mr-brain-finetune/scripts/run_mr_brain_finetune.py \
PATH_TO_DATALIST.json \
--data-base-dir PATH_TO_DATA_ROOT \
--output-dir runs/nv_generate_mr_brain_finetune \
--epochs 2 \
--modality mri_t1Available Scripts
| Script | Purpose | Arguments |
|---|---|---|
scripts/run_mr_brain_finetune.py | Primary entrypoint declared by skill_manifest.yaml. | DATALIST.json --data-base-dir DATA_DIR --output-dir OUT_DIR [--epochs N] [--modality mri_t1] [--num-gpus N] [--no-amp] [--model-config FILE] [--download-model-data] [--run-inference] [--preflight] |
Prerequisites
An explicit
NV_GENERATE_ROOTmay point to the caller's local checkout and must containscripts/diff_model_create_training_data.py,scripts/diff_model_train.py, andscripts/diff_model_infer.py. The result records its current commit.If
NV_GENERATE_ROOTis unset, the wrapper searches.workbench_data/upstreams/NV-Generate-CTMR.CUDA_VISIBLE_DEVICESis optional and can be used to select the GPU for real training.Runtime requirements: NVIDIA CUDA GPU for real training, Python packages from the upstream
requirements.txt, and downloaded MR-brain weights.Side effects: writes staged configs, embeddings, checkpoints, optional inference images, and logs under the caller-provided
--output-dir; may write model caches under the upstream checkout and~/.cache/huggingface/; may contacthttps://huggingface.cofor model assets andhttps://github.comfor the upstream checkout.The datalist is a MONAI-style JSON object with
training[].imagepaths relative to--data-base-dir.training[].modalityis optional and defaults tomri_t1.
When no local checkout is supplied, create the recommended pinned default checkout once:
if [ -z "${NV_GENERATE_ROOT:-}" ]; then
export NV_GENERATE_COMMIT=da438fec6484cdb6f421f8c7051d954ebefff730
export NV_GENERATE_ROOT="$HOME/.cache/nvidia-skills/upstreams/NV-Generate-CTMR-da438fe"
if [ ! -d "$NV_GENERATE_ROOT/.git" ]; then
git clone https://github.com/NVIDIA-Medtech/NV-Generate-CTMR.git "$NV_GENERATE_ROOT"
git -C "$NV_GENERATE_ROOT" checkout --detach "$NV_GENERATE_COMMIT"
fi
fiThe wrapper executes upstream code only when NV_GENERATE_ROOT is at the exact
manifest commit and its tracked files are clean. Supply custom training and
inference settings through the documented config flags rather than editing the
checkout. Child processes receive only an allowlist of runtime, CUDA, locale,
and certificate variables; API keys, tokens, passwords, and unrelated parent
environment values are not forwarded. The public v1 assets do not require a
credential; pre-download them if your network setup requires separate tooling.
Before a GPU run, download the exact autoencoder and MR-Brain v1 checkpoint
revisions declared by the manifest. Passing --download-model-data performs
these same two pinned downloads:
python -m huggingface_hub.commands.huggingface_cli download \
nvidia/NV-Generate-CT models/autoencoder_v1.pt \
--revision 75ac080fb1083c403793563477724c038e7d430c \
--local-dir "$NV_GENERATE_ROOT"
python -m huggingface_hub.commands.huggingface_cli download \
nvidia/NV-Generate-MR-Brain models/diff_unet_3d_rflow-mr-brain_v1.pt \
--revision ef9759bf221265b2704569cdeeac20bbf03b62ee \
--local-dir "$NV_GENERATE_ROOT"1. Config and environment JSON (adapt to your data)
This is a thin wrapper around the upstream train_diff_unet_tutorial.ipynb flow. Each run performs four steps, delegating the heavy lifting to the model author's scripts:
Stage configs — copy the three config JSONs and rewrite only the run-specific paths and
n_epochs(notebook cell 15).python -m scripts.diff_model_create_training_data→ latent*_emb.nii.gzembeddings (cell 17).Write embedding sidecars — a
<emb>.nii.gz.jsonper embedding withspacing/modality(and body-region indices when the model uses them). This is the one piece of glue that lives in the notebook (cell 19), not in upstreamscripts/, anddiff_model_trainrequires it; the skill owns it.python -m scripts.diff_model_train(cell 21), optionallypython -m scripts.diff_model_infer.
Tune by editing the config JSON, not by adding flags. All training/inference hyperparameters (lr, batch_size, cache_rate, inference dim/spacing/num_inference_steps/cfg_guidance_scale, …) live in config_maisi_diff_model_rflow-mr-brain.json. Edit the upstream copy, or pass your own with --model-config FILE (and --env-config / --model-def for the other two). The wrapper only ever rewrites the fields below.
Environment JSON (environment_maisi_diff_model_rflow-mr-brain.json) — fields the wrapper rewrites per run:
| Field | Set from | Notes |
|---|---|---|
data_base_dir | --data-base-dir | Root for relative training[].image paths. |
json_data_list | your datalist | Staged copy with per-entry modality filled in. |
embedding_base_dir, model_dir, output_dir | --output-dir | Latent embeddings, checkpoints, inference images. |
modality_mapping_path | upstream | Maps modality name → integer code. |
model_filename | --model-filename | Output checkpoint name (default diff_unet_3d_rflow-mr-brain_v1.pt). |
existing_ckpt_filepath | upstream weights / --existing-ckpt-filepath | Starting checkpoint; cleared by --train-from-scratch. |
trained_autoencoder_path | upstream weights / --trained-autoencoder-path | VAE used to encode/decode latents. |
Model config (config_maisi_diff_model_rflow-mr-brain.json) — the only fields the wrapper touches:
| Field | Set from | Default | Notes |
|---|---|---|---|
diffusion_unet_train.n_epochs | --epochs | 2 (upstream config ships 1000) | Convenience override (cell 15 does the same); wrapper default is small for verification. |
diffusion_unet_inference.modality | --modality | from modality_mapping.json | Kept consistent with the training modality for optional --run-inference. |
Everything else in that file (lr, batch_size, cache_rate, the rest of diffusion_unet_inference) is left exactly as written — edit the JSON to change it.
The pinned v1 inference block defaults to dim=[256,256,128],
spacing=[0.94,0.94,1.36], and cfg_guidance_scale=2. The wrapper preserves
those fields. Older v0 examples may show 256^3, 1 mm spacing, and guidance
scale 10; use the staged v1 JSON as the execution source of truth.
Runtime flags (not config fields): --num-gpus N (>1 launches torch.distributed.run), --no-amp (disable mixed precision, passed through to diff_model_train).
--modality selects the integer code from configs/modality_mapping.json.
Supported brain values include mri (8), mri_t1 (9, default), mri_t2
(10), mri_flair (11), mri_mra (16), mri_swi (20), and the
skull-stripped values mri_t1_skull_stripped (29),
mri_t2_skull_stripped (30), mri_flair_skull_stripped (31),
mri_swi_skull_stripped (32), and mri_mra_skull_stripped (33). Per-case
training[].modality overrides --modality. The modality also feeds the
step-3 embedding sidecars. Upstream reports sparse MRA training coverage, so
MRA output quality is not guaranteed.
For an end-to-end reference including example data download and checkpoint loading, see the upstream tutorial train_diff_unet_tutorial.ipynb.
2. Usage (one-line training)
Preflight only:
export NV_GENERATE_ROOT="${NV_GENERATE_ROOT:-$HOME/.cache/nvidia-skills/upstreams/NV-Generate-CTMR-da438fe}" && \
python skills/nv-generate-mr-brain-finetune/scripts/run_mr_brain_finetune.py \
PATH_TO_DATALIST.json \
--data-base-dir PATH_TO_DATA_ROOT \
--output-dir runs/nv_generate_mr_brain_finetune_preflight \
--preflightPreflight bundle input:
export NV_GENERATE_ROOT="${NV_GENERATE_ROOT:-$HOME/.cache/nvidia-skills/upstreams/NV-Generate-CTMR-da438fe}" && \
python skills/nv-generate-mr-brain-finetune/scripts/run_mr_brain_finetune.py \
PATH_TO_INPUT_BUNDLE/preflight_datalist.json \
--data-base-dir PATH_TO_INPUT_BUNDLE/preflight_dataset \
--output-dir runs/nv_generate_mr_brain_finetune_preflight \
--preflightGPU finetuning:
export NV_GENERATE_ROOT="${NV_GENERATE_ROOT:-$HOME/.cache/nvidia-skills/upstreams/NV-Generate-CTMR-da438fe}" && \
python -m pip install -r "$NV_GENERATE_ROOT/requirements.txt" && \
python skills/nv-generate-mr-brain-finetune/scripts/run_mr_brain_finetune.py \
PATH_TO_DATALIST.json \
--data-base-dir PATH_TO_DATA_ROOT \
--output-dir runs/nv_generate_mr_brain_finetune \
--epochs 2 \
--modality mri_t1 \
--run-inferenceReplace PATH_TO_DATALIST.json and PATH_TO_DATA_ROOT with the user's actual paths. Do not use the fixture datalist for real training; it is a preflight-only placeholder.
3. Monitor training (TensorBoard)
scripts.diff_model_train writes TensorBoard event files under the staged model_dir (OUT_DIR/artifacts/models). Launch TensorBoard against the output directory and watch the loss curve:
python -m pip install tensorboard && \
tensorboard --logdir runs/nv_generate_mr_brain_finetune/artifactsThe run summary is written to OUT_DIR/artifacts/workflow_summary.json (checkpoint path, embedding sidecars, inference outputs); the JSON the wrapper prints to stdout mirrors the same paths plus exit_code and a stderr_tail for quick triage.
4. Hyperparameter tuning and common pitfalls
Loss not decreasing / unstable — lower
diffusion_unet_train.lr(default1e-5) in the model-config JSON, or keep AMP on (default);--no-ampis slower but more numerically stable on older GPUs.Out-of-memory — keep
diffusion_unet_train.batch_sizeat1andcache_rateat0in the config JSON, and confirm the autoencoder/UNet fit your GPU before scaling. Multi-GPU (--num-gpus N) shards the batch viatorch.distributed.run.Few cases / quick check — keep
--epochssmall (the wrapper default2is for verification, not convergence; the upstream config ships1000).Wrong modality conditioning — set
--modalityor per-casetraining[].modalityto a value present inconfigs/modality_mapping.json; a mismatch produces a clear error rather than silently mislabeling latents.Slow startup on first run —
diff_model_create_training_dataprecomputes latent embeddings once; reuse the same--output-dirto avoid recomputing them.
5. Evaluate the finetuned model
Use the staged checkpoint (OUT_DIR/artifacts/models/<model_filename>) as the diffusion UNet for generation, then inspect the synthesized volumes:
Pass
--run-inferencehere for a quick built-in sanity render, orPoint the
nv-generate-mr-braininference skill at the finetuned checkpoint to generate fresh brain MRI volumes for qualitative review.
This skill gates file accounting and command provenance only — anatomical realism and downstream utility must be judged by a domain expert on the generated images.
Limitations
Requires a current upstream
NV-Generate-CTMRcheckout with the existing diffusion training scripts. The skill itself stages the required config and datalist glue locally and does not depend on the notebook or PR #33.Full training can be expensive and is not deterministic across hardware, CUDA, and package versions.
The wrapper gates file accounting and command provenance, not anatomical realism or downstream model utility.
Not for clinical deployment, clinical interpretation, autonomous diagnosis, regulatory submission, or production training-data approval.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
diffusion training scripts were not found | NV_GENERATE_ROOT does not point at a current NV-Generate-CTMR checkout. | Clone or update https://github.com/NVIDIA-Medtech/NV-Generate-CTMR and set NV_GENERATE_ROOT. |
missing datalist image | training[].image paths are not relative to --data-base-dir or files are absent. | Fix the datalist or pass the correct data root. |
| CUDA or MONAI import failure | Runtime environment lacks upstream dependencies. | Install "$NV_GENERATE_ROOT/requirements.txt" in the selected environment. |
Files
- ATTRIBUTION.mdAdded by AutoGineer958 B
- BENCHMARK.md7.6 KiB
- LICENSELicence file added by AutoGineer11.2 KiB
- SKILL.md15.3 KiB
- evals/evals.json2.0 KiB
- fixtures/README.md228 B
- fixtures/preflight_datalist.json130 B
- fixtures/preflight_dataset/imagesTr/placeholder_mri_t1.txt133 B
- scripts/run_mr_brain_finetune.pyScript30.2 KiB
- skill-card.md4.8 KiB
- skill.oms.sig6.2 KiB
- skill_manifest.yaml7.4 KiB
- tests/test_run_mr_brain_finetune.pyScript10.1 KiB
- validators/output_schema.json4.2 KiB
scripts/run_mr_brain_finetune.py
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Thin wrapper for NV-Generate-CTMR MR-brain diffusion-UNet finetuning.
The wrapper mirrors the upstream ``train_diff_unet_tutorial.ipynb`` flow without
reimplementing it:
1. stage the three config JSONs, rewriting only the run-specific paths and
``n_epochs`` (notebook cell 15);
2. ``python -m scripts.diff_model_create_training_data`` -> ``*_emb.nii.gz``
(notebook cell 17);
3. write a ``<emb>.nii.gz.json`` sidecar per embedding (notebook cell 19) -- the
one piece of glue that lives in the notebook, not in upstream ``scripts/``,
and that ``diff_model_train`` requires;
4. ``python -m scripts.diff_model_train`` (notebook cell 21), optionally followed
by ``python -m scripts.diff_model_infer``.
All hyperparameters (lr, batch_size, cache_rate, inference settings, ...) live in
the model-config JSON and are edited there or supplied via ``--model-config``;
the wrapper does not surface them as flags. It does not execute the notebook.
Engineering verification only. Outputs are not clinically meaningful.
"""
from __future__ import annotations
import argparse
import copy
import json
import os
import subprocess
import sys
import time
from pathlib import Path
from typing import Any
SKILL_NAME = "nv_generate_mr_brain_finetune"
UPSTREAM_REPO = "https://github.com/NVIDIA-Medtech/NV-Generate-CTMR"
UPSTREAM_COMMIT = "da438fec6484cdb6f421f8c7051d954ebefff730"
UPSTREAM_ENTRYPOINT = (
"python -m scripts.diff_model_create_training_data; " "python -m scripts.diff_model_train"
)
VERSION = "rflow-mr-brain"
MODEL_ASSETS = (
{
"repo_id": "nvidia/NV-Generate-CT",
"revision": "75ac080fb1083c403793563477724c038e7d430c",
"filename": "models/autoencoder_v1.pt",
},
{
"repo_id": "nvidia/NV-Generate-MR-Brain",
"revision": "ef9759bf221265b2704569cdeeac20bbf03b62ee",
"filename": "models/diff_unet_3d_rflow-mr-brain_v1.pt",
},
)
REPO_ROOT = Path(__file__).resolve().parents[3]
DEFAULT_UPSTREAM = REPO_ROOT / ".workbench_data" / "upstreams" / "NV-Generate-CTMR"
_CHILD_ENV_KEYS = (
"CUDA_DEVICE_ORDER",
"CUDA_HOME",
"CUDA_PATH",
"CUDA_VISIBLE_DEVICES",
"CUBLAS_WORKSPACE_CONFIG",
"CURL_CA_BUNDLE",
"HOME",
"LANG",
"LC_ALL",
"LD_LIBRARY_PATH",
"NCCL_DEBUG",
"NCCL_IB_DISABLE",
"NCCL_P2P_DISABLE",
"NCCL_SOCKET_IFNAME",
"NVIDIA_DRIVER_CAPABILITIES",
"NVIDIA_VISIBLE_DEVICES",
"OMP_NUM_THREADS",
"PATH",
"PYTHONNOUSERSITE",
"REQUESTS_CA_BUNDLE",
"SSL_CERT_FILE",
"TMPDIR",
"TORCH_EXTENSIONS_DIR",
"TORCH_HOME",
)
REQUIRED_UPSTREAM_FILES = (
"scripts/download_model_data.py",
"scripts/diff_model_create_training_data.py",
"scripts/diff_model_train.py",
"scripts/diff_model_infer.py",
"configs/config_network_rflow.json",
"configs/environment_maisi_diff_model_rflow-mr-brain.json",
"configs/config_maisi_diff_model_rflow-mr-brain.json",
"configs/modality_mapping.json",
)
SUPPORTED_MODALITIES = (
"mri",
"mri_t1",
"mri_t2",
"mri_flair",
"mri_mra",
"mri_swi",
"mri_t1_skull_stripped",
"mri_t2_skull_stripped",
"mri_flair_skull_stripped",
"mri_mra_skull_stripped",
"mri_swi_skull_stripped",
)
def _emit(payload: dict[str, Any]) -> None:
sys.stdout.write(json.dumps(payload, indent=2))
sys.stdout.flush()
def _tail(text: str, n_chars: int = 4000) -> str:
return text if len(text) <= n_chars else "..." + text[-n_chars:]
def _child_process_env(overrides: dict[str, str] | None = None) -> dict[str, str]:
"""Build the minimal environment needed by the pinned upstream workflow."""
env: dict[str, str] = {}
for name in _CHILD_ENV_KEYS:
value = os.environ.get(name)
if value is not None:
env[name] = value
env.setdefault("PATH", os.defpath)
if overrides:
env.update(overrides)
return env
def _load_json(path: Path) -> Any:
return json.loads(path.read_text())
def _write_json(path: Path, payload: dict[str, Any]) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
return path
def _parse_region(value: str) -> list[int]:
parts = [int(v.strip()) for v in value.split(",")]
if len(parts) != 4 or any(v not in (0, 1) for v in parts):
raise argparse.ArgumentTypeError("expected four comma-separated 0/1 values")
return parts
def _resolve_upstream_root(explicit: str | None = None) -> tuple[Path | None, list[str]]:
candidates: list[Path] = []
if explicit:
candidates.append(Path(explicit).expanduser())
env_root = os.environ.get("NV_GENERATE_ROOT")
if env_root:
candidates.append(Path(env_root).expanduser())
candidates.extend([DEFAULT_UPSTREAM, Path.home() / "NV-Generate-CTMR"])
checked: list[str] = []
seen: set[str] = set()
for candidate in candidates:
resolved = candidate.resolve()
key = str(resolved)
if key in seen:
continue
seen.add(key)
checked.append(key)
if all((resolved / rel).is_file() for rel in REQUIRED_UPSTREAM_FILES):
return resolved, checked
return None, checked
def _resolve_data_path(data_base_dir: Path, image: str) -> Path:
image_path = Path(image)
if image_path.is_absolute():
raise ValueError("datalist image paths must be relative to --data-base-dir")
return data_base_dir / image_path
def _validate_datalist(data_base_dir: Path, datalist: Path, modality: str) -> dict[str, Any]:
if modality not in SUPPORTED_MODALITIES:
raise ValueError(f"unsupported modality {modality!r}")
raw = _load_json(datalist)
if not isinstance(raw, dict):
raise ValueError("datalist must be a JSON object")
training = raw.get("training")
testing = raw.get("testing", [])
if not isinstance(training, list) or not training:
raise ValueError("datalist.training must be a non-empty list")
if not isinstance(testing, list):
raise ValueError("datalist.testing must be a list when provided")
missing: list[str] = []
modality_values: set[str] = set()
for split_name, entries in (("training", training), ("testing", testing)):
for i, item in enumerate(entries):
if not isinstance(item, dict) or "image" not in item:
raise ValueError(f"{split_name}[{i}] must contain image")
item_modality = str(item.get("modality", modality))
if item_modality not in SUPPORTED_MODALITIES:
raise ValueError(f"unsupported modality {item_modality!r} in {split_name}[{i}]")
image_path = _resolve_data_path(data_base_dir, str(item["image"]))
if not image_path.is_file():
missing.append(str(image_path))
modality_values.add(item_modality)
if missing:
raise FileNotFoundError(f"missing datalist image(s): {missing[:5]}")
return {
"data_base_dir": str(data_base_dir),
"datalist": str(datalist),
"training_cases": len(training),
"testing_cases": len(testing),
"modalities": sorted(modality_values),
"default_modality": modality,
}
def _stage_datalist(
data_base_dir: Path,
input_path: Path,
output_path: Path,
default_modality: str,
) -> tuple[Path, dict[str, Any]]:
raw = _load_json(input_path)
staged: dict[str, Any] = {"training": [], "testing": []}
for split in ("training", "testing"):
for item in raw.get(split, []):
next_item = dict(item)
next_item.setdefault("modality", default_modality)
_resolve_data_path(data_base_dir, str(next_item["image"]))
staged[split].append(next_item)
return _write_json(output_path, staged), staged
def _git_commit(root: Path) -> str:
try:
proc = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=str(root),
env=_child_process_env(),
check=False,
capture_output=True,
text=True,
timeout=10,
)
except Exception:
return ""
return proc.stdout.strip() if proc.returncode == 0 else ""
def _git_tracked_files_clean(root: Path) -> bool:
try:
proc = subprocess.run(
["git", "status", "--porcelain", "--untracked-files=no"],
cwd=str(root),
env=_child_process_env(),
check=False,
capture_output=True,
text=True,
timeout=10,
)
except Exception:
return False
return proc.returncode == 0 and not proc.stdout.strip()
def _upstream_identity(root: Path) -> dict[str, Any]:
commit = _git_commit(root)
tracked_files_clean = _git_tracked_files_clean(root)
return {
"commit": commit,
"expected_commit": UPSTREAM_COMMIT,
"commit_match": commit == UPSTREAM_COMMIT,
"tracked_files_clean": tracked_files_clean,
"trusted": commit == UPSTREAM_COMMIT and tracked_files_clean,
}
def _config_sources(args: argparse.Namespace, upstream_root: Path) -> tuple[Path, Path, Path]:
"""Resolve the three config JSONs: caller-supplied overrides or upstream defaults."""
model_def = (
Path(args.model_def).expanduser()
if args.model_def
else upstream_root / "configs" / "config_network_rflow.json"
)
env_config = (
Path(args.env_config).expanduser()
if args.env_config
else upstream_root / "configs" / f"environment_maisi_diff_model_{VERSION}.json"
)
model_config = (
Path(args.model_config).expanduser()
if args.model_config
else upstream_root / "configs" / f"config_maisi_diff_model_{VERSION}.json"
)
return model_def, env_config, model_config
def _modality_mapping(upstream_root: Path) -> dict[str, int]:
path = upstream_root / "configs" / "modality_mapping.json"
return {str(k): int(v) for k, v in _load_json(path).items()}
def _resolve_from_upstream(upstream_root: Path, value: str | None) -> str | None:
if value in (None, ""):
return value
path = Path(str(value)).expanduser()
if path.is_absolute():
return str(path)
return str((upstream_root / path).resolve())
def _stage_configs(args: argparse.Namespace, upstream_root: Path) -> dict[str, Any]:
"""Stage configs the way the notebook does: rewrite run paths + n_epochs only.
Every other hyperparameter is left exactly as it appears in the (upstream or
caller-supplied) model-config JSON.
"""
model_def_src, env_src, model_src = _config_sources(args, upstream_root)
for path in (model_def_src, env_src, model_src):
if not path.is_file():
raise FileNotFoundError(path)
work_dir = args.output_dir.resolve() / "workflow"
artifacts_dir = args.output_dir.resolve() / "artifacts"
config_dir = work_dir / "configs"
embedding_dir = work_dir / "embeddings"
model_dir = artifacts_dir / "models"
inference_dir = artifacts_dir / "inference"
staged_datalist_path, staged_datalist = _stage_datalist(
args.data_base_dir.resolve(),
args.datalist.resolve(),
work_dir / "dataset.json",
args.modality,
)
model_def = copy.deepcopy(_load_json(model_def_src))
env_config = copy.deepcopy(_load_json(env_src))
model_config = copy.deepcopy(_load_json(model_src))
# Run-specific path rewrites (notebook cell 15).
env_config["data_base_dir"] = str(args.data_base_dir.resolve())
env_config["embedding_base_dir"] = str(embedding_dir)
env_config["json_data_list"] = str(staged_datalist_path)
env_config["model_dir"] = str(model_dir)
env_config["output_dir"] = str(inference_dir)
env_config["modality_mapping_path"] = str(
(upstream_root / "configs" / "modality_mapping.json").resolve()
)
env_config["trained_autoencoder_path"] = (
str(args.trained_autoencoder_path.resolve())
if args.trained_autoencoder_path
else _resolve_from_upstream(upstream_root, env_config.get("trained_autoencoder_path"))
)
if args.existing_ckpt_filepath:
env_config["existing_ckpt_filepath"] = str(args.existing_ckpt_filepath.resolve())
elif args.train_from_scratch:
env_config["existing_ckpt_filepath"] = None
else:
env_config["existing_ckpt_filepath"] = _resolve_from_upstream(
upstream_root,
env_config.get("existing_ckpt_filepath"),
)
if args.model_filename:
env_config["model_filename"] = args.model_filename
# The only training field the notebook overrides; everything else stays as in
# the config JSON so users tune by editing it (or passing --model-config).
model_config.setdefault("diffusion_unet_train", {})["n_epochs"] = args.epochs
# Keep optional inference conditioning consistent with the chosen modality.
modality_code = _modality_mapping(upstream_root).get(args.modality)
if modality_code is None:
raise ValueError(f"modality {args.modality!r} not found in configs/modality_mapping.json")
model_config.setdefault("diffusion_unet_inference", {})["modality"] = modality_code
artifacts_dir.mkdir(parents=True, exist_ok=True)
return {
"env_config": _write_json(config_dir / "environment_maisi_diff_model.json", env_config),
"model_config": _write_json(config_dir / "config_maisi_diff_model.json", model_config),
"model_def": _write_json(config_dir / "config_maisi.json", model_def),
"embedding_dir": embedding_dir,
"artifacts_dir": artifacts_dir,
"model_dir": model_dir,
"inference_dir": inference_dir,
"datalist": staged_datalist,
"include_body_region": bool(model_def.get("include_body_region", False)),
"modality_code": modality_code,
}
def _module_command(module: str, module_args: list[str], num_gpus: int) -> list[str]:
if num_gpus > 1:
return [
sys.executable,
"-m",
"torch.distributed.run",
"--nproc_per_node",
str(num_gpus),
"--nnodes",
"1",
"--master_addr",
"localhost",
"--master_port",
"1234",
"-m",
module,
*module_args,
]
return [sys.executable, "-m", module, *module_args]
def _build_command_plan(
args: argparse.Namespace,
upstream_root: Path,
staged: dict[str, Any] | None = None,
) -> list[list[str]]:
env_config = str(staged["env_config"]) if staged else "<staged-env-config>"
model_config = str(staged["model_config"]) if staged else "<staged-model-config>"
model_def = str(staged["model_def"]) if staged else "<staged-model-def>"
plan: list[list[str]] = []
if args.download_model_data:
for asset in MODEL_ASSETS:
plan.append(
[
sys.executable,
"-m",
"huggingface_hub.commands.huggingface_cli",
"download",
asset["repo_id"],
asset["filename"],
"--revision",
asset["revision"],
"--local-dir",
str(upstream_root),
]
)
if not args.skip_create_training_data:
plan.append(
_module_command(
"scripts.diff_model_create_training_data",
["-e", env_config, "-c", model_config, "-t", model_def, "-g", str(args.num_gpus)],
args.num_gpus,
)
)
if not args.skip_train:
train_args = [
"-e",
env_config,
"-c",
model_config,
"-t",
model_def,
"-g",
str(args.num_gpus),
]
if args.no_amp:
train_args.append("--no_amp")
plan.append(_module_command("scripts.diff_model_train", train_args, args.num_gpus))
if args.run_inference:
plan.append(
_module_command(
"scripts.diff_model_infer",
["-e", env_config, "-c", model_config, "-t", model_def, "-g", str(args.num_gpus)],
args.num_gpus,
)
)
return plan
def _create_embedding_sidecars(
embedding_base_dir: Path,
modality: str,
include_body_region: bool,
top_region_index: list[int],
bottom_region_index: list[int],
) -> list[Path]:
"""Reproduce notebook cell 19: a <emb>.nii.gz.json sidecar per embedding.
``diff_model_train`` reads spacing/modality (and region indices when the model
uses body-region conditioning) from these files; upstream ``scripts/`` does not
write them, so this glue stays in the skill.
"""
import nibabel as nib
sidecars: list[Path] = []
for emb in sorted(embedding_base_dir.rglob("*_emb.nii.gz")):
img = nib.load(str(emb))
data: dict[str, Any] = {
"dim": [int(v) for v in img.shape[:3]],
"spacing": [float(v) for v in img.header.get_zooms()[:3]],
"modality": modality,
}
if include_body_region:
data["top_region_index"] = top_region_index
data["bottom_region_index"] = bottom_region_index
sidecars.append(_write_json(Path(str(emb) + ".json"), data))
return sidecars
def _run_command(
command: list[str], upstream_root: Path, env: dict[str, str]
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
command,
cwd=str(upstream_root),
env=env,
capture_output=True,
text=True,
check=False,
)
def _write_workflow_summary(
args: argparse.Namespace,
staged: dict[str, Any],
sidecars: list[Path],
inference_outputs: list[Path],
) -> Path:
env_config = _load_json(staged["env_config"])
model_filename = env_config.get("model_filename")
checkpoint = staged["model_dir"] / model_filename if model_filename else None
summary = {
"generate_version": VERSION,
"modality": args.modality,
"modality_code": staged["modality_code"],
"training_cases": len(staged["datalist"].get("training", [])),
"testing_cases": len(staged["datalist"].get("testing", [])),
"embedding_sidecars": [str(p) for p in sidecars],
"checkpoint": str(checkpoint) if checkpoint else None,
"inference_outputs": [str(p) for p in inference_outputs],
"staged_configs": {
"env_config": str(staged["env_config"]),
"model_config": str(staged["model_config"]),
"model_def": str(staged["model_def"]),
},
}
return _write_json(staged["artifacts_dir"] / "workflow_summary.json", summary)
def _run_workflow(
args: argparse.Namespace,
upstream_root: Path,
staged: dict[str, Any],
env: dict[str, str],
) -> tuple[int, str, str, list[list[str]]]:
command_plan = _build_command_plan(args, upstream_root, staged)
stdout_parts: list[str] = []
stderr_parts: list[str] = []
sidecars: list[Path] = []
command_index = 0
if args.download_model_data:
for _asset in MODEL_ASSETS:
proc = _run_command(command_plan[command_index], upstream_root, env)
stdout_parts.append(proc.stdout)
stderr_parts.append(proc.stderr)
command_index += 1
if proc.returncode != 0:
return (
proc.returncode,
"\n".join(stdout_parts),
"\n".join(stderr_parts),
command_plan,
)
if not args.skip_create_training_data:
proc = _run_command(command_plan[command_index], upstream_root, env)
stdout_parts.append(proc.stdout)
stderr_parts.append(proc.stderr)
command_index += 1
if proc.returncode != 0:
return proc.returncode, "\n".join(stdout_parts), "\n".join(stderr_parts), command_plan
sidecars = _create_embedding_sidecars(
staged["embedding_dir"],
args.modality,
staged["include_body_region"],
args.top_region_index,
args.bottom_region_index,
)
if not args.skip_train:
proc = _run_command(command_plan[command_index], upstream_root, env)
stdout_parts.append(proc.stdout)
stderr_parts.append(proc.stderr)
command_index += 1
if proc.returncode != 0:
return proc.returncode, "\n".join(stdout_parts), "\n".join(stderr_parts), command_plan
inference_outputs: list[Path] = []
if args.run_inference:
proc = _run_command(command_plan[command_index], upstream_root, env)
stdout_parts.append(proc.stdout)
stderr_parts.append(proc.stderr)
if proc.returncode != 0:
return proc.returncode, "\n".join(stdout_parts), "\n".join(stderr_parts), command_plan
inference_outputs = sorted(staged["inference_dir"].glob("*.nii.gz"))
_write_workflow_summary(args, staged, sidecars, inference_outputs)
return 0, "\n".join(stdout_parts), "\n".join(stderr_parts), command_plan
def _summarize_output(output_dir: Path) -> dict[str, Any]:
artifacts_dir = output_dir / "artifacts"
summary_path = artifacts_dir / "workflow_summary.json"
summary = _load_json(summary_path) if summary_path.is_file() else {}
checkpoint = Path(summary.get("checkpoint") or "")
inference_outputs = [Path(p) for p in summary.get("inference_outputs", [])]
return {
"directory": str(output_dir),
"artifacts_dir": str(artifacts_dir),
"workflow_summary": str(summary_path) if summary_path.is_file() else None,
"checkpoint": str(checkpoint) if str(checkpoint) else None,
"checkpoint_present": checkpoint.is_file() if str(checkpoint) else False,
"checkpoint_bytes": checkpoint.stat().st_size if checkpoint.is_file() else None,
"embedding_sidecars": summary.get("embedding_sidecars", []),
"num_embedding_sidecars": len(summary.get("embedding_sidecars", [])),
"inference_outputs": [str(p) for p in inference_outputs],
"num_inference_outputs": len(inference_outputs),
"all_inference_outputs_present": all(p.is_file() for p in inference_outputs),
}
def _empty_output(output_dir: Path) -> dict[str, Any]:
return {
"directory": str(output_dir),
"artifacts_dir": str(output_dir / "artifacts"),
"workflow_summary": None,
"checkpoint": None,
"checkpoint_present": False,
"checkpoint_bytes": None,
"embedding_sidecars": [],
"num_embedding_sidecars": 0,
"inference_outputs": [],
"num_inference_outputs": 0,
"all_inference_outputs_present": False,
}
def _payload(
args: argparse.Namespace,
dataset: dict[str, Any],
upstream_root: Path | None,
checked_roots: list[str],
command_plan: list[list[str]],
exit_code: int,
elapsed: float,
stdout: str = "",
stderr: str = "",
) -> dict[str, Any]:
output = (
_summarize_output(args.output_dir)
if exit_code == 0 and not args.preflight
else _empty_output(args.output_dir)
)
return {
"skill": SKILL_NAME,
"model": VERSION,
"model_repo": UPSTREAM_REPO,
"license": "Apache-2.0",
"input": {
**dataset,
"epochs": args.epochs,
"num_gpus": args.num_gpus,
"amp": not args.no_amp,
"modality": args.modality,
"run_inference": bool(args.run_inference),
"train_from_scratch": bool(args.train_from_scratch),
},
"output": output,
"invocation": {
"official_entrypoint": UPSTREAM_ENTRYPOINT,
"upstream_root": str(upstream_root) if upstream_root else None,
"upstream_commit": _git_commit(upstream_root) if upstream_root else "",
"upstream_identity": _upstream_identity(upstream_root) if upstream_root else None,
"checked_upstream_roots": checked_roots,
"command": command_plan[0] if command_plan else [],
"command_plan": command_plan,
"exit_code": exit_code,
"subprocess_seconds": elapsed,
},
"runtime": {
"subprocess_seconds": elapsed,
"device": "cuda" if args.num_gpus > 0 else "cpu",
"preflight_only": bool(args.preflight),
},
"logs": {"stdout_tail": _tail(stdout), "stderr_tail": _tail(stderr)},
"intended_use_disclaimer": (
"Engineering wrapper for synthetic MR-brain diffusion model finetuning; "
"not for clinical interpretation, regulatory use, or production training data approval."
),
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("datalist", type=Path)
parser.add_argument("--data-base-dir", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--upstream-root")
# Config sources: edit these JSONs (or the upstream defaults) to tune training.
parser.add_argument(
"--env-config", help="Override environment JSON (default: upstream MR-brain env config)"
)
parser.add_argument(
"--model-config",
help="Override model-config JSON holding training/inference hyperparameters",
)
parser.add_argument("--model-def", help="Override network-definition JSON")
parser.add_argument("--modality", default="mri_t1", choices=SUPPORTED_MODALITIES)
parser.add_argument(
"--epochs",
type=int,
default=2,
help="Overrides diffusion_unet_train.n_epochs in the model config",
)
parser.add_argument("--num-gpus", type=int, default=1)
parser.add_argument("--no-amp", action="store_true")
parser.add_argument("--top-region-index", type=_parse_region, default=[0, 1, 0, 0])
parser.add_argument("--bottom-region-index", type=_parse_region, default=[0, 0, 1, 0])
parser.add_argument("--existing-ckpt-filepath", type=Path)
parser.add_argument("--trained-autoencoder-path", type=Path)
parser.add_argument("--model-filename", default="")
parser.add_argument("--download-model-data", action="store_true")
parser.add_argument("--train-from-scratch", action="store_true")
parser.add_argument("--skip-create-training-data", action="store_true")
parser.add_argument("--skip-train", action="store_true")
parser.add_argument("--run-inference", action="store_true")
parser.add_argument("--preflight", action="store_true")
return parser
def main() -> None:
args = build_parser().parse_args()
args.output_dir.mkdir(parents=True, exist_ok=True)
dataset = _validate_datalist(
args.data_base_dir.resolve(), args.datalist.resolve(), args.modality
)
upstream_root, checked = _resolve_upstream_root(args.upstream_root)
start = time.time()
command_plan = _build_command_plan(args, upstream_root or DEFAULT_UPSTREAM)
if upstream_root is None and args.preflight:
payload = _payload(args, dataset, None, checked, command_plan, 0, time.time() - start)
payload["logs"]["stderr_tail"] = (
"Preflight did not find an NV-Generate-CTMR checkout containing the existing "
"diffusion training scripts. A real training run requires NV_GENERATE_ROOT to "
"point at a current NVIDIA-Medtech/NV-Generate-CTMR checkout."
)
_emit(payload)
return
if upstream_root is None:
payload = _payload(args, dataset, None, checked, command_plan, 2, time.time() - start)
payload["logs"]["stderr_tail"] = (
"NV-Generate-CTMR checkout with diffusion training scripts was not found. "
"Set NV_GENERATE_ROOT or pass --upstream-root."
)
_emit(payload)
raise SystemExit(2)
upstream_identity = _upstream_identity(upstream_root)
if not upstream_identity["trusted"]:
payload = _payload(
args, dataset, upstream_root, checked, command_plan, 2, time.time() - start
)
payload["logs"]["stderr_tail"] = (
"Use the exact clean NV-Generate-CTMR checkout documented in SKILL.md; "
"tracked source edits and other commits are rejected before upstream code execution."
)
_emit(payload)
raise SystemExit(2)
try:
staged = _stage_configs(args, upstream_root)
command_plan = _build_command_plan(args, upstream_root, staged)
except Exception as exc:
payload = _payload(
args,
dataset,
upstream_root,
checked,
command_plan,
2,
time.time() - start,
stderr=str(exc),
)
_emit(payload)
raise SystemExit(2)
if args.preflight:
payload = _payload(
args, dataset, upstream_root, checked, command_plan, 0, time.time() - start
)
payload["logs"][
"stderr_tail"
] = "Preflight staged configs and validated existing upstream script entrypoints."
_emit(payload)
return
cache_dir = args.output_dir / "cache"
env = _child_process_env(
{
"CUDA_CACHE_PATH": str(cache_dir / "cuda"),
"HF_HOME": str(cache_dir / "huggingface"),
"HF_HUB_CACHE": str(cache_dir / "huggingface" / "hub"),
"MPLCONFIGDIR": str(cache_dir / "matplotlib"),
"XDG_CACHE_HOME": str(cache_dir / "xdg"),
}
)
exit_code, stdout, stderr, command_plan = _run_workflow(args, upstream_root, staged, env)
payload = _payload(
args,
dataset,
upstream_root,
checked,
command_plan,
exit_code,
time.time() - start,
stdout,
stderr,
)
_emit(payload)
raise SystemExit(exit_code)
if __name__ == "__main__":
main()
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Needs an API key, Needs a command-line tool, Needs Python
Security
What our scanner noticed· Medium risk
- Large base64-encoded blobs
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- NVIDIA MedTech Team
- Publisher
- NVIDIA (Official)
- Source
- NVIDIA/skills · skills/nv-generate-mr-brain-finetune · commit fce3cca
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE-APACHE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by NVIDIA MedTech Team or NVIDIA.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download nv-generate-mr-brain-finetune.zip