22from numbers import Number
33from typing import Union
44
5- import nibabel as nib
65import numpy as np
76import torch
7+ from nibabel .affines import apply_affine
88
9- from ....data .image import LabelMap
9+ from ....data .image import Image
1010from ....data .subject import Subject
1111from .bounds_transform import BoundsTransform
1212from .bounds_transform import TypeBounds
@@ -31,7 +31,10 @@ class Pad(BoundsTransform):
3131 :math:`w_{ini} = w_{fin} = h_{ini} = h_{fin} =
3232 d_{ini} = d_{fin} = n`.
3333 padding_mode: See possible modes in `NumPy docs`_. If it is a number,
34- the mode will be set to ``'constant'``.
34+ the mode will be set to ``'constant'``. If it is ``'mean'``,
35+ ``'maximum'``, ``'median'`` or ``'minimum'``, the statistic will be
36+ computed from the whole volume, unlike in NumPy, which computes it
37+ along the padded axis.
3538 **kwargs: See :class:`~torchio.transforms.Transform` for additional
3639 keyword arguments.
3740
@@ -78,26 +81,49 @@ def check_padding_mode(cls, padding_mode):
7881 )
7982 raise KeyError (message )
8083
84+ def _check_truncation (self , image : Image , mode : Union [str , float ]) -> None :
85+ if mode not in ('mean' , 'median' ):
86+ return
87+ if torch .is_floating_point (image .data ):
88+ return
89+ message = (
90+ f'The constant value computed for padding mode "{ mode } " might '
91+ ' be truncated in the output, as the input image is not'
92+ 'floating point. Consider converting the image to a floating'
93+ ' point type before applying this transform.'
94+ )
95+ warnings .warn (message , RuntimeWarning , stacklevel = 2 )
96+
8197 def apply_transform (self , subject : Subject ) -> Subject :
8298 assert self .bounds_parameters is not None
8399 low = self .bounds_parameters [::2 ]
84100 for image in self .get_images (subject ):
85- if isinstance (image , LabelMap ) and self .padding_mode == 'mean' :
86- message = (
87- 'Padding mode "mean" might create non-integer values in label maps'
88- )
89- warnings .warn (message , RuntimeWarning , stacklevel = 2 )
90- new_origin = nib .affines .apply_affine (image .affine , - np .array (low ))
101+ self ._check_truncation (image , self .padding_mode )
102+ new_origin = apply_affine (image .affine , - np .array (low ))
91103 new_affine = image .affine .copy ()
92104 new_affine [:3 , 3 ] = new_origin
93- kwargs : dict [str , Union [str , float ]]
105+
106+ mode : str | float = 'constant'
107+ constant : torch .Tensor | float | None = None
108+ kwargs : dict [str , str | float | torch .Tensor ] = {}
94109 if isinstance (self .padding_mode , Number ):
95- kwargs = {
96- 'mode' : 'constant' ,
97- 'constant_values' : self .padding_mode ,
98- }
110+ constant = self .padding_mode # type: ignore[assignment]
111+ elif self .padding_mode == 'maximum' :
112+ constant = image .data .max ()
113+ elif self .padding_mode == 'mean' :
114+ constant = image .data .float ().mean ()
115+ elif self .padding_mode == 'median' :
116+ constant = torch .quantile (image .data .float (), 0.5 )
117+ elif self .padding_mode == 'minimum' :
118+ constant = image .data .min ()
99119 else :
100- kwargs = {'mode' : self .padding_mode }
120+ constant = None
121+ mode = self .padding_mode
122+
123+ if constant is not None :
124+ kwargs ['constant_values' ] = constant
125+ kwargs ['mode' ] = mode
126+
101127 pad_params = self .bounds_parameters
102128 paddings = (0 , 0 ), pad_params [:2 ], pad_params [2 :4 ], pad_params [4 :]
103129 padded = np .pad (image .data , paddings , ** kwargs ) # type: ignore[call-overload]
0 commit comments