~wecacuee/mrol/mrol-dev

« back to all changes in this revision

Viewing changes to mrol_mapping/mrol.py

  • Committer: Julian Ryde
  • Date: 2011-06-03 16:52:15 UTC
  • Revision ID: julian_ryde-20110603165215-oqr6yo9nwng3j9nb
Initial import from revision 206 of Julian Ryde's mrol_mapping repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''
 
2
Implementation of multi-resolution occupied voxel lists (MROL) 
 
3
Author: Julian Ryde and  Nick Hillier
 
4
'''
 
5
from __future__ import division
 
6
 
 
7
import time
 
8
import os
 
9
import numpy as np
 
10
 
 
11
import occupiedlist
 
12
import optimization
 
13
import util
 
14
import poseutil
 
15
import quantizer
 
16
 
 
17
import cython.fast as fast
 
18
 
 
19
#modalfractions = [0.7, 0.8, 0.9, 0.9, 0.9] 
 
20
 
 
21
modalfractions = np.repeat(0.3, 5)
 
22
topn = 100
 
23
 
 
24
# TODO consider pulling in table class from icra2011 code, table.py
 
25
def printtable(table_array, width):
 
26
    for row in table_array:
 
27
        for field in row:
 
28
            print '| ' + field.ljust(width),
 
29
        print
 
30
        
 
31
def _cost_min_scipy(objective_func, initialpose, args):
 
32
    import scipy.optimize as optimize
 
33
    initialpose = initialpose.getTuple()
 
34
    returns = optimize.fmin(objective_func, initialpose, args=args, disp=True, full_output=True)
 
35
    #returns = optimize.fmin_powell(objective_func, initialpose, args=args, disp=True, full_output=True)
 
36
    #returns = optimize.fmin_slsqp(objective_func, initialpose, args=args, full_output=True) # doesn't work?    
 
37
    #initpose = np.asarray(initialpose)
 
38
    #delta = np.asarray((0.5, 0.5, 0.5, 0.2, 0.2, 0.2))
 
39
    #lower = initpose - delta
 
40
    #upper = initpose + delta    
 
41
    #returns = optimize.anneal(objective_func, initialpose, args=args, lower=lower, upper=upper)
 
42
    bestpose = poseutil.Pose3D(X=returns[0])
 
43
    cost = returns[1]
 
44
    return bestpose, cost
 
45
 
 
46
class MROL:
 
47
    '''
 
48
    Stores voxel indices at multiple resolution occupied voxel lists
 
49
    and provides interfaces to add, remove and manipulate the data.
 
50
    '''
 
51
    def __init__(self, resolution, levels=3):
 
52
        # coarse to fine resolutions
 
53
        self.factor = 2 # multiplier between the resolutions might have to be an integer?
 
54
        resolutions = (pow(self.factor, np.arange(levels))*resolution)[::-1]
 
55
        self.mapvoxels = [] # list of occupied voxel lists
 
56
        self.maptimes = []
 
57
        for i, res in enumerate(resolutions):
 
58
            ol = occupiedlist.OccupiedList(res)
 
59
            self.mapvoxels.append(ol)
 
60
            #self.maptimes.append([]) # TODO
 
61
 
 
62
        self.keyframe = set()
 
63
        self.feature_range = 8.0
 
64
        # range to a good feature density for angular alignment purposes
 
65
 
 
66
    def __repr__(self):
 
67
        return self.__str__()
 
68
 
 
69
    def __str__(self):
 
70
        width = 6
 
71
        delim = '\t'
 
72
        resolutions = (str(m.resolution).rjust(width) for m in self.mapvoxels)
 
73
        retstr = 'resolutions:  ' + delim.join(resolutions) + '\nvoxel counts: '
 
74
        voxel_counts = (str(len(m)).rjust(width) for m in self.mapvoxels)
 
75
        retstr += delim.join(voxel_counts)
 
76
        return retstr 
 
77
    
 
78
    def __len__(self):
 
79
        return len(self.mapvoxels)
 
80
 
 
81
    def __eq__(self, mrol_map):
 
82
        return self.__dict__ == mrol_map.__dict__
 
83
        
 
84
    def getresolution(self, level=None):
 
85
        if level == None: return self.getfinest().resolution
 
86
        return self.mapvoxels[level].resolution
 
87
 
 
88
    def getpoints(self, level=-1):
 
89
        return self.mapvoxels[level].getpoints()
 
90
 
 
91
    def getvoxels(self, level=-1):
 
92
        return self.mapvoxels[level].getvoxels()
 
93
 
 
94
    def getfinest(self):
 
95
        '''
 
96
        Returns the occupied list with the finest/best resolution it has.
 
97
        '''
 
98
        return self.mapvoxels[-1]
 
99
        
 
100
    def addpoints(self, xyzs, timestamp=None, userdata=None, increment=1):
 
101
        # TODO do something with timestamps for re-journalling
 
102
        # TODO allow userdata to be added as an information field to the voxels 
 
103
        # (e.g. RGB)
 
104
        # TODO work out how to get the userdata out again (perhaps in 
 
105
        # getpoints/getvoxels?)        
 
106
 
 
107
        for ol in self.mapvoxels:
 
108
            ol.addpoints(xyzs)
 
109
 
 
110
        ## add to the finest occupiedlist
 
111
        #modified_inds, increments = self.getfinest().addpoints(xyzs, return_modified=True, increment=increment)
 
112
        ##modified_inds_finest = modified_inds.copy()
 
113
        ## now propagate these modifications to coarser resolutions
 
114
        #for ol in self.mapvoxels[-2::-1]:
 
115
        #    modified_inds /= self.factor
 
116
        #    # TODO slow but hopefully more likely to be correct way
 
117
        #    ol._update_voxels(modified_inds, increments)
 
118
        ##return modified_inds_finest
 
119
 
 
120
    def removepoints(self, xyzs, return_removed=False):
 
121
        return self.addpoints(xyzs, increment=-1)
 
122
            
 
123
    # TODO Reconsider keyframe usage after trajectory class is implemented
 
124
    #def setkeyframe(self, xyzs, pose):
 
125
    #    xyzs = poseutil.transformPointsTuple(xyzs,pose)
 
126
    #    voxel_array = occupiedlist.pointstovoxels(xyzs, self.getresolution(), unique=True)
 
127
    #    self.keyframe = set(occupiedlist.hashrows(voxel_array))
 
128
    #    
 
129
    #def getkeyframeset(self):
 
130
    #    return self.keyframe
 
131
        
 
132
    def optimise_alignment(self, xyzs, initialpose, full_data=False):
 
133
        ''' The format of this function allows us to use standard optimizers 
 
134
        and arbitrary cost functions from the scipy.optimize library.'''
 
135
        dtheta_base = np.radians(0.5)
 
136
        if not full_data:
 
137
            xyzs_sample = util.volumetricsample(xyzs, self.getfinest().resolution)
 
138
        else:
 
139
            xyzs_sample = xyzs
 
140
        bestpose = initialpose
 
141
        for level, ol in enumerate(self.mapvoxels):
 
142
            objective_func = ol.cost_func
 
143
            #objective_func = self.objectivefuncMROL
 
144
            #ol.use_bloom = False
 
145
            dtheta = dtheta_base*pow(self.factor,(len(self.mapvoxels)-level-1))
 
146
            dx = ol.resolution
 
147
            # This helps avoid local minima, but limits accuracy to voxel size 
 
148
            # at the finest resolution
 
149
            bestpose, cost = optimization.cost_min(objective_func, bestpose, (xyzs_sample,), dx, dtheta, max_iterations=20, verbosity=0)
 
150
            #import pydb; pydb.set_trace()
 
151
            #bestpose, cost = _cost_min_scipy(objective_func, bestpose, (xyzs_sample,))
 
152
        # run it again at the finest resolution with tighter steps to get 
 
153
        # sub-voxel accuracy.
 
154
        dx = dx*pow(self.factor,-2)
 
155
        dtheta = dtheta_base*pow(self.factor,-2)
 
156
        if not full_data:
 
157
            # Sometimes get better results with the below resampling, but much faster.
 
158
            xyzs = util.offsetvolsample(xyzs, self.getfinest().resolution)
 
159
        bestpose, cost = optimization.cost_min(objective_func, bestpose, (xyzs,), dx, dtheta, max_iterations=100, verbosity=0)
 
160
        return bestpose, cost
 
161
 
 
162
    def objectivefuncMROL(self, pose, xyzs):
 
163
        '''
 
164
        Objective function to be minimised.
 
165
        For a list of xyzs, transforms by pose and calculations the cost
 
166
        at the various resolutions. If level is specified, only 
 
167
        calc at that level.
 
168
        '''
 
169
        levels = len(self.mapvoxels)
 
170
        hits = np.empty(levels)
 
171
        resolutions = np.empty(levels)
 
172
        for i, ol in enumerate(self.mapvoxels):
 
173
            hits[i] = ol.calccollisions(pose, xyzs, query=False)
 
174
            resolutions[i] = ol.resolution
 
175
        # sort hits from coarse to fine
 
176
        new = np.hstack((-np.diff(hits), hits[-1]))
 
177
        #weights = np.ones(levels)
 
178
        weights = resolutions[-1]/resolutions # same as ..., 1/8, 1/4, 1/2, 1
 
179
        #cost = -np.dot(weights, hits)
 
180
        cost = -np.dot(weights, new)
 
181
        return cost
 
182
     
 
183
    def calcray(self, ray):    
 
184
        ol = self.getfinest()
 
185
        hits = ol.segment_ovl(None, ray)[0] 
 
186
        if len(hits) > 0:
 
187
            vox_hits = occupiedlist.pointstovoxels(hits,ol.resolution,ol.lattice)
 
188
            return quantizer.uniquerows(vox_hits, 0)
 
189
        else:
 
190
            return []
 
191
            
 
192
    def raytrace(self, pose, length, axis=0):
 
193
        '''
 
194
        perform a ray-trace from the pose and return all map voxels at the
 
195
        base resolution along the ray.
 
196
        It traces from any point in the map frame (e.g. the sensor's
 
197
        current position) along a ray oriented as per the pose's angular 
 
198
        components.
 
199
        '''
 
200
        res = self.getresolution()/2.
 
201
        #import pdb;pdb.set_trace()
 
202
        numcells = np.ceil(length/res)+2 # + 1 for 0th point + 1 for end point with poor aligned voxels
 
203
        ray = np.zeros([numcells,3])
 
204
        ray[:,axis] = np.hstack([0,np.cumsum(np.ones(numcells-1))*res])
 
205
        ray = poseutil.check_and_transform_points(ray, pose)[0]
 
206
        return self.calcray(ray)
 
207
        
 
208
    def raytracepts(self, point1, point2, return_extras=False):
 
209
        '''
 
210
        perform a ray-trace from point1 to point2 and return all map 
 
211
        voxels at the base resolution along the ray.
 
212
        '''
 
213
        res = self.getresolution()#/3.
 
214
        point2a = point2 - point1
 
215
        range2 = np.sum(point2a**2)
 
216
        length = np.sqrt(range2)
 
217
        dir_unit = point2a/length
 
218
        numcells = int(np.ceil(length/res)+2) # + 1 for 0th point + 1 for last point
 
219
        ray = np.zeros([numcells,3])
 
220
        ## TODO make this a list comprehension as this loop slows the cast down lots
 
221
        #for i in range(numcells):
 
222
        #    ray[i] = dir_unit*i*res
 
223
        ray_res = length/(numcells-1)
 
224
        ray = fast.fillray(numcells, dir_unit, ray_res, ray)
 
225
        ray = ray + point1
 
226
        if return_extras:
 
227
            return self.calcray(ray), length, dir_unit, ray
 
228
        else:
 
229
            return self.calcray(ray)
 
230
        
 
231
                
 
232
    def add_dilate(self, xyzs):
 
233
        self.dilated = occupiedlist.dilate(self.getvoxels())
 
234
 
 
235
    def match(self, xyzs, centrepose, spreadpose):
 
236
        """Does a global search of the specified pose space.
 
237
        Find the possible locations of the occupied list ol in the current 
 
238
        occupied list.  Takes an occupied list to match to the current one and 
 
239
        two poses."""
 
240
        debug = False
 
241
 
 
242
        # determines the orientation step size as it relates to occupancy list 
 
243
        # resolution
 
244
        poses = None
 
245
        bestpose = poseutil.Pose3D()
 
246
 
 
247
        totaltime = time.time()
 
248
        lasttime = time.time()
 
249
        #for i in range(len(self.voxels)):
 
250
        # volumetric sample and each resolution equivalent to matching two 
 
251
        # occupiedlists together but without the double quantisation error
 
252
        # TODO think about this number
 
253
        #xyzs = util.getsample(xyzs, 1000)
 
254
 
 
255
        levels = len(self.mapvoxels)
 
256
        table = np.zeros((levels+2, 9), dtype=np.dtype('S8'))
 
257
        # header for the table of values of interest
 
258
        table[0, :] = "Res modal map    scan   pose  pose  Top     Time  Best".split()
 
259
        table[1, :] = "(m) Frac  voxels points count count overlap Taken pose".split()
 
260
        for i, mapol in enumerate(self.mapvoxels):
 
261
            tablerow = i + 2
 
262
            res = mapol.resolution
 
263
            deltaposition = res
 
264
            deltaorientation = res/self.feature_range # 8
 
265
            if poses == None:
 
266
                poses = poseutil.uniformposes(centrepose, spreadpose, deltaposition, deltaorientation)
 
267
            else:
 
268
                poses = poseutil.refineposes(poses, deltaposition, deltaorientation)
 
269
 
 
270
            # TODO remove poses outside search space this needs tidying up/thinking about
 
271
            #cparr = np.array(centrepose.get())
 
272
            #sparr = np.array(spreadpose.get())
 
273
            #
 
274
            #D = np.array((deltaposition, deltaposition, deltaposition, deltaorientation, deltaorientation, deltaorientation))
 
275
            #maxpose = cparr + sparr + D
 
276
            #minpose = cparr - sparr - D
 
277
            #select = np.logical_and(np.all(poses <= maxpose, 1), np.all(poses >= minpose, 1))
 
278
            ##print 'Culling poses: ', len(poses), sum(select)
 
279
            #poses = poses[select]
 
280
            
 
281
            xyzs_sample = util.volumetricsample(xyzs, res)
 
282
            table[tablerow, :5] = (res, modalfractions[i], len(mapol), len(xyzs_sample), len(poses))
 
283
 
 
284
            # TODO TIME consuming line
 
285
            #overlaps = self.getoverlaps(ol, i, poses)
 
286
            overlaps = []
 
287
            for pose in poses:
 
288
                #overlap = np.sum(mapol.calccollisions(pose, xyzs, query=True))
 
289
                overlap = mapol.calccollisions(pose, xyzs, query=False)
 
290
                overlaps.append(overlap)
 
291
                #overlaps.append(-self.objectivefuncMROL(pose, xyzs_sample))
 
292
            overlaps = np.asarray(overlaps)
 
293
 
 
294
            ind_max = np.argmax(overlaps)
 
295
            bestpose.set(poses[ind_max])
 
296
            bestoverlap = overlaps[ind_max]
 
297
            #candidates = overlaps >= modalfractions[i] * bestoverlap
 
298
            # maybe just take the top n?
 
299
            if len(overlaps) > topn:
 
300
                candidates = overlaps.argsort()[-topn:]
 
301
            else:
 
302
                candidates = range(len(overlaps))
 
303
            timetaken = time.time() - lasttime
 
304
            lasttime = time.time()
 
305
            table[tablerow, 5:] = (len(candidates), bestoverlap, timetaken, bestpose)
 
306
            print
 
307
            printtable(table, width=9)
 
308
            print bestpose
 
309
            #scansize = ol.voxels[-1].shape[0]
 
310
            poses = poses[candidates]
 
311
 
 
312
        print "Total time: ", time.time() - totaltime
 
313
        overlaps = overlaps[candidates]
 
314
        return bestpose, bestoverlap
 
315
        
 
316
    def matchbest(self, xyzs, centrepose, spreadpose):
 
317
        poses, overlaps = self.match(xyzs, centrepose, spreadpose)
 
318
        bestind = np.argmax(overlaps)
 
319
        bestpose = poseutil.Pose3D(poses[bestind, :])
 
320
        cost = overlaps[bestind]
 
321
        return bestpose, cost
 
322
    
 
323
    def matchandadd(self, ol, centrepose, spreadpose):
 
324
        """ matches the given occupiedlist ol by searching around the 
 
325
        centrepose a distance of spreadpose. If the match is successful the 
 
326
        occupied list is transfomred and combined with the current occupied 
 
327
        list. Returns the pose at which the occupied list ol was added."""
 
328
        # TODO add a threshold for deciding whether to merge 
 
329
        # TODO add unit test for this
 
330
        # TODO add a ray trace clear?
 
331
        poses, overlaps = self.match(ol, centrepose, spreadpose)
 
332
        bestpose = poseutil.Pose3D(poses[np.argmax(overlaps), :])
 
333
        self.addpoints(bestpose.transformPoints(ol.getpoints()))
 
334
        return bestpose
 
335