-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy patharrays.py
273 lines (196 loc) · 6.73 KB
/
arrays.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import numpy
import scipy.ndimage as ndimage
##### ##### ##### ##### #####
EPSILON = numpy.finfo(numpy.float16).eps # Use a lo-rez float to be safe
FLOATS = numpy.float32 # Note: Change to tune accuracy and performace
FLOATS_INFO = numpy.finfo(FLOATS)
BYTES = numpy.uint8 # Important: This must never change!
BYTES_INFO = numpy.iinfo(BYTES)
##### ##### ##### ##### #####
def is_nearly(x, y):
# Given x and y as numbers, or arrays.
# Check if all values of x are nearly equal to the corresponding y value
return numpy.all(numpy.abs(y - x) <= EPSILON)
def is_nearly_zero(x):
return numpy.all(numpy.abs(x) <= EPSILON)
##### ##### ##### ##### #####
def is_similar_type(x, t):
# x is either a type or an instance of an object
# t must be a type
#
# If x is a type,
# Return True if x type and t type are the same
# Otherwise False
# Otherwise
# Return True if x is an instance of t
# Otherwise False
#
# Examples:
# is_similar_type(numpy.uint8, numpy.uint8) => True
# is_similar_type(numpy.uint8, numpy.float32) => False
# is_similar_type(numpy.uint8(), numpy.uint8) => True
# is_similar_type(numpy.uint16(), numpy.uint8) => False
if isinstance(x, type) or isinstance(x, numpy.dtype):
return x == t
elif type(x) == numpy.ndarray:
return x.dtype == t
else:
return isinstance(x, t)
##### ##### ##### ##### #####
def is_bytes(x):
return is_similar_type(x, BYTES)
def as_bytes(x):
# Important:
# This function is not guaranteed to be reversable.
# The data might be scaled, or translated to reduce data loss.
# It is most useful for converting float-images to byte-images.
if is_bytes(x):
return x
x = normalize(x) # Scale to [0, 1]
# Story time:
# When rectifying images,
# I found that the transformations were inconsistent between FLOATS types.
# I determined that the best rectification results were obtained with numpy.float16.
# While debugging I noticed small differences were propagated from within as_bytes.
# When FLOATS = numpy.float16, numpy.float32, ... small differences in pixel value occured between these types,
# Which propagated to a feature detector that I used to help determine the transformation.
# Some of these issues were resolved when I ironed out a few bugs, but the problem was obviously in as_bytes.
# My solution was to use numpy.around.
# I understand that when floating operations are done,
# Errors can accumulate in least-significant decimals and overflow into most-significant decimals.
# So numpy.around is used to trim off these anomalies at the 4th decimal.
# The 4th decimal was selected because of the the storage capacity of the unsigned char / uint8 / byte.
# We need at most 4 decimals for every byte value:
# 1/256 ~= 0.0039 => 4 decimal precision
x = numpy.around(x, decimals = 4) # TODO: find out how to programmatically convert BYTES type to number of decimals
x = x*BYTES_INFO.max # Scale to [0, max]
x = numpy.array(x, dtype = BYTES)
return x
##### ##### ##### ##### #####
def is_floats(x):
return is_similar_type(x, FLOATS)
def as_floats(x):
if is_floats(x):
return x
# Capacity is assumed to be large enough
# If this is cast down from larger floating point types,
# Truncation will occur.
x = numpy.array(x, dtype = FLOATS)
return x
##### ##### ##### ##### #####
def is_equal(a, b):
a = numpy.array(a)
b = numpy.array(b)
if numpy.any(a != b): # "If any in a does not equal in b"
return False
else:
return True
##### ##### ##### ##### #####
def normalize(x):
x = numpy.array(x)
x_min = numpy.min(x)
x_max = numpy.max(x)
x_range = x_max - x_min
x -= x_min
# Prevent division by zero:
# Simply check if the value is greater
# Note: x_range guaranteed to be non-negative
if (x_range > 0):
# Reduce rounding error:
# Only mess with the numbers if they are in voilation of domain
if (x_range < 1):
return x
else:
x = as_floats(x)
x /= x_range
return x
##### ##### ##### ##### #####
def is_vector(x):
#return (len(x) == len(x.ravel()))
dimension = len(x.shape)
if (dimension == 1): return True
if (dimension == 2):
if (x.shape[0] == 1) or (x.shape[1] == 1):
return True
return False
return False
def as_vector(x):
return x.ravel()
##### ##### ##### ##### #####
def euclidean_to_homogeneous(x):
"""
See: OpenCV - convertPointsToHomogeneous
The function converts points from Euclidean to homogeneous space by appending 1's to the tuple of point coordinates.
That is, each point (x1, x2, ..., xn) is converted to (x1, x2, ..., xn, 1).
"""
if is_vector(x):
return numpy.r_[x, 1]
else:
S = (1,) + x.shape[1:]
return numpy.r_[x, numpy.ones(S)]
def homogeneous_to_euclidean(x):
"""
See: OpenCV - convertPointsFromHomogeneous
The function converts points homogeneous to Euclidean space using perspective projection.
That is, each point (x1, x2, ... x(n-1), xn) is converted to (x1/xn, x2/xn, ..., x(n-1)/xn).
When xn=0, the output point coordinates will be (0,0,0,...).
"""
# Faster method: avoid dividing everything
y = as_floats(x) # Make a copy of x and convert to FLOATS
if is_vector(y):
y[-1] = numpy.nan if (y[-1] == 0) else y[-1]
y[:-1] /= y[-1]
y[numpy.isnan(y)] = 0
y[-1] = 0 if (y[-1] == 0) else 1
else:
y[-1, y[-1] == 0] = numpy.nan # Replace 0s in end row with nan to prevent division by zero
y[:-1] /= y[-1] # Divide all rows excluding end row, by end row
y[numpy.isnan(y)] = 0 # Replace all nan with 0s
y[-1, y[-1] != 0] = 1 # Replace end row with 1s if the value is not zero
return y
##### ##### ##### ##### #####
def resample(array, new_shape):
'''
order = 0 # nearest
order = 1 # bilinear
order = 2 # cubic
...
order = 6
'''
array = numpy.array(array)
old_shape = array.shape
old_shape = as_floats(old_shape)
new_shape = as_floats(new_shape)
array = ndimage.interpolation.zoom(input = array, zoom = (new_shape / old_shape), order = 2)
return array
##### ##### ##### ##### #####
def span(x):
return 2*x+1
##### ##### ##### ##### #####
def neighbours(array, yy, xx, size, roll): # 2D array
# Given an image
# Return an NxN array whose "center" element is arr[y,x]
(height, width) = array.shape
yy_start = yy-size
yy_end = yy+size+1
xx_start = xx-size
xx_end = xx+size+1
# Check if roll operation can be skipped
# Note: numpy.roll is slow
if (0 < yy_start) and (yy_end < height):
if (0 < xx_start) and (xx_end < width):
roll = False
if roll:
array = numpy.roll(array, shift = 1-yy, axis = 0)
array = numpy.roll(array, shift = 1-xx, axis = 1)
N = span(size)
return array[:N, :N]
else:
return array[yy_start:yy_end, xx_start:xx_end]
def zero_pad(x, padding, axis = None):
y = numpy.pad(x, pad_width = padding, mode = 'constant')
if axis == 0:
return y[padding:(0-padding), :]
elif axis == 1:
return y[:, padding:(0-padding)]
return y