• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ghiggi / ximage / 23169211772

16 Mar 2026 10:37PM UTC coverage: 94.953%. Remained the same
23169211772

Pull #21

github

web-flow
Merge 31815bcf3 into f10b9569e
Pull Request #21: [pre-commit.ci] pre-commit autoupdate

8 of 8 new or added lines in 8 files covered. (100.0%)

21 existing lines in 4 files now uncovered.

809 of 852 relevant lines covered (94.95%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/ximage/patch/checks.py
1
# -----------------------------------------------------------------------------.
2
# MIT License
3

4
# Copyright (c) 2024-2026 ximage developers
5
#
6
# This file is part of ximage.
7

8
# Permission is hereby granted, free of charge, to any person obtaining a copy
9
# of this software and associated documentation files (the "Software"), to deal
10
# in the Software without restriction, including without limitation the rights
11
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
# copies of the Software, and to permit persons to whom the Software is
13
# furnished to do so, subject to the following conditions:
14
#
15
# The above copyright notice and this permission notice shall be included in all
16
# copies or substantial portions of the Software.
17
#
18
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
# SOFTWARE.
25

26
# -----------------------------------------------------------------------------.
27
"""Checks for patch extraction function arguments."""
28
import numpy as np
1✔
29

30
from ximage.utils.checks import are_all_integers, are_all_natural_numbers
1✔
31

32

33
def _ensure_is_dict_argument(arg, dims, arg_name):
1✔
34
    """Ensure argument is a dictionary with same order as dims."""
35
    if isinstance(arg, (int, float)):
1✔
36
        arg = dict.fromkeys(dims, arg)
1✔
37
    if isinstance(arg, (list, tuple)):
1✔
38
        if len(arg) != len(dims):
1✔
39
            raise ValueError(f"{arg_name} must match the number of dimensions of the label array.")
1✔
40
        arg = dict(zip(dims, arg, strict=True))
1✔
41
    if isinstance(arg, dict):
1✔
42
        dict_dims = np.array(list(arg))
1✔
43
        invalid_dims = dict_dims[np.isin(dict_dims, dims, invert=True)].tolist()
1✔
44
        if len(invalid_dims) > 0:
1✔
45
            raise ValueError(f"{arg_name} must not contain dimensions {invalid_dims}. It expects only {dims}.")
1✔
46
        missing_dims = np.array(dims)[np.isin(dims, dict_dims, invert=True)].tolist()
1✔
47
        if len(missing_dims) > 0:
1✔
48
            raise ValueError(f"{arg_name} must contain also dimensions {missing_dims}")
1✔
49
    else:
50
        type_str = type(arg)
1✔
51
        raise TypeError(f"Unrecognized type {type_str} for argument {arg_name}.")
1✔
52
    # Reorder arguments as function of dims
53
    return {dim: arg[dim] for dim in dims}
1✔
54

55

56
def _replace_full_dimension_flag_value(arg, shape):
1✔
57
    """Replace -1 values with the corresponding dimension shape."""
58
    # Return argument with positive integer values
59
    return {dim: shape[i] if value == -1 else value for i, (dim, value) in enumerate(arg.items())}
1✔
60

61

62
def check_patch_size(patch_size, dims, shape):
1✔
63
    """
64
    Check the validity of the ``patch_size`` argument based on the array shape.
65

66
    Parameters
67
    ----------
68
    patch_size : (int, list, tuple, dict)
69
        The size of the patch to extract from the array.
70
        If int, the patch is a hypercube of size patch_size across all dimensions.
71
        If ``list`` or ``tuple``, the length must match the number of dimensions of the array.
72
        If a ``dict``, it must have as keys all array dimensions.
73
        The value -1 can be used to specify the full array dimension shape.
74
        Otherwise, only positive integers values (>1) are accepted.
75
    dims : tuple
76
        The names of the array dimensions.
77
    shape : tuple
78
        The shape of the array.
79

80
    Returns
81
    -------
82
    patch_size : dict
83
        The shape of the patch.
84
    """
85
    patch_size = _ensure_is_dict_argument(patch_size, dims=dims, arg_name="patch_size")
1✔
86
    patch_size = _replace_full_dimension_flag_value(patch_size, shape)
1✔
87
    # Check natural number
88
    for value in patch_size.values():
1✔
89
        if not are_all_natural_numbers(value):
1✔
90
            raise ValueError("Invalid 'patch_size' values. They must be only positive integer values.")
1✔
91
    # Check patch size is smaller than array shape
92
    idx_valid = [value <= max_value for value, max_value in zip(patch_size.values(), shape, strict=True)]
1✔
93
    max_allowed_patch_size = dict(zip(dims, shape, strict=True))
1✔
94
    if not all(idx_valid):
1✔
95
        raise ValueError(f"The maximum allowed patch_size values are {max_allowed_patch_size}")
1✔
96
    return patch_size
1✔
97

98

99
def check_kernel_size(kernel_size, dims, shape):
1✔
100
    """
101
    Check the validity of the kernel_size argument based on the array shape.
102

103
    Parameters
104
    ----------
105
    kernel_size : (int, list, tuple, dict)
106
        The size of the kernel to extract from the array.
107
        If ``int`` or ``float``, the kernel is a hypercube of size patch_size across all dimensions.
108
        If ``list`` or ``tuple``, the length must match the number of dimensions of the array.
109
        If a ``dict``, it must have has keys all array dimensions.
110
        The value -1 can be used to specify the full array dimension shape.
111
        Otherwise, only positive integers values (>1) are accepted.
112
    dims : tuple
113
        The names of the array dimensions.
114
    shape : tuple
115
        The shape of the array.
116

117
    Returns
118
    -------
119
    kernel_size : dict
120
        The shape of the kernel.
121
    """
122
    kernel_size = _ensure_is_dict_argument(kernel_size, dims=dims, arg_name="kernel_size")
1✔
123
    kernel_size = _replace_full_dimension_flag_value(kernel_size, shape)
1✔
124
    # Check natural number
125
    for value in kernel_size.values():
1✔
126
        if not are_all_natural_numbers(value):
1✔
127
            raise ValueError("Invalid 'kernel_size' values. They must be only positive integer values.")
1✔
128
    # Check patch size is smaller than array shape
129
    idx_valid = [value <= max_value for value, max_value in zip(kernel_size.values(), shape, strict=True)]
1✔
130
    max_allowed_kernel_size = dict(zip(dims, shape, strict=True))
1✔
131
    if not all(idx_valid):
1✔
132
        raise ValueError(f"The maximum allowed patch_size values are {max_allowed_kernel_size}.")
1✔
133
    return kernel_size
1✔
134

135

136
def check_buffer(buffer, dims, shape):
1✔
137
    """
138
    Check the validity of the buffer argument based on the array shape.
139

140
    Parameters
141
    ----------
142
    buffer : (int, float, list, tuple or dict)
143
        The size of the buffer to apply to the array.
144
        If ``int`` or ``float``, equal buffer is set on each dimension of the array.
145
        If ``list`` or ``tuple``, the length must match the number of dimensions of the array.
146
        If a ``dict``, it must have has keys all array dimensions.
147
    dims : tuple
148
        The names of the array dimensions.
149
    shape : tuple
150
        The shape of the array.
151

152
    Returns
153
    -------
154
    buffer : dict
155
        The buffer to apply on each dimension.
156
    """
157
    buffer = _ensure_is_dict_argument(buffer, dims=dims, arg_name="buffer")
1✔
158
    for value in buffer.values():
1✔
159
        if not are_all_integers(value):
1✔
160
            raise ValueError("Invalid 'buffer' values. They must be only integer values.")
1✔
161
    # Check buffer is smaller than half the array shape
162
    dict_max_values = {dim: int(np.floor(size / 2)) for dim, size in zip(buffer.keys(), shape, strict=True)}
1✔
163
    idx_valid = [value <= dict_max_values[dim] for dim, value in buffer.items()]
1✔
164
    if not all(idx_valid):
1✔
UNCOV
165
        raise ValueError(f"The maximum allowed 'buffer' values are {dict_max_values}.")
×
166
    return buffer
1✔
167

168

169
def check_padding(padding, dims, shape):
1✔
170
    """
171
    Check the validity of the padding argument based on the array shape.
172

173
    Parameters
174
    ----------
175
    padding : (int, float, list, tuple or dict)
176
        The size of the padding to apply to the array.
177
        If ``None``, zero padding is assumed.
178
        If ``int`` or ``float``, equal padding is set on each dimension of the array.
179
        If ``list`` or ``tuple``, the length must match the number of dimensions of the array.
180
        If a ``dict``, it must have has keys all array dimensions.
181
    dims : tuple
182
        The names of the array dimensions.
183
    shape : tuple
184
        The shape of the array.
185

186
    Returns
187
    -------
188
    padding : dict
189
        The padding to apply on each dimension.
190
    """
191
    padding = _ensure_is_dict_argument(padding, dims=dims, arg_name="padding")
1✔
192
    for value in padding.values():
1✔
193
        if not are_all_integers(value):
1✔
194
            raise ValueError("Invalid 'padding' values. They must be only integer values.")
1✔
195
    # Check padding is smaller than half the array shape
196
    dict_max_values = {dim: int(np.floor(size / 2)) for dim, size in zip(padding.keys(), shape, strict=True)}
1✔
197
    idx_valid = [value <= dict_max_values[dim] for dim, value in padding.items()]
1✔
198
    if not all(idx_valid):
1✔
UNCOV
199
        raise ValueError(f"The maximum allowed 'padding' values are {dict_max_values}.")
×
200
    return padding
1✔
201

202

203
def check_partitioning_method(partitioning_method):
1✔
204
    """Check partitioning method."""
205
    if not isinstance(partitioning_method, (str, type(None))):
1✔
206
        raise TypeError("'partitioning_method' must be either a string or None.")
1✔
207
    if isinstance(partitioning_method, str):
1✔
208
        valid_methods = ["sliding", "tiling"]
1✔
209
        if partitioning_method not in valid_methods:
1✔
210
            raise ValueError(f"Valid 'partitioning_method' are {valid_methods}.")
1✔
211
    return partitioning_method
1✔
212

213

214
def check_stride(stride, dims, shape, partitioning_method):
1✔
215
    """
216
    Check the validity of the stride argument based on the array shape.
217

218
    Parameters
219
    ----------
220
    stride : (None, int, float, list, tuple, dict)
221
        The size of the stride to apply to the array.
222
        If None, no striding is assumed.
223
        If ``int`` or ``float``, equal stride is set on each dimension of the array.
224
        If ``list`` or ``tuple``, the length must match the number of dimensions of the array.
225
        If a ``dict``, it must have has keys all array dimensions.
226
    dims : tuple
227
        The names of the array dimensions.
228
    shape : tuple
229
        The shape of the array.
230
    partitioning_method: (None, str)
231
        The optional partitioning method (tiling or sliding) to use.
232

233
    Returns
234
    -------
235
    stride : dict
236
        The stride to apply on each dimension.
237
    """
238
    if partitioning_method is None:
1✔
239
        return None
1✔
240
    # Set default arguments
241
    if stride is None:
1✔
242
        stride = 0 if partitioning_method == "tiling" else 1
1✔
243
    stride = _ensure_is_dict_argument(stride, dims=dims, arg_name="stride")
1✔
244
    # If tiling, check just that are integers
245
    # --> Negative strides lead to overlapping
246
    # --> Positive strides lead to not contiguous tiles
247
    if partitioning_method == "tiling":
1✔
248
        for value in stride.values():
1✔
249
            if not are_all_integers(value):
1✔
250
                raise ValueError("Invalid 'stride' values. They must be only integer values.")
1✔
251
    # If sliding, check are only positive numbers !
252
    else:  # sliding
253
        for value in stride.values():
1✔
254
            if not are_all_natural_numbers(value):
1✔
255
                raise ValueError("Invalid 'stride' values. They must be only positive integer (>=1) values.")
1✔
256
    # Check stride values are smaller than half the array shape
257
    # --> A stride with value exactly equal to half the array shape is equivalent to tiling
258
    dict_max_values = {dim: int(np.floor(size / 2)) for dim, size in zip(stride.keys(), shape, strict=True)}
1✔
259
    idx_valid = [value <= dict_max_values[dim] for dim, value in stride.items()]
1✔
260
    if not all(idx_valid):
1✔
UNCOV
261
        raise ValueError(f"The maximum allowed 'stride' values are {dict_max_values}.")
×
262
    return stride
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc