From 12d11af775d5eeda1ad6ef6877e0c6703e9d1a87 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 26 May 2026 13:53:43 +0200 Subject: [PATCH 01/10] autodetect license or quit in long_prepare_template --- recon_surf/long_prepare_template.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index a9f5da8f5..d0cd75f10 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -226,6 +226,8 @@ fi # check that SUBJECTS_DIR exists check_create_subjects_dir_properties "$SUBJECTS_DIR" +auto_detect_fs_license "the longitudinal template preparation" || exit $? + ################################## SETUP and LOGFILE ############################## @@ -456,4 +458,3 @@ do RunIt "$cmd" "$LF" done - From 79a81d02ceaf36adb4afc4076dd4d0d4fb4f03df Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 26 May 2026 14:22:53 +0200 Subject: [PATCH 02/10] replace mri_coreg with neuroreg coreg --- HypVINN/README.md | 6 ++--- HypVINN/run_prediction.py | 14 +++++----- HypVINN/utils/__init__.py | 2 +- HypVINN/utils/preproc.py | 57 ++++++++++----------------------------- run_fastsurfer.sh | 21 +++++++-------- 5 files changed, 34 insertions(+), 66 deletions(-) diff --git a/HypVINN/README.md b/HypVINN/README.md index 558e175c5..e6fc67eb1 100644 --- a/HypVINN/README.md +++ b/HypVINN/README.md @@ -7,7 +7,7 @@ Hypothalamic subfields segmentation pipeline ### Requirements * Same as FastSurfer. -* If the T1w and T2w images are available and not co-registered, FreeSurfer should be sourced to run the registration code, and the mri_coreg and mri_vol2vol binaries should also be available. +* If the T1w and T2w images are available and not co-registered, HypVINN can register the T2w image to the T1w reference internally. ### Model weights * EUDAT (FZ Jülich) data repository: https://b2share.fz-juelich.de/records/2af6da63d5c1414b832c1f606bbd068a @@ -30,7 +30,7 @@ Note: These weights (version 1.1) are retrained compared to paper ([version 1.0] * `--t2 ` : T2 image path * `--seg_log` : Path to file in which run logs will be saved. If not set logs will be stored in `/sd/sid/scripts/hypvinn_seg.log` ### Image processing options - * `--reg_mode` : Ignored, if no T2 image is passed. Specifies the registration method used to register T1 and T2 images. Options are 'coreg' (default) for mri_coreg, 'robust' for mri_robust_register, and 'none' to skip registration (this requires T1 and T2 are externally co-registered). + * `--reg_mode` : Ignored, if no T2 image is passed. Specifies the registration method used to register T1 and T2 images. Options are 'coreg' (default) for `neuroreg.coreg` and 'none' to skip registration (this requires T1 and T2 are externally co-registered). * `--qc_snap`: Activate the creation of QC snapshots of the predicted HypVINN segmentation in `/sd/sid/qc_snapshots`. The created QC snapshots are created to simplify the visual quality control process. ### FastSurfer Technical parameters (see FastSurfer documentation) * `--device` @@ -104,7 +104,7 @@ Note: These weights (version 1.1) are retrained compared to paper ([version 1.0] |--hypothalamus.HypVINN.nii.gz(Hypothalamus Segmentation) |-- hypothalamus_mask.HypVINN.nii.gz (Hypothalamus Segmentation Mask) |-- transforms - |-- t2tot1.lta (FreeSurfer registration file, only available if registration is performed) + |-- t2tot1.lta (T2-to-T1 registration transform, only available if registration is performed) |-- qc_snapshots : QC outputs (optional) |-- hypothalamus.HypVINN_qc_screenshoot.png (Coronal quality control image) |-- stats : Statistics outputs diff --git a/HypVINN/run_prediction.py b/HypVINN/run_prediction.py index 95fbb2c88..0abe34829 100644 --- a/HypVINN/run_prediction.py +++ b/HypVINN/run_prediction.py @@ -109,11 +109,11 @@ def option_parse() -> argparse.ArgumentParser: "--reg_mode", type=str, default="coreg", - choices=["none", "coreg", "robust"], - help="Freesurfer Registration type to run. coreg: mri_coreg, " - "robust : mri_robust_register, none: entirely deactivates " - "registration of T2 to T1, if both images are passed, " - "images need to be register properly externally.", + choices=["none", "coreg"], + help="Registration type to run for T2-to-T1 alignment when both images " + "are passed. coreg: neuroreg.coreg, none: entirely deactivates " + "registration of T2 to T1, so images need to be registered " + "properly externally.", ) parser.add_argument( @@ -156,7 +156,7 @@ def main( hypo_maskfile: str = HYPVINN_MASK_NAME, qc_snapshots: bool = False, threads: int | None = None, - reg_mode: Literal["coreg", "robust", "none"] = "coreg", + reg_mode: Literal["coreg", "none"] = "coreg", batch_size: int = 1, async_io: bool = False, device: str = "auto", @@ -195,7 +195,7 @@ def main( Whether to create QC snapshots. threads : int, optional If not None, updates the FastSurfer global setting in `FastSurfer.utils.parallel`. - reg_mode : "coreg", "robust", "none", default="coreg" + reg_mode : "coreg", "none", default="coreg" The registration mode to use. batch_size : int, default=1 The batch size to use. diff --git a/HypVINN/utils/__init__.py b/HypVINN/utils/__init__.py index 64cd0b1c4..0753985d1 100644 --- a/HypVINN/utils/__init__.py +++ b/HypVINN/utils/__init__.py @@ -14,4 +14,4 @@ class ViewOperationDefinition(TypedDict): ViewOperations = dict[Plane, ViewOperationDefinition | None] ModalityMode = Literal["t1", "t2", "t1t2"] ModalityDict = dict[Literal["t1", "t2"], ndarray] -RegistrationMode = Literal["robust", "coreg", "none"] +RegistrationMode = Literal["coreg", "none"] diff --git a/HypVINN/utils/preproc.py b/HypVINN/utils/preproc.py index 8460250ab..f558555e4 100644 --- a/HypVINN/utils/preproc.py +++ b/HypVINN/utils/preproc.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import time from pathlib import Path from typing import cast @@ -36,7 +35,7 @@ def t1_to_t2_registration( threads: int = -1, ) -> Path: """ - Register T1 to T2 images using either mri_coreg or mri_robust_register. + Register the T2 image to the T1 reference using neuroreg. Parameters ---------- @@ -49,7 +48,7 @@ def t1_to_t2_registration( lta_path : Path The path to the lta transform. registration_type : RegistrationMode, default="coreg" - The type of registration to be used. It can be either "coreg" or "robust". + The type of registration to be used. It can be either "coreg" or "none". threads : int, default=-1 The number of threads to be used. If it is less than or equal to 0, the number of threads will be automatically determined. @@ -62,49 +61,20 @@ def t1_to_t2_registration( Raises ------ RuntimeError - If mri_coreg, mri_vol2vol, or mri_robust_register fails to run or if they cannot - be found. + If the requested neuroreg registration backend fails. """ - import shutil - - from FastSurferCNN.utils.parallel import get_num_threads - from FastSurferCNN.utils.run_tools import Popen - - if threads <= 0: - threads = get_num_threads() - - def from_freesurfer_home(fs_binary: str) -> str: - if not os.environ.get("FREESURFER_HOME", ""): - raise RuntimeError( - f"Could not find {fs_binary}, source FreeSurfer or set the FREESURFER_HOME environment variable" - ) - return os.environ["FREESURFER_HOME"] + "/bin/" + fs_binary - - def run_fs_binary(fs_binary: str, args: list[str]) -> int: - fs_binary = shutil.which(fs_binary) or from_freesurfer_home(fs_binary) - args = [fs_binary] + list(map(str, args)) - LOGGER.info("Running " + " ".join(args)) - retval = Popen(args).finish() - if retval.retcode != 0: - LOGGER.error(f"{fs_binary} failed with error code {retval.retcode}.") - raise RuntimeError(f"{fs_binary} failed") - - LOGGER.info(f"{fs_binary} finished in {retval.runtime}!") + from neuroreg import coreg if registration_type == "coreg": - run_fs_binary( - "mri_coreg", - ["--mov", t2_path, "--targ", t1_path, "--reg", lta_path, "--threads", str(threads)], - ) - run_fs_binary( - "mri_vol2vol", - ["--mov", t2_path, "--targ", t1_path, "--reg", lta_path, "--o", output_path, "--cubic", "--keep-precision"], + LOGGER.info("Running neuroreg.coreg for T2-to-T1 registration.") + coreg( + str(t2_path), + str(t1_path), + lta_name=str(lta_path), + mapped_name=str(output_path), ) else: - run_fs_binary( - "mri_robust_register", - ["--mov", t2_path, "--dst", t1_path, "--lta", lta_path, "--mapmov", output_path, "--cost", "NMI"], - ) + raise ValueError(f"Unknown registration type: {registration_type}") return output_path @@ -125,7 +95,8 @@ def hypvinn_preproc( mode : ModalityMode The mode for HypVINN. It should be "t1t2". reg_mode : RegistrationMode - The registration mode. If it is not "none", the function will register T1 to T2 images. + The registration mode. If it is not "none", the function will register the + T2 image to the T1 reference. t1_path : Path The path to the T1 image. t2_path : Path @@ -162,7 +133,7 @@ def hypvinn_preproc( f"T2 image will be interpolated to the resolution of the T1 image." ) - LOGGER.info("Registering T1 to T2 ...") + LOGGER.info("Registering T2 to T1 ...") t1_to_t2_registration( t1_path=t1_path, t2_path=t2_path, diff --git a/run_fastsurfer.sh b/run_fastsurfer.sh index 45a36262e..e22540d79 100755 --- a/run_fastsurfer.sh +++ b/run_fastsurfer.sh @@ -239,13 +239,13 @@ SEGMENTATION PIPELINE: --t2 *Optional* T2 full head input (must be externally biasfield corrected when called with --no_biasfield). Requires an ABSOLUTE Path! - --reg_mode + --reg_mode Ignored, if no T2 image is passed. - Specifies the registration method used to register T1 - and T2 images. Options are 'coreg' (default) for - mri_coreg, 'robust' for mri_robust_register, and 'none' - to skip registration (this requires T1 and T2 are - externally co-registered). + Specifies the registration method used to register T1 + and T2 images. Options are 'coreg' (default) for + neuroreg.coreg and 'none' to skip registration + (this requires T1 and T2 are externally + co-registered). --qc_snap Create QC snapshots in \$SUBJECTS_DIR/\$sid/qc_snapshots to simplify the QC process. @@ -515,8 +515,8 @@ case $key in --hypo_statsfile) hypo_statsfile="$1" ; shift ;; --reg_mode) mode=$(echo "$1" | tr "[:upper:]" "[:lower:]") - if [[ "$mode" =~ ^(none|coreg|robust)$ ]] ; then hypvinn_regmode="$mode" - else echo "Invalid --reg_mode option, must be 'none', 'coreg' or 'robust'." ; exit 1 + if [[ "$mode" =~ ^(none|coreg)$ ]] ; then hypvinn_regmode="$mode" + else echo "Invalid --reg_mode option, must be 'none' or 'coreg'." ; exit 1 fi shift # past value ;; @@ -844,9 +844,6 @@ if [[ "$run_seg_pipeline" == "true" ]] ; then if [[ "$run_biasfield" == "true" ]] && [[ "$run_talairach_registration" == "true" ]] ; then what_needs_license+=" and the talairach-registration in the segmentation pipeline" fi - if [[ -n "$t2" ]] && [[ "$hypvinn_regmode" != "none" ]] ; then - what_needs_license+=" and the T1-T2 registration in the segmentation pipeline" - fi fi if [[ -n "$what_needs_license" ]] then @@ -1402,7 +1399,7 @@ then if [[ "$run_hypvinn_module" == "true" ]] then echo "MODULE: HypVINN hypothalamus segmentation" >> "$exec_time_log" - # currently, the order of the T2 preprocessing only is registration to T1w + # currently, the T2 preprocessing step in HypVINN is registration to T1w cmd=($python "$hypvinndir/run_prediction.py" --sd "${sd}" --sid "${subject}" --reg_mode "$hypvinn_regmode" "${hypvinn_flags[@]}" --threads "$threads_seg" --async_io --batch_size "$batch_size" --seg_log "$seg_log" --device "$device" --viewagg_device "$viewagg" --t1) From dacd95045b14b4ebf0d6cf3c6d5e746d87d5c127 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 26 May 2026 14:59:52 +0200 Subject: [PATCH 03/10] add os import --- HypVINN/utils/preproc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/HypVINN/utils/preproc.py b/HypVINN/utils/preproc.py index f558555e4..a2f599972 100644 --- a/HypVINN/utils/preproc.py +++ b/HypVINN/utils/preproc.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import time from pathlib import Path from typing import cast From 31dc6e9a9724ad7f0a68b503324da3372ebce00e Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Thu, 28 May 2026 18:46:07 +0200 Subject: [PATCH 04/10] coreg keep-dtype --- HypVINN/utils/preproc.py | 1 + pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HypVINN/utils/preproc.py b/HypVINN/utils/preproc.py index a2f599972..020aa46fa 100644 --- a/HypVINN/utils/preproc.py +++ b/HypVINN/utils/preproc.py @@ -73,6 +73,7 @@ def t1_to_t2_registration( str(t1_path), lta_name=str(lta_path), mapped_name=str(output_path), + keep_dtype=True, ) else: raise ValueError(f"Unknown registration type: {registration_type}") diff --git a/pyproject.toml b/pyproject.toml index 619de8641..68616282f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'matplotlib>=3.7.1', 'meshpy>=2025.1.1', # needed for FastSurfer-CC 'monai>=1.4.0', # needed for FastSurfer-CC - 'neuroreg>=0.6.2', # needed for registration / etiv + 'neuroreg>=0.6.3', # needed for registration / etiv 'nibabel>=5.4.0', # needed to fix a bug in nibabel 'numpy>=1.25', 'packaging', diff --git a/requirements.txt b/requirements.txt index cfea3ea10..0a289f96a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,7 @@ multipledispatch==1.0.0 narwhals==2.21.0 networkx==3.6.1 neurolit==0.6.1 -neuroreg==0.6.2 +neuroreg==0.6.3 nibabel==5.4.2 numpy==2.4.4 packaging==26.2 From 49469a05ba0f49747079bc782075dbdc4bae955d Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Tue, 23 Jun 2026 13:26:44 +0200 Subject: [PATCH 05/10] migrated robust_template, make_upright and convert commands to neuroreg --- pyproject.toml | 2 +- recon_surf/long_prepare_template.sh | 69 ++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 68616282f..4bcb42d43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'matplotlib>=3.7.1', 'meshpy>=2025.1.1', # needed for FastSurfer-CC 'monai>=1.4.0', # needed for FastSurfer-CC - 'neuroreg>=0.6.3', # needed for registration / etiv + 'neuroreg>=0.8.1', # needed for registration / etiv 'nibabel>=5.4.0', # needed to fix a bug in nibabel 'numpy>=1.25', 'packaging', diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index d0cd75f10..e045f63bf 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -24,7 +24,7 @@ # to setup a base template for processing with a slightly modified version of run_fastsurfer. # # 1. It obtains a brainmask on all time points for a specific subject -# 2. It uses mri_robust_template to co-register all time points into a mid-space +# 2. It uses neuroreg's multireg to co-register all time points into a mid-space # and sets up the base (subject template) directory with the aligned images the median image # and the forward and backward rigid transformations # @@ -39,7 +39,8 @@ # - Add flag for adding a new time point to an existing base/template. # # FreeSurfer requirements: -# mri_convert, mri_robust_template, mri_mask, mri_concatenate_lta, make_upright +# mri_convert (input import), mri_mask, mri_diff (geometry consistency check) +# neuroreg requirements: multireg, segreg, vol2vol, lta # ################################################################################################### @@ -68,6 +69,7 @@ robust_template_avg_arg=1 # median for template creation (if more than 1 time p # default arguments python="python3 -s" # avoid user-directory package inclusion sd="$SUBJECTS_DIR" +device="" # torch device for neuroreg (empty/auto = let neuroreg pick GPU else CPU) # init variables that need to be passed tid="" @@ -163,7 +165,9 @@ case $key in --t1s) while [[ $# -gt 0 ]] && [[ $1 != -* ]] ; do t1s+=("$1") ; shift ; done ;; --sd) export SUBJECTS_DIR="$1" ; shift ;; # these flags are passed through to run_prediction.py - --vox_size|--device|--viewagg_device|--conform_to_1mm_threshold) run_pred_flags+=("$key" "$1") ; shift ;; + --vox_size|--viewagg_device|--conform_to_1mm_threshold) run_pred_flags+=("$key" "$1") ; shift ;; + # device is passed through to run_prediction.py and also forwarded to neuroreg + --device) run_pred_flags+=("$key" "$1") ; device="$1" ; shift ;; --threads|--threads_seg) run_pred_flags+=("--threads" "$1") ; shift ;; --batch) run_pred_flags+=("--batch_size" "$1") ; shift ;; # these known arguments get ignored @@ -403,38 +407,60 @@ done if [ ${#tpids[@]} == 1 ] then - # if only a single time point, create fake 'base' by making the image upright - # this assures that also subjects with a single time point get processed as the other - # subjects in the longitudinal stream + # If only a single time point, we still create a 'base' so that single-tp subjects are + # processed like the others. Instead of FreeSurfer's make_upright (mirror-symmetry + # uprighting), we rigidly register this time point's segmentation to the reference centroid + # template - the same way the talairach initialization prealigns - which yields a + # reproducible, template-aligned (upright) base pose without scaling the anatomy. The + # segmentation always exists here (it was created above to obtain the brainmask). The base + # lives in the time point's own (conformed) geometry. + + seg0="$SUBJECTS_DIR/$tid/long-inputs/${tpids[0]}/cross_aparc+aseg.orig${extension}" + reference_centroids="mni_icbm152_t1_tal_nlin_asym_09c" + + # 1. rigid segmentation-to-template registration: tp -> base pose (RAS-to-RAS transform) + cmd="$python -m neuroreg.cli.segreg --seg $seg0 --centroids $reference_centroids" + cmd="$cmd --dof 6 --lta ${ltaXforms[0]}" + RunIt "$cmd" "$LF" - # 1. make the norm upright (base space) - cmd="make_upright ${normInVols[0]} \ - ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension} ${ltaXforms[0]}" + # 2. create the base brainmask by mapping the norm into the base pose. The transform is + # RAS-to-RAS, so the target geometry is taken from the input itself (--ref), keeping + # the base in the time point geometry. + cmd="$python -m neuroreg.cli.vol2vol --mov ${normInVols[0]} --transform ${ltaXforms[0]}" + cmd="$cmd --ref ${normInVols[0]} --interp cubic" + cmd="$cmd --out ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension}" RunIt "$cmd" "$LF" - # 2. create the upright orig volume - cmd="mri_convert -rt cubic \ - -at ${ltaXforms[0]} ${subjInVols[0]} ${SUBJECTS_DIR}/$tid/mri/orig.mgz" + # 3. create the base orig volume the same way + cmd="$python -m neuroreg.cli.vol2vol --mov ${subjInVols[0]} --transform ${ltaXforms[0]}" + cmd="$cmd --ref ${subjInVols[0]} --interp cubic" + cmd="$cmd --out ${SUBJECTS_DIR}/$tid/mri/orig.mgz" RunIt "$cmd" "$LF" else #more than 1 time point: + # forward the device to neuroreg if explicitly requested (empty/auto = neuroreg auto-selects) + device_opt="" + if [[ -n "$device" ]] && [[ "$device" != "auto" ]] ; then device_opt="--device $device" ; fi - # create the 'mean/median' norm volume: - cmd="mri_robust_template --mov ${normInVols[*]}" + # robust co-registration of all time points into an unbiased mid-space, creating the + # 'mean/median' norm (brainmask) volume and the forward transforms (tp -> base): + cmd="$python -m neuroreg.cli.multireg --mov ${normInVols[*]}" cmd="$cmd --lta ${ltaXforms[*]}" cmd="$cmd --template ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension}" cmd="$cmd --average ${robust_template_avg_arg}" - cmd="$cmd --sat 4.685" + cmd="$cmd --sat 4.685 $device_opt" RunIt "$cmd" "$LF" - # create the 'mean/median' input (orig) volume: - cmd="mri_robust_template --mov ${subjInVols[*]}" + # create the 'mean/median' input (orig) volume by re-using the transforms above (no + # registration, --noit); the per-tp LTAs carry the base geometry so orig.mgz lands on + # the same grid as base_brainmask: + cmd="$python -m neuroreg.cli.multireg --mov ${subjInVols[*]}" cmd="$cmd --average ${robust_template_avg_arg}" cmd="$cmd --ixforms ${ltaXforms[*]}" cmd="$cmd --noit" t1=${SUBJECTS_DIR}/$tid/mri/orig.mgz - cmd="$cmd --template $t1" + cmd="$cmd --template $t1 $device_opt" RunIt "$cmd" "$LF" fi # more than one time point @@ -453,8 +479,11 @@ done for ((i=0;i<${#tpids[@]};++i)) do mdir="$SUBJECTS_DIR/$tid/long-inputs/${tpids[i]}" - # map orig to base space - cmd="mri_convert -at ${ltaXforms[$i]} -rt $interpol $mdir/cross_input${extension} $mdir/long_conform${extension}" + # map orig to base space; the transforms are RAS-to-RAS, so the base geometry is supplied + # explicitly via --ref (rather than relying on a target geometry stored in the transform) + cmd="$python -m neuroreg.cli.vol2vol --mov $mdir/cross_input${extension}" + cmd="$cmd --transform ${ltaXforms[$i]} --ref ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension}" + cmd="$cmd --interp $interpol --out $mdir/long_conform${extension}" RunIt "$cmd" "$LF" done From 3f12f2eebf78f6bb3a1fdab88705f08f123f16fa Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 24 Jun 2026 12:39:20 +0200 Subject: [PATCH 06/10] fix spelling --- recon_surf/long_prepare_template.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index e045f63bf..839d6508f 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -452,7 +452,7 @@ else #more than 1 time point: cmd="$cmd --sat 4.685 $device_opt" RunIt "$cmd" "$LF" - # create the 'mean/median' input (orig) volume by re-using the transforms above (no + # create the 'mean/median' input (orig) volume by reusing the transforms above (no # registration, --noit); the per-tp LTAs carry the base geometry so orig.mgz lands on # the same grid as base_brainmask: cmd="$python -m neuroreg.cli.multireg --mov ${subjInVols[*]}" From 012979b6f309590224da63290332db397b2790dd Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Thu, 25 Jun 2026 00:27:01 +0200 Subject: [PATCH 07/10] replace format conversion mri_convert by neuroreg.cli.vol2vol --- pyproject.toml | 2 +- recon_surf/long_prepare_template.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4bcb42d43..7e7c472b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'matplotlib>=3.7.1', 'meshpy>=2025.1.1', # needed for FastSurfer-CC 'monai>=1.4.0', # needed for FastSurfer-CC - 'neuroreg>=0.8.1', # needed for registration / etiv + 'neuroreg>=0.9.0', # needed for registration / etiv 'nibabel>=5.4.0', # needed to fix a bug in nibabel 'numpy>=1.25', 'packaging', diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index 839d6508f..8c3993427 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -39,7 +39,7 @@ # - Add flag for adding a new time point to an existing base/template. # # FreeSurfer requirements: -# mri_convert (input import), mri_mask, mri_diff (geometry consistency check) +# mri_mask, mri_diff (geometry consistency check) # neuroreg requirements: multireg, segreg, vol2vol, lta # ################################################################################################### @@ -337,7 +337,7 @@ for ((i=0;i<${#tpids[@]};++i)); do mkdir -p "$mdir" # Import (copy) raw inputs (convert to extension format) t1input=$mdir/cross_input${extension} - cmd="mri_convert ${t1s[i]} $t1input" + cmd="$python -m neuroreg.cli.vol2vol --mov ${t1s[i]} --out $t1input" RunIt "$cmd" "$LF" # conform !!!!!!! should we conform to some common value, determined from all time points?? !!!!!! From c87226d09ba44d02ca1e25e615fc4bd2f7b5c219 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Thu, 25 Jun 2026 08:32:13 +0200 Subject: [PATCH 08/10] replace several image convert and masking with neuroreg vol2vol --- recon_surf/long_prepare_template.sh | 4 ++-- recon_surf/recon-surf.sh | 8 ++++---- recon_surf/talairach-reg.sh | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index 8c3993427..e17d08ca6 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -39,7 +39,7 @@ # - Add flag for adding a new time point to an existing base/template. # # FreeSurfer requirements: -# mri_mask, mri_diff (geometry consistency check) +# mri_diff (geometry consistency check) # neuroreg requirements: multireg, segreg, vol2vol, lta # ################################################################################################### @@ -366,7 +366,7 @@ for ((i=0;i<${#tpids[@]};++i)); do run_it "$LF" "${cmda[@]}" # mask is binary, we need to use on conformed image: - cmda=(mri_mask "$conformed_name" "$mask_name" "$mdir/cross_brainmask${extension}") + cmda=($python -m neuroreg.cli.vol2vol --mov "$conformed_name" --mask "$mask_name" --out "$mdir/cross_brainmask${extension}") run_it "$LF" "${cmda[@]}" done diff --git a/recon_surf/recon-surf.sh b/recon_surf/recon-surf.sh index 8bfca5a82..367b00550 100755 --- a/recon_surf/recon-surf.sh +++ b/recon_surf/recon-surf.sh @@ -468,7 +468,7 @@ fi # create orig.mgz and aparc.DKTatlas+aseg.orig.mgz (copy of T1 and segmentation) # also ensures .mgz format (in case inputs are nifti) -cmd="mri_convert $t1 $mdir/orig.mgz" +cmd="$python -m neuroreg.cli.vol2vol --mov $t1 --out $mdir/orig.mgz" RunIt "$cmd" "$LF" asegdkt_segfile_manedit=$(add_file_suffix "$asegdkt_segfile" "manedit") @@ -477,7 +477,7 @@ if [[ ! "$asegdkt_segfile_manedit" =~ (\.manedit){2,}\. ]] && [[ -f "$asegdkt_se then asegdkt_segfile="$asegdkt_segfile_manedit" # use the manedit file fi -cmd="mri_convert $asegdkt_segfile $mdir/aparc.DKTatlas+aseg.orig.mgz" +cmd="$python -m neuroreg.cli.vol2vol --mov $asegdkt_segfile --out $mdir/aparc.DKTatlas+aseg.orig.mgz" RunIt "$cmd" "$LF" # link original T1 input to rawavg (needed by pctsurfcon) @@ -597,7 +597,7 @@ fi # the difference between nu and orig_nu is the fact that nu has the talairach-registration header # create norm by masking nu (supports manedit-ed mask) -cmda=(mri_mask "$mdir/nu.mgz" "$mask" "$mdir/norm.mgz") +cmda=($python -m neuroreg.cli.vol2vol --mov "$mdir/nu.mgz" --mask "$mask" --out "$mdir/norm.mgz") run_it "$LF" "${cmda[@]}" if [[ "$get_t1" == "true" ]] then @@ -618,7 +618,7 @@ then cmda=(mri_normalize -g 1 -seed 1234 -mprage "$mdir/nu.mgz" "$mdir/T1.mgz" "${noconform_if_hires[@]}") run_it "$LF" "${cmda[@]}" # create brainmask by masking T1 (supports manedit-ed mask) - cmda=(mri_mask "$mdir/T1.mgz" "$mask" "$mdir/brainmask.mgz") + cmda=($python -m neuroreg.cli.vol2vol --mov "$mdir/T1.mgz" --mask "$mask" --out "$mdir/brainmask.mgz") run_it "$LF" "${cmda[@]}" else # create brainmask by linkage to norm.mgz (masked nu.mgz) diff --git a/recon_surf/talairach-reg.sh b/recon_surf/talairach-reg.sh index 9d00c28f8..ddc7c0b73 100755 --- a/recon_surf/talairach-reg.sh +++ b/recon_surf/talairach-reg.sh @@ -162,7 +162,7 @@ else prealigned_name=$mdir/segreg_prealigned.mgz target_geom="$FREESURFER_HOME/average/mni305.cor.mgz" # only used for its conformed 1mm header; does not imply registration to this template - cmd=(mri_convert --apply_transform "$prealigned_lta" --reslice_like "$target_geom" "$norm_name" "$prealigned_name") + cmd=($python -m neuroreg.cli.vol2vol --mov "$norm_name" --transform "$prealigned_lta" --ref "$target_geom" --interp linear --out "$prealigned_name") run_it "$LF" "${cmd[@]}" # talairach.xfm: compute talairach full head (25sec) From f488c2a26bfc846ae5729b90d3bd13e6e1a1203a Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Fri, 26 Jun 2026 16:41:43 +0200 Subject: [PATCH 09/10] move more to neuroreg mri* instead of vol2vol etc --- recon_surf/long_prepare_template.sh | 18 ++++++++---------- recon_surf/recon-surf.sh | 8 ++++---- recon_surf/talairach-reg.sh | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/recon_surf/long_prepare_template.sh b/recon_surf/long_prepare_template.sh index e17d08ca6..d24259088 100755 --- a/recon_surf/long_prepare_template.sh +++ b/recon_surf/long_prepare_template.sh @@ -38,9 +38,7 @@ # - Maybe use intersection of tp masks as brainmask for base, as done in FreeSurfer. # - Add flag for adding a new time point to an existing base/template. # -# FreeSurfer requirements: -# mri_diff (geometry consistency check) -# neuroreg requirements: multireg, segreg, vol2vol, lta +# neuroreg requirements: multireg, segreg, vol2vol, lta, mri (mask, diff, info) # ################################################################################################### @@ -289,7 +287,7 @@ do # check if geometry differs across time if [[ "$s" != "${t1s[0]}" ]] then - cmda=(mri_diff --notallow-pix --notallow-geo "$s" "${t1s[0]}" --res-thresh "0.000001") + cmda=($python -m neuroreg.cli.mri diff --notallow-pix --notallow-geo "$s" "${t1s[0]}" --res-thresh "0.000001") difftext=$("${cmda[@]}") retcode=${PIPESTATUS[0]} if [[ "$retcode" != 0 ]] ; then geodiff+="Comparing $s and ${t1s[0]} (code $retcode):\n$difftext\n" ; fi @@ -302,7 +300,7 @@ then echo "*******************************************************************************" echo "WARNING: Image parameters differ across time, maybe due to acquisition changes?" echo " Consistent changes in, e.g., resolution can potentially bias a " - echo " longitudinal study! You can check image parameters by running mri_info" + echo " longitudinal study! You can check image parameters by running neuroreg mri info" echo " on each input image." echo "*******************************************************************************" echo "$geodiff" @@ -337,7 +335,7 @@ for ((i=0;i<${#tpids[@]};++i)); do mkdir -p "$mdir" # Import (copy) raw inputs (convert to extension format) t1input=$mdir/cross_input${extension} - cmd="$python -m neuroreg.cli.vol2vol --mov ${t1s[i]} --out $t1input" + cmd="$python -m neuroreg.cli.vol2vol --in ${t1s[i]} --out $t1input" RunIt "$cmd" "$LF" # conform !!!!!!! should we conform to some common value, determined from all time points?? !!!!!! @@ -366,7 +364,7 @@ for ((i=0;i<${#tpids[@]};++i)); do run_it "$LF" "${cmda[@]}" # mask is binary, we need to use on conformed image: - cmda=($python -m neuroreg.cli.vol2vol --mov "$conformed_name" --mask "$mask_name" --out "$mdir/cross_brainmask${extension}") + cmda=($python -m neuroreg.cli.mri mask "$conformed_name" "$mask_name" "$mdir/cross_brainmask${extension}") run_it "$LF" "${cmda[@]}" done @@ -426,13 +424,13 @@ then # 2. create the base brainmask by mapping the norm into the base pose. The transform is # RAS-to-RAS, so the target geometry is taken from the input itself (--ref), keeping # the base in the time point geometry. - cmd="$python -m neuroreg.cli.vol2vol --mov ${normInVols[0]} --transform ${ltaXforms[0]}" + cmd="$python -m neuroreg.cli.vol2vol --in ${normInVols[0]} --transform ${ltaXforms[0]}" cmd="$cmd --ref ${normInVols[0]} --interp cubic" cmd="$cmd --out ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension}" RunIt "$cmd" "$LF" # 3. create the base orig volume the same way - cmd="$python -m neuroreg.cli.vol2vol --mov ${subjInVols[0]} --transform ${ltaXforms[0]}" + cmd="$python -m neuroreg.cli.vol2vol --in ${subjInVols[0]} --transform ${ltaXforms[0]}" cmd="$cmd --ref ${subjInVols[0]} --interp cubic" cmd="$cmd --out ${SUBJECTS_DIR}/$tid/mri/orig.mgz" RunIt "$cmd" "$LF" @@ -481,7 +479,7 @@ do mdir="$SUBJECTS_DIR/$tid/long-inputs/${tpids[i]}" # map orig to base space; the transforms are RAS-to-RAS, so the base geometry is supplied # explicitly via --ref (rather than relying on a target geometry stored in the transform) - cmd="$python -m neuroreg.cli.vol2vol --mov $mdir/cross_input${extension}" + cmd="$python -m neuroreg.cli.vol2vol --in $mdir/cross_input${extension}" cmd="$cmd --transform ${ltaXforms[$i]} --ref ${SUBJECTS_DIR}/$tid/mri/base_brainmask${extension}" cmd="$cmd --interp $interpol --out $mdir/long_conform${extension}" RunIt "$cmd" "$LF" diff --git a/recon_surf/recon-surf.sh b/recon_surf/recon-surf.sh index 367b00550..fe937a266 100755 --- a/recon_surf/recon-surf.sh +++ b/recon_surf/recon-surf.sh @@ -468,7 +468,7 @@ fi # create orig.mgz and aparc.DKTatlas+aseg.orig.mgz (copy of T1 and segmentation) # also ensures .mgz format (in case inputs are nifti) -cmd="$python -m neuroreg.cli.vol2vol --mov $t1 --out $mdir/orig.mgz" +cmd="$python -m neuroreg.cli.vol2vol --in $t1 --out $mdir/orig.mgz" RunIt "$cmd" "$LF" asegdkt_segfile_manedit=$(add_file_suffix "$asegdkt_segfile" "manedit") @@ -477,7 +477,7 @@ if [[ ! "$asegdkt_segfile_manedit" =~ (\.manedit){2,}\. ]] && [[ -f "$asegdkt_se then asegdkt_segfile="$asegdkt_segfile_manedit" # use the manedit file fi -cmd="$python -m neuroreg.cli.vol2vol --mov $asegdkt_segfile --out $mdir/aparc.DKTatlas+aseg.orig.mgz" +cmd="$python -m neuroreg.cli.vol2vol --in $asegdkt_segfile --out $mdir/aparc.DKTatlas+aseg.orig.mgz" RunIt "$cmd" "$LF" # link original T1 input to rawavg (needed by pctsurfcon) @@ -597,7 +597,7 @@ fi # the difference between nu and orig_nu is the fact that nu has the talairach-registration header # create norm by masking nu (supports manedit-ed mask) -cmda=($python -m neuroreg.cli.vol2vol --mov "$mdir/nu.mgz" --mask "$mask" --out "$mdir/norm.mgz") +cmda=($python -m neuroreg.cli.mri mask "$mdir/nu.mgz" "$mask" "$mdir/norm.mgz") run_it "$LF" "${cmda[@]}" if [[ "$get_t1" == "true" ]] then @@ -618,7 +618,7 @@ then cmda=(mri_normalize -g 1 -seed 1234 -mprage "$mdir/nu.mgz" "$mdir/T1.mgz" "${noconform_if_hires[@]}") run_it "$LF" "${cmda[@]}" # create brainmask by masking T1 (supports manedit-ed mask) - cmda=($python -m neuroreg.cli.vol2vol --mov "$mdir/T1.mgz" --mask "$mask" --out "$mdir/brainmask.mgz") + cmda=($python -m neuroreg.cli.mri mask "$mdir/T1.mgz" "$mask" "$mdir/brainmask.mgz") run_it "$LF" "${cmda[@]}" else # create brainmask by linkage to norm.mgz (masked nu.mgz) diff --git a/recon_surf/talairach-reg.sh b/recon_surf/talairach-reg.sh index ddc7c0b73..2e80c06b6 100755 --- a/recon_surf/talairach-reg.sh +++ b/recon_surf/talairach-reg.sh @@ -162,7 +162,7 @@ else prealigned_name=$mdir/segreg_prealigned.mgz target_geom="$FREESURFER_HOME/average/mni305.cor.mgz" # only used for its conformed 1mm header; does not imply registration to this template - cmd=($python -m neuroreg.cli.vol2vol --mov "$norm_name" --transform "$prealigned_lta" --ref "$target_geom" --interp linear --out "$prealigned_name") + cmd=($python -m neuroreg.cli.vol2vol --in "$norm_name" --transform "$prealigned_lta" --ref "$target_geom" --interp linear --out "$prealigned_name") run_it "$LF" "${cmd[@]}" # talairach.xfm: compute talairach full head (25sec) From d7f2dd4e53d3f4aca93e96cee7e3ca2093795cf4 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Sat, 27 Jun 2026 19:49:10 +0200 Subject: [PATCH 10/10] bump required neuroreg --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7e7c472b6..988d3faf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'matplotlib>=3.7.1', 'meshpy>=2025.1.1', # needed for FastSurfer-CC 'monai>=1.4.0', # needed for FastSurfer-CC - 'neuroreg>=0.9.0', # needed for registration / etiv + 'neuroreg>=0.10.1', # needed for registration / etiv 'nibabel>=5.4.0', # needed to fix a bug in nibabel 'numpy>=1.25', 'packaging', diff --git a/requirements.txt b/requirements.txt index 0a289f96a..0a0204299 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,7 @@ multipledispatch==1.0.0 narwhals==2.21.0 networkx==3.6.1 neurolit==0.6.1 -neuroreg==0.6.3 +neuroreg==0.10.1 nibabel==5.4.2 numpy==2.4.4 packaging==26.2