-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
utils.py
193 lines (149 loc) · 5.75 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import warnings
import functools
import sys
import numpy as np
import types
import six
from ._warnings import all_warnings, warn
__all__ = ['deprecated', 'get_bound_method_class', 'all_warnings',
'safe_as_int', 'assert_nD', 'warn']
class skimage_deprecation(Warning):
"""Create our own deprecation class, since Python >= 2.7
silences deprecations by default.
"""
pass
class deprecated(object):
"""Decorator to mark deprecated functions with warning.
Adapted from <http://wiki.python.org/moin/PythonDecoratorLibrary>.
Parameters
----------
alt_func : str
If given, tell user what function to use instead.
behavior : {'warn', 'raise'}
Behavior during call to deprecated function: 'warn' = warn user that
function is deprecated; 'raise' = raise error.
"""
def __init__(self, alt_func=None, behavior='warn'):
self.alt_func = alt_func
self.behavior = behavior
def __call__(self, func):
alt_msg = ''
if self.alt_func is not None:
alt_msg = ' Use ``%s`` instead.' % self.alt_func
msg = 'Call to deprecated function ``%s``.' % func.__name__
msg += alt_msg
@functools.wraps(func)
def wrapped(*args, **kwargs):
if self.behavior == 'warn':
func_code = six.get_function_code(func)
warnings.simplefilter('always', skimage_deprecation)
warnings.warn_explicit(msg,
category=skimage_deprecation,
filename=func_code.co_filename,
lineno=func_code.co_firstlineno + 1)
elif self.behavior == 'raise':
raise skimage_deprecation(msg)
return func(*args, **kwargs)
# modify doc string to display deprecation warning
doc = '**Deprecated function**.' + alt_msg
if wrapped.__doc__ is None:
wrapped.__doc__ = doc
else:
wrapped.__doc__ = doc + '\n\n ' + wrapped.__doc__
return wrapped
def get_bound_method_class(m):
"""Return the class for a bound method.
"""
return m.im_class if sys.version < '3' else m.__self__.__class__
def safe_as_int(val, atol=1e-3):
"""
Attempt to safely cast values to integer format.
Parameters
----------
val : scalar or iterable of scalars
Number or container of numbers which are intended to be interpreted as
integers, e.g., for indexing purposes, but which may not carry integer
type.
atol : float
Absolute tolerance away from nearest integer to consider values in
``val`` functionally integers.
Returns
-------
val_int : NumPy scalar or ndarray of dtype `np.int64`
Returns the input value(s) coerced to dtype `np.int64` assuming all
were within ``atol`` of the nearest integer.
Notes
-----
This operation calculates ``val`` modulo 1, which returns the mantissa of
all values. Then all mantissas greater than 0.5 are subtracted from one.
Finally, the absolute tolerance from zero is calculated. If it is less
than ``atol`` for all value(s) in ``val``, they are rounded and returned
in an integer array. Or, if ``val`` was a scalar, a NumPy scalar type is
returned.
If any value(s) are outside the specified tolerance, an informative error
is raised.
Examples
--------
>>> _safe_as_int(7.0)
7
>>> _safe_as_int([9, 4, 2.9999999999])
array([9, 4, 3], dtype=int32)
>>> _safe_as_int(53.01)
Traceback (most recent call last):
...
ValueError: Integer argument required but received 53.1, check inputs.
>>> _safe_as_int(53.01, atol=0.01)
53
"""
mod = np.asarray(val) % 1 # Extract mantissa
# Check for and subtract any mod values > 0.5 from 1
if mod.ndim == 0: # Scalar input, cannot be indexed
if mod > 0.5:
mod = 1 - mod
else: # Iterable input, now ndarray
mod[mod > 0.5] = 1 - mod[mod > 0.5] # Test on each side of nearest int
try:
np.testing.assert_allclose(mod, 0, atol=atol)
except AssertionError:
raise ValueError("Integer argument required but received "
"{0}, check inputs.".format(val))
return np.round(val).astype(np.int64)
def assert_nD(array, ndim, arg_name='image'):
"""
Verify an array meets the desired ndims.
Parameters
----------
array : array-like
Input array to be validated
ndim : int or iterable of ints
Allowable ndim or ndims for the array.
arg_name : str, optional
The name of the array in the original function.
"""
array = np.asanyarray(array)
msg = "The parameter `%s` must be a %s-dimensional array"
if isinstance(ndim, int):
ndim = [ndim]
if not array.ndim in ndim:
raise ValueError(msg % (arg_name, '-or-'.join([str(n) for n in ndim])))
def _mode_deprecations(mode):
"""Used to update deprecated mode names in
`skimage._shared.interpolation.pyx`."""
if mode.lower() == 'nearest':
warn(skimage_deprecation(
"Mode 'nearest' has been renamed to 'edge'. Mode 'nearest' will be "
"removed in a future release."))
mode = 'edge'
return mode
def copy_func(f, name=None):
"""Create a copy of a function.
Parameters
----------
f : function
Function to copy.
name : str, optional
Name of new function.
"""
return types.FunctionType(six.get_function_code(f),
six.get_function_globals(f), name or f.__name__,
six.get_function_defaults(f), six.get_function_closure(f))