Passed
Push — master ( 066103...5d2729 )
by Fernando
01:22
created

torchio.data.subject.Subject.spacing()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
import copy
2
import pprint
3
from typing import Any, Dict, List, Tuple
4
from ..torchio import TYPE, INTENSITY
5
from .image import Image
6
7
8
class Subject(dict):
9
    """Class to store information about the images corresponding to a subject.
10
11
    Args:
12
        *args: If provided, a dictionary of items.
13
        **kwargs: Items that will be added to the subject sample.
14
15
    Example:
16
17
        >>> import torchio
18
        >>> from torchio import Image, Subject
19
        >>> # One way:
20
        >>> subject = Subject(
21
        ...     one_image=Image('path_to_image.nii.gz', type=torchio.INTENSITY),
22
        ...     a_segmentation=Image('path_to_seg.nii.gz', type=torchio.LABEL),
23
        ...     age=45,
24
        ...     name='John Doe',
25
        ...     hospital='Hospital Juan Negrín',
26
        ... )
27
        >>> # If you want to create the mapping before, or have spaces in the keys:
28
        >>> subject_dict = {
29
        ...     'one image': Image('path_to_image.nii.gz', type=torchio.INTENSITY),
30
        ...     'a segmentation': Image('path_to_seg.nii.gz', type=torchio.LABEL),
31
        ...     'age': 45,
32
        ...     'name': 'John Doe',
33
        ...     'hospital': 'Hospital Juan Negrín',
34
        ... }
35
        >>> Subject(subject_dict)
36
37
    """
38
39
    def __init__(self, *args, **kwargs: Dict[str, Any]):
40
        if args:
41
            if len(args) == 1 and isinstance(args[0], dict):
42
                kwargs.update(args[0])
43
            else:
44
                message = (
45
                    'Only one dictionary as positional argument is allowed')
46
                raise ValueError(message)
47
        super().__init__(**kwargs)
48
        self.images = [
49
            (k, v) for (k, v) in self.items()
50
            if isinstance(v, Image)
51
        ]
52
        self._parse_images(self.images)
53
        self.__dict__.update(self)  # this allows me to do e.g. subject.t1
54
        self.history = []
55
56
    def __repr__(self):
57
        string = (
58
            f'{self.__class__.__name__}'
59
            f'(Keys: {tuple(self.keys())}; images: {len(self.images)})'
60
        )
61
        return string
62
63
    @staticmethod
64
    def _parse_images(images: List[Tuple[str, Image]]) -> None:
65
        # Check that it's not empty
66
        if not images:
67
            raise ValueError('A subject without images cannot be created')
68
69
    @property
70
    def shape(self):
71
        """Return shape of first image in subject.
72
73
        Consistency of shapes across images in the subject is checked first.
74
        """
75
        self.check_consistent_shape()
76
        image = self.get_images(intensity_only=False)[0]
77
        return image.shape
78
79
    @property
80
    def spatial_shape(self):
81
        """Return spatial shape of first image in subject.
82
83
        Consistency of shapes across images in the subject is checked first.
84
        """
85
        return self.shape[1:]
86
87
    @property
88
    def spacing(self):
89
        """Return spacing of first image in subject.
90
91
        Consistency of shapes across images in the subject is checked first.
92
        """
93
        self.check_consistent_shape()
94
        image = self.get_images(intensity_only=False)[0]
95
        return image.spacing
96
97
    def get_images_dict(self, intensity_only=True):
98
        images = {}
99
        for image_name, image in self.items():
100
            if not isinstance(image, Image):
101
                continue
102
            if intensity_only and not image[TYPE] == INTENSITY:
103
                continue
104
            images[image_name] = image
105
        return images
106
107
    def get_images(self, intensity_only=True):
108
        images_dict = self.get_images_dict(intensity_only=intensity_only)
109
        return list(images_dict.values())
110
111
    def check_consistent_shape(self) -> None:
112
        shapes_dict = {}
113
        iterable = self.get_images_dict(intensity_only=False).items()
114
        for image_name, image in iterable:
115
            shapes_dict[image_name] = image.shape
116
        num_unique_shapes = len(set(shapes_dict.values()))
117
        if num_unique_shapes > 1:
118
            message = (
119
                'Images in sample have inconsistent shapes:'
120
                f'\n{pprint.pformat(shapes_dict)}'
121
            )
122
            raise ValueError(message)
123
124
    def add_transform(
125
            self,
126
            transform: 'Transform',
127
            parameters_dict: dict,
128
            ) -> None:
129
        self.history.append((transform.name, parameters_dict))
130
131
    def load(self):
132
        for image in self.get_images(intensity_only=False):
133
            image.load()
134
135
    def crop(self, index_ini, index_fin):
136
        result_dict = {}
137
        for key, value in self.items():
138
            if isinstance(value, Image):
139
                # patch.clone() is much faster than copy.deepcopy(patch)
140
                value = value.crop(index_ini, index_fin)
141
            else:
142
                value = copy.deepcopy(value)
143
            result_dict[key] = value
144
        return Subject(result_dict)
145