Repository: aramis-lab/clinica

Issue:
Inconsistent cropped image size for ADNI FLAIR
Hello, 

I ran flair-linear on ADNI and the output images have a shape of (169, **205**, 179), different than that of the usual template size (169, **208**, 179) from the documentation. 

After investigation it appears that this comes from the size of  _input_image_ (line 433 in utils/image.py : _input_image.get_fdata()_ ) which is 3 pixel short on the y-axis compared to the coordinates of _MNI_CROP_BBOX_ : _input_image_ is of size (182, **218**, 182), and _MNI_CROP_BBOX_ has coordinates (12, 181, 13, **221**, 0, 179).

This can be problematic when using these images as inputs for neural networks, especially when working with multiple datasets with some for which the final cropped image dimensions do match the template.

Perhaps the size of the images could be checked earlier on in the pipeline and corrected if necessary to ensure a consistent final cropped size while not altering the registration to the MNI space ?

Best,

Discussion:
@NicolasGensollen: Hi @manonheff 

Thanks for reporting. I think there are two things:

First, it is not normal that the code is not raising an error when using a bounding box that goes outside of the images's limits. That will not solve the problem but at least it wouldn't silently process the images, leading to subtle bugs later on like you mentioned.

Second, I'm a bit surprised that we have two MNI templates of different shapes, one for T1 and one for Flair, but only one bounding box:

```python
>>> import nibabel as nib
>>> from clinica.utils.image import get_mni_template, get_mni_cropped_template
>>> nib.load(get_mni_template("t1")).shape
(193, 229, 193)
>>> nib.load(get_mni_template("flair")).shape
(182, 218, 182)
>>> nib.load(get_mni_cropped_template()).shape
(169, 208, 179)
```

The bounding box is indeed of the dimensions of the expected cropped template (which seem to be the same for t1 and flair), but I believe the issue is that we would need a second bounding box, specific to flair.

Does this make sense ?@manonheff: Hello,

Having a bounding box specific for the flair template makes sense, indeed. I'm surprised however that previous flair datasets processed with flair linear did not have the same issue. I have an old version of MSSEG (unfortunately I do not have the version of clinica used to process), where the uncropped image has the dimension of the flair template you mentioned, but the cropped version of the image matches the dimensions of the mni_cropped_template.@NicolasGensollen: It is due to the fact that we "recently" changed the cropping step of the pipelines t1-linear, flair-linear, and pet-linear. Previously these pipelines were not really cropping the images, but were resampling them to the reference template. Since this was considered a waste of resources (performing resampling when cropping is immediate), it was decided to change this step to perform pure cropping instead. You can check the details in issue #1214 and PR #1215 

Back then, we computed the bounding box using the MNI template of T1 before and after cropping. We had access to both and it was just a matter of finding the coordinates of the bounding box, that, when applied on the original template, will give the cropped one.
It seems like we missed the fact that the flair template is different and should have its own bounding box.

I need to take a closer look to see how we can find the bounding box for Flair, or if we go back to resampling in this case.@NicolasGensollen: After investigation, it seems like the cropped template is assumed to be the same for T1 and flair although it shouldn't be. The files `https://aramislab.paris.inria.fr/files/data/img_flair_linear/ref_cropped_template.nii.gz` and `https://aramislab.paris.inria.fr/files/data/img_t1_linear/ref_cropped_template.nii.gz` seem to be the same while I would expect the former to be the cropped version of `https://aramislab.paris.inria.fr/files/data/img_flair_linear/GG-853-FLAIR-1.0mm.nii.gz` and the latter the cropped version of `https://aramislab.paris.inria.fr/files/data/img_t1_linear/mni_icbm152_t1_tal_nlin_sym_09c.nii`.

Because of that, I cannot infer the bounding box for Flair (at least in the same way we did for T1).

I think, one possible way forward would be to try cropping the images and, if the bounding box is not coherent with the image size, then default to resampling the image to the template. This would at least produce something with expected dimensions while still using pure cropping when possible.

Solution patch:
diff --git a/clinica/utils/exceptions.py b/clinica/utils/exceptions.py
index 2dfb1360..bcea3420 100644
--- a/clinica/utils/exceptions.py
+++ b/clinica/utils/exceptions.py
@@ -34,6 +34,10 @@ class ClinicaExistingDatasetError(ClinicaDatasetError):
         )
 
 
+class ClinicaImageDimensionError(ClinicaException):
+    """Base class for errors linked to image dimensions."""
+
+
 class ClinicaParserError(ClinicaException):
     """Base class for parser errors."""
 
diff --git a/clinica/utils/image.py b/clinica/utils/image.py
index 8b04fe97..c9c41e44 100644
--- a/clinica/utils/image.py
+++ b/clinica/utils/image.py
@@ -300,10 +300,26 @@ class Bbox3D:
 MNI_CROP_BBOX = Bbox3D.from_coordinates(12, 181, 13, 221, 0, 179)
 
 
+def _is_bbox_within_array(array: np.ndarray, bbox: Bbox3D) -> bool:
+    if not (0 <= bbox.x_slice.start and bbox.x_slice.end <= array.shape[0]):
+        return False
+    if not (0 <= bbox.y_slice.start and bbox.y_slice.end <= array.shape[1]):
+        return False
+    if not (0 <= bbox.z_slice.start and bbox.z_slice.end <= array.shape[2]):
+        return False
+    return True
+
+
 def _crop_array(array: np.ndarray, bbox: Bbox3D) -> np.ndarray:
     # TODO: When Python 3.10 is dropped, replace with 'return array[*bbox.get_slices()]'
-    x, y, z = bbox.get_slices()
-    return array[x, y, z]
+    from clinica.utils.exceptions import ClinicaImageDimensionError
+
+    if _is_bbox_within_array(array, bbox):
+        x, y, z = bbox.get_slices()
+        return array[x, y, z]
+    raise ClinicaImageDimensionError(
+        f"Cannot use the bounding box {bbox} to crop the provided array of shape {array.shape}."
+    )
 
 
 def _get_file_locally_or_download(
@@ -387,7 +403,7 @@ def _get_mni_template_flair() -> Path:
     )
 
 
-def crop_nifti(input_image: Path, output_dir: Optional[Path] = None) -> Path:
+def crop_nifti(input_image_path: Path, output_dir: Optional[Path] = None) -> Path:
     """Crop input image.
 
     The function expects a 3D anatomical image and will crop it
@@ -397,9 +413,13 @@ def crop_nifti(input_image: Path, output_dir: Optional[Path] = None) -> Path:
     into the cropped template (located in
     clinica/resources/masks/ref_cropped_template.nii.gz).
 
+    If the bounding box falls outside of the image limits, the
+    function will perform resampling of the input image onto the
+    reference cropped template.
+
     Parameters
     ----------
-    input_image : Path
+    input_image_path : Path
         The path to the input image to be cropped.
 
     output_dir : Path, optional
@@ -413,25 +433,42 @@ def crop_nifti(input_image: Path, output_dir: Optional[Path] = None) -> Path:
 
     Raises
     ------
-    ValueError:
-        If the input image is not 3D.
+    ClinicaImageDimensionError:
+        If the input image is not 3D or if the output image has unexpected dimension.
     """
-    from nilearn.image import new_img_like
+    from nilearn.image import new_img_like, resample_to_img
 
+    from clinica.utils.exceptions import ClinicaImageDimensionError
     from clinica.utils.filemanip import get_filename_no_ext
+    from clinica.utils.stream import log_and_warn
 
-    filename_no_ext = get_filename_no_ext(input_image)
-    input_image = nib.load(input_image)
+    filename_no_ext = get_filename_no_ext(input_image_path)
+    input_image = nib.load(input_image_path)
+    reference_image = nib.load(get_mni_cropped_template())
     if len(input_image.shape) != 3:
-        raise ValueError(
+        raise ClinicaImageDimensionError(
             "The function crop_nifti is implemented for anatomical 3D images. "
             f"You provided an image of shape {input_image.shape}."
         )
     output_dir = output_dir or Path.cwd()
-    crop_img = new_img_like(
-        nib.load(get_mni_cropped_template()),
-        _crop_array(input_image.get_fdata(), MNI_CROP_BBOX),
-    )
+    try:
+        cropped_array = _crop_array(input_image.get_fdata(), MNI_CROP_BBOX)
+        crop_img = new_img_like(reference_image, cropped_array)
+    except ClinicaImageDimensionError:
+        log_and_warn(
+            (
+                f"The image {input_image_path} has dimensions {input_image.get_fdata().shape} and cannot be "
+                f"cropped using the bounding box {MNI_CROP_BBOX}. The `crop_nifti` function will try to resample the "
+                f"input image to the reference template {get_mni_cropped_template()} instead of cropping."
+            ),
+            UserWarning,
+        )
+        crop_img = resample_to_img(input_image, reference_image, force_resample=True)
+    if crop_img.shape != reference_image.shape:
+        raise ClinicaImageDimensionError(
+            f"The cropped image has shape {crop_img.shape} different from the expected shape "
+            f"{reference_image.shape} of the reference template {get_mni_cropped_template()}."
+        )
     output_img = output_dir / f"{filename_no_ext}_cropped.nii.gz"
     crop_img.to_filename(output_img)
 


Test patch:
diff --git a/.github/workflows/test_converters.yml b/.github/workflows/test_converters.yml
index db806f8a..9978fe40 100644
--- a/.github/workflows/test_converters.yml
+++ b/.github/workflows/test_converters.yml
@@ -14,26 +14,26 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  test-converters-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run non-regression tests for converters
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/run_converters_mac.xml \
-          --disable-warnings \
-          ./nonregression/iotools/test_run_converters.py
+#  test-converters-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run non-regression tests for converters
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/run_converters_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/iotools/test_run_converters.py
 
   test-converters-Linux:
     runs-on:
diff --git a/.github/workflows/test_instantiation.yml b/.github/workflows/test_instantiation.yml
index d28561a3..a79a1225 100644
--- a/.github/workflows/test_instantiation.yml
+++ b/.github/workflows/test_instantiation.yml
@@ -14,28 +14,28 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  test-instantiation-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run instantiation tests
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica.all
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/instantiation_mac.xml \
-          --disable-warnings \
-          ./instantiation/
+#  test-instantiation-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run instantiation tests
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica.all
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/instantiation_mac.xml \
+#          --disable-warnings \
+#          ./instantiation/
 
   test-instantiation-Linux:
     runs-on:
diff --git a/.github/workflows/test_non_regression_fast.yml b/.github/workflows/test_non_regression_fast.yml
index 365d7e82..2fab9bb4 100644
--- a/.github/workflows/test_non_regression_fast.yml
+++ b/.github/workflows/test_non_regression_fast.yml
@@ -13,28 +13,28 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  test-non-regression-fast-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run fast non regression tests
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica.all
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --junitxml=./test-reports/non_regression_fast_mac.xml \
-          --disable-warnings \
-          -m "fast" \
-          ./nonregression/
+#  test-non-regression-fast-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run fast non regression tests
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica.all
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --junitxml=./test-reports/non_regression_fast_mac.xml \
+#          --disable-warnings \
+#          -m "fast" \
+#          ./nonregression/
 
   test-non-regression-fast-Linux:
     runs-on:
diff --git a/.github/workflows/test_pipelines_anat.yml b/.github/workflows/test_pipelines_anat.yml
index 21cee730..331ede87 100644
--- a/.github/workflows/test_pipelines_anat.yml
+++ b/.github/workflows/test_pipelines_anat.yml
@@ -8,54 +8,54 @@ permissions:
   contents: read
 
 jobs:
-  test-t1-linear-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 120
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for t1 linear pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica/ants/2.4.4
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_anat_t1_linear_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/anat/test_t1_linear.py
-
-  test-t1-volume-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 720
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for t1 volume pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica/matlab/2019b
-          module load clinica/spm12/r7771
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_anat_t1_volume_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/anat/test_t1_volume.py
+#  test-t1-linear-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 120
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for t1 linear pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica/ants/2.4.4
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_anat_t1_linear_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/anat/test_t1_linear.py
+#
+#  test-t1-volume-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 720
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for t1 volume pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica/matlab/2019b
+#          module load clinica/spm12/r7771
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_anat_t1_volume_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/anat/test_t1_volume.py
 
   test-t1-linear-Linux:
     runs-on:
diff --git a/.github/workflows/test_pipelines_anat_freesurfer.yml b/.github/workflows/test_pipelines_anat_freesurfer.yml
index 55cae260..5eb78003 100644
--- a/.github/workflows/test_pipelines_anat_freesurfer.yml
+++ b/.github/workflows/test_pipelines_anat_freesurfer.yml
@@ -8,29 +8,29 @@ permissions:
   contents: read
 
 jobs:
-  test-pipelines-anat-freesurfer-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 1440
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for anat pipelines using freesurfer
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica/freesurfer/6.0.0
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_anat_t1_freesurfer_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/anat/test_t1_freesurfer.py
+#  test-pipelines-anat-freesurfer-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 1440
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for anat pipelines using freesurfer
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica/freesurfer/6.0.0
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_anat_t1_freesurfer_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/anat/test_t1_freesurfer.py
 
   test-pipelines-anat-freesurfer-Linux:
     runs-on:
diff --git a/.github/workflows/test_pipelines_dwi.yml b/.github/workflows/test_pipelines_dwi.yml
index 34e87914..db6403af 100644
--- a/.github/workflows/test_pipelines_dwi.yml
+++ b/.github/workflows/test_pipelines_dwi.yml
@@ -8,31 +8,31 @@ permissions:
   contents: read
 
 jobs:
-  test-dwi-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 720
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for dwi pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica/fsl/6.0.5
-          module load clinica/ants/2.4.4
-          module load clinica/freesurfer/6.0.0
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_dwi_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/dwi/test_pipelines.py
+#  test-dwi-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 720
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for dwi pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica/fsl/6.0.5
+#          module load clinica/ants/2.4.4
+#          module load clinica/freesurfer/6.0.0
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_dwi_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/dwi/test_pipelines.py
 
   test-dwi-Linux:
     runs-on:
diff --git a/.github/workflows/test_pipelines_dwi_preprocessing.yml b/.github/workflows/test_pipelines_dwi_preprocessing.yml
index c5e81e50..228809d3 100644
--- a/.github/workflows/test_pipelines_dwi_preprocessing.yml
+++ b/.github/workflows/test_pipelines_dwi_preprocessing.yml
@@ -8,53 +8,53 @@ permissions:
   contents: read
 
 jobs:
-  test-dwi-preproc-t1-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 720
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for dwi pre-processing using t1 pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica.all
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_dwi_preproc_using_t1_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/dwi/preprocessing/test_t1.py
-
-  test-dwi-preproc-phasediff-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 720
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for dwi pre-processing using phasediff pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica.all
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_dwi_preproc_using_phasediff_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/dwi/preprocessing/test_phase_diff.py
+#  test-dwi-preproc-t1-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 720
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for dwi pre-processing using t1 pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica.all
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_dwi_preproc_using_t1_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/dwi/preprocessing/test_t1.py
+#
+#  test-dwi-preproc-phasediff-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 720
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for dwi pre-processing using phasediff pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica.all
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_dwi_preproc_using_phasediff_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/dwi/preprocessing/test_phase_diff.py
 
   test-dwi-preproc-t1-Linux:
     runs-on:
diff --git a/.github/workflows/test_pipelines_pet.yml b/.github/workflows/test_pipelines_pet.yml
index 30411395..1d5b13cd 100644
--- a/.github/workflows/test_pipelines_pet.yml
+++ b/.github/workflows/test_pipelines_pet.yml
@@ -8,29 +8,29 @@ permissions:
   contents: read
 
 jobs:
-  test-pipelines-pet-MacOS:
-    runs-on:
-      - self-hosted
-      - macOS
-    timeout-minutes: 720
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run tests for pet pipelines
-        run: |
-          make env.conda
-          source ~/miniconda3/etc/profile.d/conda.sh
-          conda activate "${{ github.workspace }}"/env
-          source "$(brew --prefix)/opt/modules/init/bash"
-          module load clinica.all
-          make install
-          cd test
-          poetry run pytest --verbose \
-          --working_directory=/Volumes/data/working_dir_mac \
-          --input_data_directory=/Volumes/data_ci \
-          --basetemp=/Volumes/data/tmp \
-          --junitxml=./test-reports/non_regression_pet_mac.xml \
-          --disable-warnings \
-          ./nonregression/pipelines/pet
+#  test-pipelines-pet-MacOS:
+#    runs-on:
+#      - self-hosted
+#      - macOS
+#    timeout-minutes: 720
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run tests for pet pipelines
+#        run: |
+#          make env.conda
+#          source ~/miniconda3/etc/profile.d/conda.sh
+#          conda activate "${{ github.workspace }}"/env
+#          source "$(brew --prefix)/opt/modules/init/bash"
+#          module load clinica.all
+#          make install
+#          cd test
+#          poetry run pytest --verbose \
+#          --working_directory=/Volumes/data/working_dir_mac \
+#          --input_data_directory=/Volumes/data_ci \
+#          --basetemp=/Volumes/data/tmp \
+#          --junitxml=./test-reports/non_regression_pet_mac.xml \
+#          --disable-warnings \
+#          ./nonregression/pipelines/pet
 
   test-pipelines-pet-Linux:
     runs-on:
diff --git a/test/unittests/utils/test_image.py b/test/unittests/utils/test_image.py
index 6877049d..230868bb 100644
--- a/test/unittests/utils/test_image.py
+++ b/test/unittests/utils/test_image.py
@@ -5,6 +5,7 @@ import numpy as np
 import pytest
 from numpy.testing import assert_array_equal
 
+from clinica.utils.exceptions import ClinicaImageDimensionError
 from clinica.utils.testing_utils import (
     assert_nifti_equal,
     build_test_image_cubic_object,
@@ -192,6 +193,24 @@ def test_slice():
     assert s.get_slice() == slice(3, 16)
 
 
+@pytest.mark.parametrize(
+    "array,coords,expected",
+    [
+        (np.ones((10, 10, 10)), (3, 6, 0, 10, 6, 6), True),
+        (np.ones((10, 10, 10)), (3, 11, 0, 10, 6, 6), False),
+        (np.ones((10, 10, 10)), (3, 6, -1, 10, 6, 6), False),
+        (np.ones((10, 10, 10)), (1, 1, 1, 1, 1, 1), True),
+        (np.ones((10, 10, 10)), (11, 11, 11, 11, 11, 11), False),
+    ],
+)
+def test_is_bbox_within_array(
+    array: np.ndarray, coords: tuple[int, int, int, int, int, int], expected: bool
+):
+    from clinica.utils.image import Bbox3D, _is_bbox_within_array  # noqa
+
+    assert _is_bbox_within_array(array, Bbox3D.from_coordinates(*coords)) is expected
+
+
 def test_mni_cropped_bbox():
     from clinica.utils.image import MNI_CROP_BBOX  # noqa
 
@@ -253,7 +272,7 @@ def test_get_mni_template(tmp_path, mocker):
     assert img.shape == expected_shape
 
 
-def test_crop_nifti_error(tmp_path):
+def test_crop_nifti_input_image_not_3d_error(tmp_path):
     from clinica.utils.image import crop_nifti
 
     nib.Nifti1Image(np.random.random((10, 10, 10, 10)), np.eye(4)).to_filename(
@@ -261,7 +280,7 @@ def test_crop_nifti_error(tmp_path):
     )
 
     with pytest.raises(
-        ValueError,
+        ClinicaImageDimensionError,
         match=re.escape(
             "The function crop_nifti is implemented for anatomical 3D images. "
             "You provided an image of shape (10, 10, 10, 10)."
@@ -270,6 +289,26 @@ def test_crop_nifti_error(tmp_path):
         crop_nifti(tmp_path / "test.nii.gz")
 
 
+def test_crop_nifti_with_resampling(tmp_path):
+    from clinica.utils.image import (
+        crop_nifti,
+        get_mni_cropped_template,
+        get_mni_template,
+    )
+
+    nib.load(get_mni_template("flair")).to_filename(tmp_path / "mni.nii.gz")
+    with pytest.warns(
+        UserWarning,
+        match=re.escape(
+            f"The image {tmp_path / 'mni.nii.gz'} has dimensions (182, 218, 182) and cannot "
+            "be cropped using the bounding box ( ( 12, 181 ), ( 13, 221 ), ( 0, 179 ) ). "
+            "The `crop_nifti` function will try to resample the input image to the reference template"
+        ),
+    ):
+        cropped = crop_nifti(tmp_path / "mni.nii.gz", output_dir=tmp_path)
+        assert nib.load(cropped).shape == nib.load(get_mni_cropped_template()).shape
+
+
 def test_crop_nifti(tmp_path):
     from clinica.utils.image import (
         crop_nifti,