Passed
Push — master ( 1c5439...729305 )
by Fernando
01:12
created

Pad.check_padding_mode()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
from numbers import Number
2
from typing import Union
3
4
import numpy as np
5
import nibabel as nib
6
import torch
7
8
from ....data.subject import Subject
9
from .bounds_transform import BoundsTransform, TypeBounds
10
11
12
class Pad(BoundsTransform):
13
    r"""Pad an image.
14
15
    Args:
16
        padding: Tuple
17
            :math:`(w_{ini}, w_{fin}, h_{ini}, h_{fin}, d_{ini}, d_{fin})`
18
            defining the number of values padded to the edges of each axis.
19
            If the initial shape of the image is
20
            :math:`W \times H \times D`, the final shape will be
21
            :math:`(w_{ini} + W + w_{fin}) \times (h_{ini} + H + h_{fin})
22
            \times (d_{ini} + D + d_{fin})`.
23
            If only three values :math:`(w, h, d)` are provided, then
24
            :math:`w_{ini} = w_{fin} = w`,
25
            :math:`h_{ini} = h_{fin} = h` and
26
            :math:`d_{ini} = d_{fin} = d`.
27
            If only one value :math:`n` is provided, then
28
            :math:`w_{ini} = w_{fin} = h_{ini} = h_{fin} =
29
            d_{ini} = d_{fin} = n`.
30
        padding_mode: See possible modes in `NumPy docs`_. If it is a number,
31
            the mode will be set to ``'constant'``.
32
        **kwargs: See :class:`~torchio.transforms.Transform` for additional keyword arguments.
33
34
    .. _NumPy docs: https://numpy.org/doc/stable/reference/generated/numpy.pad.html
35
    """
36
37
    PADDING_MODES = (
38
        'empty',
39
        'edge',
40
        'wrap',
41
        'constant',
42
        'linear_ramp',
43
        'maximum',
44
        'mean',
45
        'median',
46
        'minimum',
47
        'reflect',
48
        'symmetric',
49
    )
50
51
    def __init__(
52
            self,
53
            padding: TypeBounds,
54
            padding_mode: Union[str, float] = 0,
55
            **kwargs
56
            ):
57
        super().__init__(padding, **kwargs)
58
        self.padding = padding
59
        self.check_padding_mode(padding_mode)
60
        self.padding_mode = padding_mode
61
        self.args_names = 'padding', 'padding_mode'
62
63
    @classmethod
64
    def check_padding_mode(cls, padding_mode):
65
        if not (padding_mode in cls.PADDING_MODES or isinstance(padding_mode, Number)):
66
            message = (
67
                f'Padding mode "{padding_mode}" not valid. Valid options are'
68
                f' {list(cls.PADDING_MODES)} or a number'
69
            )
70
            raise KeyError(message)
71
72
    def apply_transform(self, subject: Subject) -> Subject:
73
        low = self.bounds_parameters[::2]
74
        for image in self.get_images(subject):
75
            new_origin = nib.affines.apply_affine(image.affine, -np.array(low))
76
            new_affine = image.affine.copy()
77
            new_affine[:3, 3] = new_origin
78
            if isinstance(self.padding_mode, Number):
79
                kwargs = {'mode': 'constant',
80
                          'constant_values': self.padding_mode}
81
            else:
82
                kwargs = {'mode': self.padding_mode}
83
            pad_params = self.bounds_parameters
84
            paddings = (0, 0), pad_params[:2], pad_params[2:4], pad_params[4:]
85
            padded = np.pad(image.data, paddings, **kwargs)
86
            image.data = torch.from_numpy(padded)
87
            image.affine = new_affine
88
        return subject
89
90
    def inverse(self):
91
        from .crop import Crop
92
        return Crop(self.padding)
93