From a7674dada9f08fceb240228541614f9be37ddf10 Mon Sep 17 00:00:00 2001 From: santiestrada32 Date: Tue, 4 Aug 2026 11:35:09 +0200 Subject: [PATCH 1/4] Modify fov computation, particullary the decimal precision on the voxel size. --- FastSurferCNN/data_loader/conform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FastSurferCNN/data_loader/conform.py b/FastSurferCNN/data_loader/conform.py index 840dd22f2..10b105da0 100644 --- a/FastSurferCNN/data_loader/conform.py +++ b/FastSurferCNN/data_loader/conform.py @@ -1600,7 +1600,7 @@ def conformed_vox_img_size( if target_vox_size is not None: # correct sizes for changing voxel size (if voxel size is changing) # compute field of view dimensions in mm (in native orientation) - fov = np.array(img.header.get_zooms()[:3]) * target_img_size + fov = np.array(np.round(img.header.get_zooms()[:3], decimals=int(np.ceil(-np.log10(vox_eps))))) * target_img_size # compute number of voxels needed to cover field of view target_img_size = np.ceil((fov / target_vox_size * 10000).astype(int).astype(float) / 10000).astype(int) # use cube (same size in all directions) with MAX_DIMENSION in each direction as minimum From 74f20c1282e1a7d3c8fbba6265336a2a236f3421 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 5 Aug 2026 15:53:06 +0200 Subject: [PATCH 2/4] Dedupe decimals rounding in conformed_vox_img_size and fix long line E501 --- FastSurferCNN/data_loader/conform.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FastSurferCNN/data_loader/conform.py b/FastSurferCNN/data_loader/conform.py index 10b105da0..c1cd5e4b1 100644 --- a/FastSurferCNN/data_loader/conform.py +++ b/FastSurferCNN/data_loader/conform.py @@ -1566,10 +1566,12 @@ def conformed_vox_img_size( target_img_size: IntVector3d | None MAX_VOX_SIZE = 1.0 MAX_DIMENSION = 256 + # number of decimals to round voxel sizes to, so that vox_eps-sized float noise does not affect results + decimals = int(np.ceil(-np.log10(vox_eps))) # this is similar to mri_convert --conform_min, note, vox_size == 'auto' is extra, but not covered by VoxSizeOption if isinstance(vox_size, str) and (vox_size := cast(VoxSizeOption, vox_size.lower())) in ["min", "auto"]: # find minimal voxel side length - min_vox_size = np.round(np.min(img.header.get_zooms()[:3]), decimals=int(np.ceil(-np.log10(vox_eps)))) + min_vox_size = np.round(np.min(img.header.get_zooms()[:3]), decimals=decimals) # set to 1 mm if larger than that _conformed_vox_size = min(min_vox_size, MAX_VOX_SIZE) if threshold_1mm and _conformed_vox_size > threshold_1mm: @@ -1600,7 +1602,7 @@ def conformed_vox_img_size( if target_vox_size is not None: # correct sizes for changing voxel size (if voxel size is changing) # compute field of view dimensions in mm (in native orientation) - fov = np.array(np.round(img.header.get_zooms()[:3], decimals=int(np.ceil(-np.log10(vox_eps))))) * target_img_size + fov = np.array(np.round(img.header.get_zooms()[:3], decimals=decimals)) * target_img_size # compute number of voxels needed to cover field of view target_img_size = np.ceil((fov / target_vox_size * 10000).astype(int).astype(float) / 10000).astype(int) # use cube (same size in all directions) with MAX_DIMENSION in each direction as minimum From 4f2aed0fe19af73d68eeaf40d74186d1021687b9 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 5 Aug 2026 16:25:04 +0200 Subject: [PATCH 3/4] Guard voxel-count rounding against float error with a relative tolerance The ceil() that turns field-of-view into a voxel count could round up on floating-point noise: storing the zoom as float32 leaves a relative residual (~1e-8) that, scaled by the voxel count, pushed an exact fit just over the integer (e.g. 320.0000048 -> 321). This is machine precision, not a voxel-size tolerance, and its absolute size grows with the image, so the previous fixed-tolerance guard (*10000/int/10000, ~1e-4) was both conceptually wrong and fragile for large images. Snap counts that are integer within a relative 1e-6 to the nearest integer, and ceil only genuine partial voxels. --- FastSurferCNN/data_loader/conform.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/FastSurferCNN/data_loader/conform.py b/FastSurferCNN/data_loader/conform.py index c1cd5e4b1..50374c40f 100644 --- a/FastSurferCNN/data_loader/conform.py +++ b/FastSurferCNN/data_loader/conform.py @@ -1603,8 +1603,17 @@ def conformed_vox_img_size( # correct sizes for changing voxel size (if voxel size is changing) # compute field of view dimensions in mm (in native orientation) fov = np.array(np.round(img.header.get_zooms()[:3], decimals=decimals)) * target_img_size - # compute number of voxels needed to cover field of view - target_img_size = np.ceil((fov / target_vox_size * 10000).astype(int).astype(float) / 10000).astype(int) + # number of voxels needed to cover the field of view + n_vox = fov / target_vox_size + # n_vox is integer when the fov is a multiple of the voxel size, but floating-point + # error makes it only approximately so. Storing the zoom as float32 leaves a relative + # residual of ~1e-8 that scales with the voxel count, i.e. machine precision rather + # than a voxel-size tolerance. Snap counts that are integer within that relative error + # and round up only genuine partial voxels. + rounded = np.rint(n_vox) + target_img_size = np.where( + np.isclose(n_vox, rounded, rtol=1e-6, atol=0.0), rounded, np.ceil(n_vox) + ).astype(int) # use cube (same size in all directions) with MAX_DIMENSION in each direction as minimum if _img_size == "auto": target_img_size = np.full_like(np.maximum(MAX_DIMENSION, target_img_size), np.amax(target_img_size)) From fde95d797e770b158f06d4adc6d494459701071f Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 5 Aug 2026 18:47:09 +0200 Subject: [PATCH 4/4] Validate vox_eps and clamp decimals in conformed_vox_img_size --- FastSurferCNN/data_loader/conform.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FastSurferCNN/data_loader/conform.py b/FastSurferCNN/data_loader/conform.py index 50374c40f..1e3316095 100644 --- a/FastSurferCNN/data_loader/conform.py +++ b/FastSurferCNN/data_loader/conform.py @@ -1566,8 +1566,11 @@ def conformed_vox_img_size( target_img_size: IntVector3d | None MAX_VOX_SIZE = 1.0 MAX_DIMENSION = 256 - # number of decimals to round voxel sizes to, so that vox_eps-sized float noise does not affect results - decimals = int(np.ceil(-np.log10(vox_eps))) + if vox_eps <= 0: + raise ValueError(f"vox_eps must be > 0, got {vox_eps}.") + # number of decimals to round voxel sizes to, so that vox_eps-sized float noise does not affect + # results; clamp at 0 so that vox_eps > 1 does not round voxel sizes to tens of mm + decimals = max(0, int(np.ceil(-np.log10(vox_eps)))) # this is similar to mri_convert --conform_min, note, vox_size == 'auto' is extra, but not covered by VoxSizeOption if isinstance(vox_size, str) and (vox_size := cast(VoxSizeOption, vox_size.lower())) in ["min", "auto"]: # find minimal voxel side length