~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

1 by Brian Sidebotham
Initial import into Bazaar
1
#!/usr/bin/env python
2
3
"""
4
A Bounding Box object and assorted utilities , subclassed from a numpy array
5
6
"""
7
8
import numpy as N
9
10
class BBox(N.ndarray):
11
    """
12
    A Bounding Box object:
13
    
14
    Takes Data as an array. Data is any python sequence that can be turned into a 
15
    2x2 numpy array of floats:
16
17
    [[MinX, MinY ],
18
     [MaxX, MaxY ]]
19
20
    It is a subclass of numpy.ndarray, so for the most part it can be used as 
21
    an array, and arrays that fit the above description can be used in its place.
22
    
23
    Usually created by the factory functions:
24
    
25
        asBBox
26
        
27
        and 
28
        
29
        fromPoints
30
    
31
    """
32
    def __new__(subtype, data):
33
        """
34
        Takes Data as an array. Data is any python sequence that can be turned into a 
35
        2x2 numpy array of floats:
36
37
        [[MinX, MinY ],
38
        [MaxX, MaxY ]]
39
40
        You don't usually call this directly. BBox objects are created with the factory functions:
41
        
42
        asBBox
43
        
44
        and 
45
        
46
        fromPoints
47
48
        """
49
        arr = N.array(data, N.float)
50
        arr.shape = (2,2)
51
        if arr[0,0] > arr[1,0] or arr[0,1] > arr[1,1]:
52
            # note: zero sized BB OK.
53
            raise ValueError("BBox values not aligned: \n minimum values must be less that maximum values")
54
        return N.ndarray.__new__(subtype, shape=arr.shape, dtype=arr.dtype, buffer=arr)
55
56
    def Overlaps(self, BB):
57
        """
58
        Overlap(BB):
59
60
        Tests if the given Bounding Box overlaps with this one.
61
        Returns True is the Bounding boxes overlap, False otherwise
62
        If they are just touching, returns True
63
        """
64
65
        if N.isinf(self).all() or N.isinf(BB).all():
66
            return True
67
        if ( (self[1,0] >= BB[0,0]) and (self[0,0] <= BB[1,0]) and
68
             (self[1,1] >= BB[0,1]) and (self[0,1] <= BB[1,1]) ):
69
            return True
70
        else:
71
            return False
72
73
    def Inside(self, BB):
74
        """
75
        Inside(BB):
76
77
        Tests if the given Bounding Box is entirely inside this one.
78
79
        Returns True if it is entirely inside, or touching the
80
        border.
81
82
        Returns False otherwise
83
        """
84
        if ( (BB[0,0] >= self[0,0]) and (BB[1,0] <= self[1,0]) and
85
             (BB[0,1] >= self[0,1]) and (BB[1,1] <= self[1,1]) ):
86
            return True
87
        else:
88
            return False
89
    
90
    def PointInside(self, Point):
91
        """
92
        Inside(BB):
93
94
        Tests if the given Point is entirely inside this one.
95
96
        Returns True if it is entirely inside, or touching the
97
        border.
98
99
        Returns False otherwise
100
        
101
        Point is any length-2 sequence (tuple, list, array) or two numbers
102
        """
103
        if Point[0] >= self[0,0] and \
104
               Point[0] <= self[1,0] and \
105
               Point[1] <= self[1,1] and \
106
               Point[1] >= self[0,1]:
107
            return True
108
        else:
109
            return False
110
    
111
    def Merge(self, BB):
112
        """
113
        Joins this bounding box with the one passed in, maybe making this one bigger
114
115
        """ 
116
        if self.IsNull():
117
            self[:] = BB
118
        elif N.isnan(BB).all(): ## BB may be a regular array, so I can't use IsNull
119
            pass
120
        else:
121
            if BB[0,0] < self[0,0]: self[0,0] = BB[0,0]
122
            if BB[0,1] < self[0,1]: self[0,1] = BB[0,1]
123
            if BB[1,0] > self[1,0]: self[1,0] = BB[1,0]
124
            if BB[1,1] > self[1,1]: self[1,1] = BB[1,1]
125
        
126
        return None
127
    
128
    def IsNull(self):
129
        return N.isnan(self).all()
130
131
    ## fixme: it would be nice to add setter, too.
132
    def _getLeft(self):
133
        return self[0,0]
134
    Left = property(_getLeft)
135
    def _getRight(self):
136
        return self[1,0]
137
    Right = property(_getRight)
138
    def _getBottom(self):
139
        return self[0,1]
140
    Bottom = property(_getBottom)
141
    def _getTop(self):
142
        return self[1,1]
143
    Top = property(_getTop)
144
145
    def _getWidth(self):
146
        return self[1,0] - self[0,0]
147
    Width = property(_getWidth)
148
149
    def _getHeight(self):
150
        return self[1,1] - self[0,1]
151
    Height = property(_getHeight)
152
    
153
    def _getCenter(self):
154
        return self.sum(0) / 2.0
155
    Center = property(_getCenter)
156
    ### This could be used for a make BB from a bunch of BBs
157
158
    #~ def _getboundingbox(bboxarray): # lrk: added this
159
        #~ # returns the bounding box of a bunch of bounding boxes
160
        #~ upperleft = N.minimum.reduce(bboxarray[:,0])
161
        #~ lowerright = N.maximum.reduce(bboxarray[:,1])
162
        #~ return N.array((upperleft, lowerright), N.float)
163
    #~ _getboundingbox = staticmethod(_getboundingbox)
164
165
166
    ## Save the ndarray __eq__ for internal use.
167
    Array__eq__ = N.ndarray.__eq__
168
    def __eq__(self, BB):
169
        """
170
        __eq__(BB) The equality operator
171
172
        A == B if and only if all the entries are the same
173
174
        """
175
        if self.IsNull() and N.isnan(BB).all(): ## BB may be a regular array, so I can't use IsNull
176
            return True
177
        else:
178
            return self.Array__eq__(BB).all()
179
        
180
   
181
def asBBox(data):
182
    """
183
    returns a BBox object.
184
185
    If object is a BBox, it is returned unaltered
186
187
    If object is a numpy array, a BBox object is returned that shares a
188
    view of the data with that array. The numpy array should be of the correct
189
    format: a 2x2 numpy array of floats:
190
191
    [[MinX, MinY ],
192
     [MaxX, MaxY ]]
193
    
194
    """
195
196
    if isinstance(data, BBox):
197
        return data
198
    arr = N.asarray(data, N.float)
199
    return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
200
201
def fromPoints(Points):
202
    """
203
    fromPoints (Points).
204
205
    reruns the bounding box of the set of points in Points. Points can
206
    be any python object that can be turned into a numpy NX2 array of Floats.
207
208
    If a single point is passed in, a zero-size Bounding Box is returned.
209
    
210
    """
211
    Points = N.asarray(Points, N.float).reshape(-1,2)
212
213
    arr = N.vstack( (Points.min(0), Points.max(0)) )
214
    return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
215
216
def fromBBArray(BBarray):
217
   """
218
   Builds a BBox object from an array of Bounding Boxes. 
219
   The resulting Bounding Box encompases all the included BBs.
220
   
221
   The BBarray is in the shape: (Nx2x2) where BBarray[n] is a 2x2 array that represents a BBox
222
   """
223
   
224
   #upperleft = N.minimum.reduce(BBarray[:,0])
225
   #lowerright = N.maximum.reduce(BBarray[:,1])
226
227
#   BBarray = N.asarray(BBarray, N.float).reshape(-1,2)
228
#   arr = N.vstack( (BBarray.min(0), BBarray.max(0)) )
229
   BBarray = N.asarray(BBarray, N.float).reshape(-1,2,2)
230
   arr = N.vstack( (BBarray[:,0,:].min(0), BBarray[:,1,:].max(0)) )
231
   return asBBox(arr)
232
   #return asBBox( (upperleft, lowerright) ) * 2
233
   
234
def NullBBox():
235
    """
236
    Returns a BBox object with all NaN entries.
237
    
238
    This represents a Null BB box;
239
    
240
    BB merged with it will return BB.
241
    
242
    Nothing is inside it.
243
244
    """
245
246
    arr = N.array(((N.nan, N.nan),(N.nan, N.nan)), N.float)
247
    return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
248
249
def InfBBox():
250
    """
251
    Returns a BBox object with all -inf and inf entries
252
253
    """
254
255
    arr = N.array(((-N.inf, -N.inf),(N.inf, N.inf)), N.float)
256
    return N.ndarray.__new__(BBox, shape=arr.shape, dtype=arr.dtype, buffer=arr)
257
258
class RectBBox(BBox):
259
    """
260
    subclass of a BBox that can be used for a rotated Rectangle
261
    
262
    contributed by MArco Oster (marco.oster@bioquant.uni-heidelberg.de)
263
264
    """
265
    
266
    def __new__(self, data, edges=None):
267
        return BBox.__new__(self, data)
268
269
    def __init__(self, data, edges=None):
270
        ''' assume edgepoints are ordered such you can walk along all edges with left rotation sense
271
            This may be:
272
            left-top
273
            left-bottom
274
            right-bottom
275
            right-top
276
277
            or any rotation.
278
        '''
279
        BBox.BBox(data)
280
        self.edges = np.asarray(edges)
281
282
        print "new rectbbox created"
283
284
285
    def ac_leftOf_ab(self, a, b, c):
286
        ab = np.array(b) - np.array(a)
287
        ac = np.array(c) - np.array(a)
288
289
        return (ac[0]*ab[1] - ac[1]*ab[0]) <= 0
290
291
    def PointInside(self, point):
292
        print "point inside called"
293
294
        for edge in xrange(4):
295
            if self.ac_leftOf_ab(self.edges[edge],
296
                                 self.edges[(edge+1)%4],
297
                                 point):
298
                continue
299
            else:
300
                return False
301
        return True 
302
    
303