arrayTools.py revision 41144b93069d17dc6aeed494016ba8d47c74f1e9
1#
2# Various array and rectangle tools, but mostly rectangles, hence the
3# name of this module (not).
4#
5
6import Numeric
7
8def calcBounds(array):
9	"""Return the bounding rectangle of a 2D points array as a tuple:
10	(xMin, yMin, xMax, yMax)
11	"""
12	if len(array) == 0:
13		return 0, 0, 0, 0
14	xMin, yMin = Numeric.minimum.reduce(array)
15	xMax, yMax = Numeric.maximum.reduce(array)
16	return xMin, yMin, xMax, yMax
17
18def updateBounds(bounds, (x, y), min=min, max=max):
19	"""Return the bounding recangle of rectangle bounds and point (x, y)."""
20	xMin, yMin, xMax, yMax = bounds
21	return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
22
23def pointInRect((x, y), rect):
24	"""Return True when point (x, y) is inside rect."""
25	xMin, yMin, xMax, yMax = rect
26	return (xMin <= x <= xMax) and (yMin <= y <= yMax)
27
28def pointsInRect(array, rect):
29	"""Find out which points or array are inside rect.
30	Returns an array with a boolean for each point.
31	"""
32	if len(array) < 1:
33		return []
34	lefttop = rect[:2]
35	rightbottom = rect[2:]
36	condition = Numeric.logical_and(
37			Numeric.greater_equal(array, lefttop),
38			Numeric.less_equal(array, rightbottom))
39	return Numeric.logical_and.reduce(condition, -1)
40
41def vectorLength(vector):
42	"""Return the length of the given vector."""
43	return Numeric.sqrt(vector[0]**2 + vector[1]**2)
44
45def asInt16(array):
46	"""Round and cast to 16 bit integer."""
47	return Numeric.floor(array + 0.5).astype(Numeric.Int16)
48
49
50def normRect((xMin, yMin, xMax, yMax)):
51	"""Normalize the rectangle so that the following holds:
52		xMin <= xMax and yMin <= yMax
53	"""
54	return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
55
56def scaleRect((xMin, yMin, xMax, yMax), x, y):
57	"""Scale the rectangle by x, y."""
58	return xMin * x, yMin * y, xMax * x, yMax * y
59
60def offsetRect((xMin, yMin, xMax, yMax), dx, dy):
61	"""Offset the rectangle by dx, dy."""
62	return xMin+dx, yMin+dy, xMax+dx, yMax+dy
63
64def insetRect((xMin, yMin, xMax, yMax), dx, dy):
65	"""Inset the rectangle by dx, dy on all sides."""
66	return xMin+dx, yMin+dy, xMax-dx, yMax-dy
67
68def sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
69	"""Return a boolean and a rectangle. If the input rectangles intersect, return
70	True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
71	rectangles don't intersect.
72	"""
73	xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
74	                          min(xMax1, xMax2), min(yMax1, yMax2))
75	if xMin >= xMax or yMin >= yMax:
76		return 0, (0, 0, 0, 0)
77	return 1, (xMin, yMin, xMax, yMax)
78
79def unionRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
80	"""Return the smallest rectangle in which both input rectangles are fully
81	enclosed. In other words, return the total bounding rectangle of both input
82	rectangles.
83	"""
84	xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
85	                          max(xMax1, xMax2), max(yMax1, yMax2))
86	return (xMin, yMin, xMax, yMax)
87
88def rectCenter((xMin, yMin, xMax, yMax)):
89	"""Return the center of the rectangle as an (x, y) coordinate."""
90	return (xMin+xMax)/2, (yMin+yMax)/2
91
92def intRect((xMin, yMin, xMax, yMax)):
93	"""Return the rectangle, rounded off to integer values, but guaranteeing that
94	the resulting rectangle is NOT smaller than the original.
95	"""
96	import math
97	xMin = int(math.floor(xMin))
98	yMin = int(math.floor(yMin))
99	xMax = int(math.ceil(xMax))
100	yMax = int(math.ceil(yMax))
101	return (xMin, yMin, xMax, yMax)
102
103
104def _test():
105	"""
106	>>> import math
107	>>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
108	(0, 10, 80, 100)
109	>>> updateBounds((0, 0, 0, 0), (100, 100))
110	(0, 0, 100, 100)
111	>>> pointInRect((50, 50), (0, 0, 100, 100))
112	True
113	>>> pointInRect((0, 0), (0, 0, 100, 100))
114	True
115	>>> pointInRect((100, 100), (0, 0, 100, 100))
116	True
117	>>> not pointInRect((101, 100), (0, 0, 100, 100))
118	True
119	>>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
120	[1, 1, 1, 0]
121	>>> vectorLength((3, 4))
122	5.0
123	>>> vectorLength((1, 1)) == math.sqrt(2)
124	True
125	>>> list(asInt16(Numeric.array([0, 0.1, 0.5, 0.9])))
126	[0, 0, 1, 1]
127	>>> normRect((0, 10, 100, 200))
128	(0, 10, 100, 200)
129	>>> normRect((100, 200, 0, 10))
130	(0, 10, 100, 200)
131	>>> scaleRect((10, 20, 50, 150), 1.5, 2)
132	(15.0, 40, 75.0, 300)
133	>>> offsetRect((10, 20, 30, 40), 5, 6)
134	(15, 26, 35, 46)
135	>>> insetRect((10, 20, 50, 60), 5, 10)
136	(15, 30, 45, 50)
137	>>> insetRect((10, 20, 50, 60), -5, -10)
138	(5, 10, 55, 70)
139	>>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
140	>>> not intersects
141	True
142	>>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
143	>>> intersects
144	1
145	>>> rect
146	(5, 20, 20, 30)
147	>>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
148	(0, 10, 20, 50)
149	>>> rectCenter((0, 0, 100, 200))
150	(50, 100)
151	>>> rectCenter((0, 0, 100, 199.0))
152	(50, 99.5)
153	>>> intRect((0.9, 2.9, 3.1, 4.1))
154	(0, 2, 4, 5)
155	"""
156
157if __name__ == "__main__":
158	import doctest
159	doctest.testmod()
160