ialdev-maths 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iad/maths/__init__.py +0 -0
- iad/maths/geom/__init__.py +1 -0
- iad/maths/geom/plane.py +656 -0
- iad/maths/geom/shapes.py +496 -0
- iad/maths/hist.py +1458 -0
- iad/maths/regress.py +336 -0
- ialdev_maths-0.1.0.dist-info/METADATA +19 -0
- ialdev_maths-0.1.0.dist-info/RECORD +9 -0
- ialdev_maths-0.1.0.dist-info/WHEEL +4 -0
iad/maths/geom/shapes.py
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
__all__ = ['Pose', 'zero3D', 'Vec2d', 'Range', 'Rect', 'in_region', 'TupleXY']
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Union, Iterable, Optional
|
|
5
|
+
from numbers import Real, Number
|
|
6
|
+
import operator
|
|
7
|
+
import math
|
|
8
|
+
from collections import namedtuple
|
|
9
|
+
|
|
10
|
+
TupleXY = namedtuple('TupleXY', ['x', 'y'])
|
|
11
|
+
Range = namedtuple('Range', ['low', 'high'])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Vec2d(object):
|
|
15
|
+
"""2d vector class, supports vector and scalar operators,
|
|
16
|
+
and also provides a bunch of high level functions
|
|
17
|
+
"""
|
|
18
|
+
__slots__ = ['x', 'y']
|
|
19
|
+
|
|
20
|
+
def __init__(self, x_or_pair, y=None):
|
|
21
|
+
if y is None:
|
|
22
|
+
self.x = x_or_pair[0]
|
|
23
|
+
self.y = x_or_pair[1]
|
|
24
|
+
else:
|
|
25
|
+
self.x = x_or_pair
|
|
26
|
+
self.y = y
|
|
27
|
+
|
|
28
|
+
def __len__(self):
|
|
29
|
+
return 2
|
|
30
|
+
|
|
31
|
+
def __getitem__(self, key):
|
|
32
|
+
if key == 0:
|
|
33
|
+
return self.x
|
|
34
|
+
elif key == 1:
|
|
35
|
+
return self.y
|
|
36
|
+
else:
|
|
37
|
+
raise IndexError("Invalid subscript " + str(key) + " to Vec2d")
|
|
38
|
+
|
|
39
|
+
def __setitem__(self, key, value):
|
|
40
|
+
if key == 0:
|
|
41
|
+
self.x = value
|
|
42
|
+
elif key == 1:
|
|
43
|
+
self.y = value
|
|
44
|
+
else:
|
|
45
|
+
raise IndexError("Invalid subscript " + str(key) + " to Vec2d")
|
|
46
|
+
|
|
47
|
+
# String representation (for debugging)
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
return 'Vec2d(%s, %s)' % (self.x, self.y)
|
|
50
|
+
|
|
51
|
+
# Comparison
|
|
52
|
+
def __eq__(self, other):
|
|
53
|
+
if hasattr(other, "__getitem__") and len(other) == 2:
|
|
54
|
+
return self.x == other[0] and self.y == other[1]
|
|
55
|
+
else:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def __ne__(self, other):
|
|
59
|
+
if hasattr(other, "__getitem__") and len(other) == 2:
|
|
60
|
+
return self.x != other[0] or self.y != other[1]
|
|
61
|
+
else:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def __nonzero__(self):
|
|
65
|
+
return bool(self.x or self.y)
|
|
66
|
+
|
|
67
|
+
# Generic operator handlers
|
|
68
|
+
def _o2(self, other, f):
|
|
69
|
+
"""Any two-operator operation where the left operand is a Vec2d"""
|
|
70
|
+
if isinstance(other, Number):
|
|
71
|
+
return Vec2d(f(self.x, other), f(self.y, other))
|
|
72
|
+
if isinstance(other, Vec2d):
|
|
73
|
+
return Vec2d(f(self.x, other.x), f(self.y, other.y))
|
|
74
|
+
if hasattr(other, "__getitem__") and hasattr(other, "__len__"):
|
|
75
|
+
return Vec2d(f(self.x, other[0]), f(self.y, other[1]))
|
|
76
|
+
return Vec2d(f(self.x, other), f(self.y, other))
|
|
77
|
+
|
|
78
|
+
def _r_o2(self, other, f):
|
|
79
|
+
"Any two-operator operation where the right operand is a Vec2d"
|
|
80
|
+
if hasattr(other, "__getitem__") and hasattr(other, "__len__"):
|
|
81
|
+
return Vec2d(f(other[0], self.x),
|
|
82
|
+
f(other[1], self.y))
|
|
83
|
+
else:
|
|
84
|
+
return Vec2d(f(other, self.x),
|
|
85
|
+
f(other, self.y))
|
|
86
|
+
|
|
87
|
+
def _io(self, other, f):
|
|
88
|
+
"inplace operator"
|
|
89
|
+
if hasattr(other, "__getitem__"):
|
|
90
|
+
self.x = f(self.x, other[0])
|
|
91
|
+
self.y = f(self.y, other[1])
|
|
92
|
+
else:
|
|
93
|
+
self.x = f(self.x, other)
|
|
94
|
+
self.y = f(self.y, other)
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
# Addition
|
|
98
|
+
def __add__(self, other):
|
|
99
|
+
if isinstance(other, Vec2d):
|
|
100
|
+
return Vec2d(self.x + other.x, self.y + other.y)
|
|
101
|
+
elif hasattr(other, "__getitem__"):
|
|
102
|
+
return Vec2d(self.x + other[0], self.y + other[1])
|
|
103
|
+
else:
|
|
104
|
+
return Vec2d(self.x + other, self.y + other)
|
|
105
|
+
|
|
106
|
+
__radd__ = __add__
|
|
107
|
+
|
|
108
|
+
def __iadd__(self, other):
|
|
109
|
+
if isinstance(other, Vec2d):
|
|
110
|
+
self.x += other.x
|
|
111
|
+
self.y += other.y
|
|
112
|
+
elif hasattr(other, "__getitem__"):
|
|
113
|
+
self.x += other[0]
|
|
114
|
+
self.y += other[1]
|
|
115
|
+
else:
|
|
116
|
+
self.x += other
|
|
117
|
+
self.y += other
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
# Subtraction
|
|
121
|
+
def __sub__(self, other):
|
|
122
|
+
if isinstance(other, Vec2d):
|
|
123
|
+
return Vec2d(self.x - other.x, self.y - other.y)
|
|
124
|
+
elif hasattr(other, "__getitem__"):
|
|
125
|
+
return Vec2d(self.x - other[0], self.y - other[1])
|
|
126
|
+
else:
|
|
127
|
+
return Vec2d(self.x - other, self.y - other)
|
|
128
|
+
|
|
129
|
+
def __rsub__(self, other):
|
|
130
|
+
if isinstance(other, Vec2d):
|
|
131
|
+
return Vec2d(other.x - self.x, other.y - self.y)
|
|
132
|
+
if hasattr(other, "__getitem__"):
|
|
133
|
+
return Vec2d(other[0] - self.x, other[1] - self.y)
|
|
134
|
+
else:
|
|
135
|
+
return Vec2d(other - self.x, other - self.y)
|
|
136
|
+
|
|
137
|
+
def __isub__(self, other):
|
|
138
|
+
if isinstance(other, Vec2d):
|
|
139
|
+
self.x -= other.x
|
|
140
|
+
self.y -= other.y
|
|
141
|
+
elif hasattr(other, "__getitem__"):
|
|
142
|
+
self.x -= other[0]
|
|
143
|
+
self.y -= other[1]
|
|
144
|
+
else:
|
|
145
|
+
self.x -= other
|
|
146
|
+
self.y -= other
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
# Multiplication
|
|
150
|
+
def __mul__(self, other):
|
|
151
|
+
if isinstance(other, Vec2d):
|
|
152
|
+
return Vec2d(self.x * other.x, self.y * other.y)
|
|
153
|
+
if hasattr(other, "__getitem__"):
|
|
154
|
+
return Vec2d(self.x * other[0], self.y * other[1])
|
|
155
|
+
else:
|
|
156
|
+
return Vec2d(self.x * other, self.y * other)
|
|
157
|
+
|
|
158
|
+
__rmul__ = __mul__
|
|
159
|
+
|
|
160
|
+
def __imul__(self, other):
|
|
161
|
+
if isinstance(other, Vec2d):
|
|
162
|
+
self.x *= other.x
|
|
163
|
+
self.y *= other.y
|
|
164
|
+
elif hasattr(other, "__getitem__"):
|
|
165
|
+
self.x *= other[0]
|
|
166
|
+
self.y *= other[1]
|
|
167
|
+
else:
|
|
168
|
+
self.x *= other
|
|
169
|
+
self.y *= other
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
# Division
|
|
173
|
+
def __div__(self, other):
|
|
174
|
+
return self._o2(other, operator.div)
|
|
175
|
+
|
|
176
|
+
def __rdiv__(self, other):
|
|
177
|
+
return self._r_o2(other, operator.div)
|
|
178
|
+
|
|
179
|
+
def __idiv__(self, other):
|
|
180
|
+
return self._io(other, operator.div)
|
|
181
|
+
|
|
182
|
+
def __floordiv__(self, other):
|
|
183
|
+
return self._o2(other, operator.floordiv)
|
|
184
|
+
|
|
185
|
+
def __rfloordiv__(self, other):
|
|
186
|
+
return self._r_o2(other, operator.floordiv)
|
|
187
|
+
|
|
188
|
+
def __ifloordiv__(self, other):
|
|
189
|
+
return self._io(other, operator.floordiv)
|
|
190
|
+
|
|
191
|
+
def __truediv__(self, other):
|
|
192
|
+
return self._o2(other, operator.truediv)
|
|
193
|
+
|
|
194
|
+
def __rtruediv__(self, other):
|
|
195
|
+
return self._r_o2(other, operator.truediv)
|
|
196
|
+
|
|
197
|
+
def __itruediv__(self, other):
|
|
198
|
+
return self._io(other, operator.floordiv)
|
|
199
|
+
|
|
200
|
+
# Modulo
|
|
201
|
+
def __mod__(self, other):
|
|
202
|
+
return self._o2(other, operator.mod)
|
|
203
|
+
|
|
204
|
+
def __rmod__(self, other):
|
|
205
|
+
return self._r_o2(other, operator.mod)
|
|
206
|
+
|
|
207
|
+
def __divmod__(self, other):
|
|
208
|
+
return self._o2(other, operator.divmod)
|
|
209
|
+
|
|
210
|
+
def __rdivmod__(self, other):
|
|
211
|
+
return self._r_o2(other, operator.divmod)
|
|
212
|
+
|
|
213
|
+
# Exponentation
|
|
214
|
+
def __pow__(self, other):
|
|
215
|
+
return self._o2(other, operator.pow)
|
|
216
|
+
|
|
217
|
+
def __rpow__(self, other):
|
|
218
|
+
return self._r_o2(other, operator.pow)
|
|
219
|
+
|
|
220
|
+
# Bitwise operators
|
|
221
|
+
def __lshift__(self, other):
|
|
222
|
+
return self._o2(other, operator.lshift)
|
|
223
|
+
|
|
224
|
+
def __rlshift__(self, other):
|
|
225
|
+
return self._r_o2(other, operator.lshift)
|
|
226
|
+
|
|
227
|
+
def __rshift__(self, other):
|
|
228
|
+
return self._o2(other, operator.rshift)
|
|
229
|
+
|
|
230
|
+
def __rrshift__(self, other):
|
|
231
|
+
return self._r_o2(other, operator.rshift)
|
|
232
|
+
|
|
233
|
+
def __and__(self, other):
|
|
234
|
+
return self._o2(other, operator.and_)
|
|
235
|
+
|
|
236
|
+
__rand__ = __and__
|
|
237
|
+
|
|
238
|
+
def __or__(self, other):
|
|
239
|
+
return self._o2(other, operator.or_)
|
|
240
|
+
|
|
241
|
+
__ror__ = __or__
|
|
242
|
+
|
|
243
|
+
def __xor__(self, other):
|
|
244
|
+
return self._o2(other, operator.xor)
|
|
245
|
+
|
|
246
|
+
__rxor__ = __xor__
|
|
247
|
+
|
|
248
|
+
# Unary operations
|
|
249
|
+
def __neg__(self):
|
|
250
|
+
return Vec2d(operator.neg(self.x), operator.neg(self.y))
|
|
251
|
+
|
|
252
|
+
def __pos__(self):
|
|
253
|
+
return Vec2d(operator.pos(self.x), operator.pos(self.y))
|
|
254
|
+
|
|
255
|
+
def __abs__(self):
|
|
256
|
+
return Vec2d(abs(self.x), abs(self.y))
|
|
257
|
+
|
|
258
|
+
def __invert__(self):
|
|
259
|
+
return Vec2d(-self.x, -self.y)
|
|
260
|
+
|
|
261
|
+
# vectory functions
|
|
262
|
+
def length_sqr(self):
|
|
263
|
+
return self.x ** 2 + self.y ** 2
|
|
264
|
+
|
|
265
|
+
def __get_length(self):
|
|
266
|
+
return math.sqrt(self.x ** 2 + self.y ** 2)
|
|
267
|
+
|
|
268
|
+
def __set_length(self, value):
|
|
269
|
+
k = value / self.__get_length()
|
|
270
|
+
self.x *= k
|
|
271
|
+
self.y *= k
|
|
272
|
+
|
|
273
|
+
length = property(__get_length, __set_length, None, "gets or sets the magnitude of the vector")
|
|
274
|
+
|
|
275
|
+
def rotate(self, angle_degrees):
|
|
276
|
+
radians = math.radians(angle_degrees)
|
|
277
|
+
cos = math.cos(radians)
|
|
278
|
+
sin = math.sin(radians)
|
|
279
|
+
x = self.x * cos - self.y * sin
|
|
280
|
+
y = self.x * sin + self.y * cos
|
|
281
|
+
self.x = x
|
|
282
|
+
self.y = y
|
|
283
|
+
|
|
284
|
+
def rotated(self, angle_degrees):
|
|
285
|
+
radians = math.radians(angle_degrees)
|
|
286
|
+
cos = math.cos(radians)
|
|
287
|
+
sin = math.sin(radians)
|
|
288
|
+
x = self.x * cos - self.y * sin
|
|
289
|
+
y = self.x * sin + self.y * cos
|
|
290
|
+
return Vec2d(x, y)
|
|
291
|
+
|
|
292
|
+
def __get_angle(self):
|
|
293
|
+
if self.length_sqr() == 0:
|
|
294
|
+
return 0
|
|
295
|
+
return math.degrees(math.atan2(self.y, self.x))
|
|
296
|
+
|
|
297
|
+
def __set_angle(self, angle_degrees):
|
|
298
|
+
self.x = self.length
|
|
299
|
+
self.y = 0
|
|
300
|
+
self.rotate(angle_degrees)
|
|
301
|
+
|
|
302
|
+
angle = property(__get_angle, __set_angle, None, "gets or sets the angle of a vector")
|
|
303
|
+
|
|
304
|
+
def angle_between(self, other):
|
|
305
|
+
cross = self.x * other[1] - self.y * other[0]
|
|
306
|
+
dot = self.x * other[0] + self.y * other[1]
|
|
307
|
+
return math.degrees(math.atan2(cross, dot))
|
|
308
|
+
|
|
309
|
+
def normalized(self):
|
|
310
|
+
length = self.length
|
|
311
|
+
if length != 0:
|
|
312
|
+
return self / length
|
|
313
|
+
return Vec2d(self)
|
|
314
|
+
|
|
315
|
+
def normalize_return_length(self):
|
|
316
|
+
length = self.length
|
|
317
|
+
if length != 0:
|
|
318
|
+
self.x /= length
|
|
319
|
+
self.y /= length
|
|
320
|
+
return length
|
|
321
|
+
|
|
322
|
+
def perpendicular(self):
|
|
323
|
+
return Vec2d(-self.y, self.x)
|
|
324
|
+
|
|
325
|
+
def perpendicular_normal(self):
|
|
326
|
+
length = self.length
|
|
327
|
+
if length != 0:
|
|
328
|
+
return Vec2d(-self.y / length, self.x / length)
|
|
329
|
+
return Vec2d(self)
|
|
330
|
+
|
|
331
|
+
def dot(self, other):
|
|
332
|
+
return float(self.x * other[0] + self.y * other[1])
|
|
333
|
+
|
|
334
|
+
def distance(self, other):
|
|
335
|
+
return math.sqrt((self.x - other[0]) ** 2 + (self.y - other[1]) ** 2)
|
|
336
|
+
|
|
337
|
+
def get_dist_sqrd(self, other):
|
|
338
|
+
return (self.x - other[0]) ** 2 + (self.y - other[1]) ** 2
|
|
339
|
+
|
|
340
|
+
def projection(self, other):
|
|
341
|
+
other_length_sqrd = other[0] * other[0] + other[1] * other[1]
|
|
342
|
+
projected_length_times_other_length = self.dot(other)
|
|
343
|
+
return other * (projected_length_times_other_length / other_length_sqrd)
|
|
344
|
+
|
|
345
|
+
def cross(self, other):
|
|
346
|
+
return self.x * other[1] - self.y * other[0]
|
|
347
|
+
|
|
348
|
+
def interpolate_to(self, other, range):
|
|
349
|
+
return Vec2d(self.x + (other[0] - self.x) * range, self.y + (other[1] - self.y) * range)
|
|
350
|
+
|
|
351
|
+
def convert_to_basis(self, x_vector, y_vector):
|
|
352
|
+
return Vec2d(self.dot(x_vector) / x_vector.length_sqr(), self.dot(y_vector) / y_vector.length_sqr())
|
|
353
|
+
|
|
354
|
+
def __getstate__(self):
|
|
355
|
+
return [self.x, self.y]
|
|
356
|
+
|
|
357
|
+
def __setstate__(self, dict):
|
|
358
|
+
self.x, self.y = dict
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class _Meta2D(type):
|
|
362
|
+
@classmethod
|
|
363
|
+
def __prepare__(metacls, name, bases, dtype=float, default=np.nan):
|
|
364
|
+
cls_dict = super().__prepare__(name, bases)
|
|
365
|
+
slots = ['x', 'y']
|
|
366
|
+
|
|
367
|
+
def __init__(self,
|
|
368
|
+
first: Union[dtype, Iterable] = (default, default),
|
|
369
|
+
second: Optional[dtype] = None):
|
|
370
|
+
"""
|
|
371
|
+
Create 2D coordinate container of {dtype}
|
|
372
|
+
Args:
|
|
373
|
+
first: x or iterable of len=2 for xy (deafults = ({default}, {default}) )
|
|
374
|
+
second: y (if first is x) or not needed if iterable is passed
|
|
375
|
+
"""
|
|
376
|
+
self.x, self.y = ((dtype(v) for v in first) if hasattr(first, '__iter__')
|
|
377
|
+
else (first, second))
|
|
378
|
+
|
|
379
|
+
__init__.__doc__ = __init__.__doc__.format(dtype=dtype,
|
|
380
|
+
default=default) # insert data type infor into the docstring
|
|
381
|
+
|
|
382
|
+
cls_dict.update(
|
|
383
|
+
__slots__=slots,
|
|
384
|
+
__annotations__={m: dtype for m in slots},
|
|
385
|
+
xy=property(lambda self: (self.x, self.y)),
|
|
386
|
+
ij=property(lambda self: (int(self.x), int(self.y))),
|
|
387
|
+
array=property(lambda self: np.ndarray(self.xy)),
|
|
388
|
+
dtype=dtype,
|
|
389
|
+
__init__=__init__,
|
|
390
|
+
__iter__=lambda self: iter((self.x, self.y)),
|
|
391
|
+
__eq__=lambda self, other: ((self.x, self.y) == other),
|
|
392
|
+
__getitem__=lambda self, key: getattr(self, 'xy'[key]),
|
|
393
|
+
__setitem__=lambda self, key, val: setattr(self, 'xy'[key], val)
|
|
394
|
+
)
|
|
395
|
+
return cls_dict
|
|
396
|
+
|
|
397
|
+
def __new__(mcs, name, bases, namespace, **kargs):
|
|
398
|
+
return super().__new__(mcs, name, bases, namespace)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# TODO: 3D geometry classes?
|
|
402
|
+
zero3D = np.zeros(3)
|
|
403
|
+
V3D = np.ndarray
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class Pose:
|
|
407
|
+
rot: V3D
|
|
408
|
+
trans: V3D
|
|
409
|
+
|
|
410
|
+
def __init__(self, rot=0, trans=0):
|
|
411
|
+
def prep(v):
|
|
412
|
+
if v == 0:
|
|
413
|
+
return zero3D
|
|
414
|
+
assert len(v) == 3
|
|
415
|
+
return v if isinstance(v, np.ndarray) else np.array(v)
|
|
416
|
+
self.rot, self.trans = (prep(v) for v in (rot, trans))
|
|
417
|
+
|
|
418
|
+
def __repr__(self):
|
|
419
|
+
rot, trans = ('0' if (self.rot == zero3D).all() else '{},{},{}'.format(*v)
|
|
420
|
+
for v in (self.rot, self.trans))
|
|
421
|
+
return f'R:{rot} T:{trans}'
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class Rect:
|
|
425
|
+
__slots__ = ['low', 'high']
|
|
426
|
+
low: Vec2d
|
|
427
|
+
high: Vec2d
|
|
428
|
+
|
|
429
|
+
def __init__(self, low: Union[Vec2d, Iterable],
|
|
430
|
+
high: Optional[Union[Vec2d, Iterable]] = None,
|
|
431
|
+
*, dim: Optional[Union[Vec2d, Iterable]] = None):
|
|
432
|
+
"""
|
|
433
|
+
Create rectangle from two corner points or from low point and dimension
|
|
434
|
+
:param low: tuple, pix2D
|
|
435
|
+
:param high: tuple, pix2D - optional
|
|
436
|
+
:param dim: tuple, pix2D - if high is not provided
|
|
437
|
+
"""
|
|
438
|
+
self.low = Vec2d(low)
|
|
439
|
+
self.high = Vec2d(self.low + dim if high is None else high)
|
|
440
|
+
if self.low.x > self.high.x or self.low.y > self.high.y:
|
|
441
|
+
raise ValueError('Low coordinates may not be larger than the high')
|
|
442
|
+
|
|
443
|
+
def __repr__(self):
|
|
444
|
+
return f'Rect({self.low.x}, {self.low.y}; {self.high.x}, {self.high.y})'
|
|
445
|
+
|
|
446
|
+
def __iter__(self):
|
|
447
|
+
return iter((self.low, self.high))
|
|
448
|
+
|
|
449
|
+
def __eq__(self, other):
|
|
450
|
+
return self.low == other.low and self.high == other.high
|
|
451
|
+
|
|
452
|
+
def extend(self, add: Union[Real, Vec2d]):
|
|
453
|
+
self.low -= add
|
|
454
|
+
self.high += add
|
|
455
|
+
|
|
456
|
+
def move(self, shift: Vec2d):
|
|
457
|
+
self.low += shift
|
|
458
|
+
self.high += shift
|
|
459
|
+
|
|
460
|
+
def __contains__(self, p: Vec2d):
|
|
461
|
+
if isinstance(p, Rect):
|
|
462
|
+
return self.high.x <= p.high.x <= p.low.x <= self.low.x and \
|
|
463
|
+
self.high.y <= p.high.y <= p.low.y <= self.low.y
|
|
464
|
+
elif not isinstance(p, Vec2d):
|
|
465
|
+
p = Vec2d(p)
|
|
466
|
+
return self.low.x <= p.x <= self.high.x and self.low.y <= p.y <= self.high.y
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def dim(self):
|
|
470
|
+
return self.high - self.low
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def ranges(self) -> TupleXY:
|
|
474
|
+
""" representation as pair of two segments """
|
|
475
|
+
return TupleXY(Range(self.low.x, self.high.x),
|
|
476
|
+
Range(self.low.y, self.high.y))
|
|
477
|
+
|
|
478
|
+
def vertices(self):
|
|
479
|
+
""" Generator of all the vertices from low in clock-wise direction"""
|
|
480
|
+
yield Vec2d(self.low.x, self.low.y)
|
|
481
|
+
yield Vec2d(self.low.x, self.high.y)
|
|
482
|
+
yield Vec2d(self.high.x, self.high.y)
|
|
483
|
+
yield Vec2d(self.high.x, self.low.y)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def in_region(coords, region):
|
|
487
|
+
"""
|
|
488
|
+
Checks if D-dim coordinates of N points are inside D-dim region
|
|
489
|
+
:param coords: [D x N] - iterable of D 1-d arrays of length N
|
|
490
|
+
:param region: [D x 2] - iterable of D segments of (low, high) for each dimension
|
|
491
|
+
low <= v < high
|
|
492
|
+
:return: np.bool(N) boolean array of length N indicating if a point inside the region
|
|
493
|
+
"""
|
|
494
|
+
from functools import reduce
|
|
495
|
+
by_dimensions = (((x >= low) & (x < high)) for x, (low, high) in zip(coords, region))
|
|
496
|
+
return reduce(np.logical_and, by_dimensions)
|