comparison python/moving.py @ 567:072cedc3f33d

first integration of curvilinear transformation from Paul
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Tue, 05 Aug 2014 17:45:36 -0400
parents 259ccb3dd962
children 538fb47b3007 0057c04f94d5
comparison
equal deleted inserted replaced
566:07b1bd0785cd 567:072cedc3f33d
187 from matplotlib.pylab import plot 187 from matplotlib.pylab import plot
188 plot([self.x], [self.y], options, **kwargs) 188 plot([self.x], [self.y], options, **kwargs)
189 189
190 def norm2Squared(self): 190 def norm2Squared(self):
191 '''2-norm distance (Euclidean distance)''' 191 '''2-norm distance (Euclidean distance)'''
192 return self.x*self.x+self.y*self.y 192 return self.x**2+self.y**2
193 193
194 def norm2(self): 194 def norm2(self):
195 '''2-norm distance (Euclidean distance)''' 195 '''2-norm distance (Euclidean distance)'''
196 return sqrt(self.norm2Squared()) 196 return sqrt(self.norm2Squared())
197 197
305 def pointsInPolygon(points, polygon): 305 def pointsInPolygon(points, polygon):
306 '''Optimized tests of a series of points within (Shapely) polygon ''' 306 '''Optimized tests of a series of points within (Shapely) polygon '''
307 prepared_polygon = prep(polygon) 307 prepared_polygon = prep(polygon)
308 return filter(prepared_polygon.contains, points) 308 return filter(prepared_polygon.contains, points)
309 309
310 # Functions for coordinate transformation
311 # From Paul St-Aubin's PVA tools
312 def ppd(*args):
313 ''' Get point-to-point distance.
314
315 Example usage:
316 ==============
317 d = ppd(x1,y1,x2,y2)
318 d = ppd(P1,P2)
319 '''
320
321 if(len(args)==4): [x1,y1,x2,y2] = [args[0],args[1],args[2],args[3]]
322 elif(len(args)==2 and hasattr(args[0], '__iter__')): [x1,y1,x2,y2] = [args[0][0],args[0][1],args[1][0],args[1][1]]
323 else: raise Exception("Library error: ppd() in tools_geo takes exactly 2 or 4 arguments.")
324 return sqrt((x2-x1)**2+(y2-y1)**2)
325
326 def subsec_spline_dist(splines):
327 ''' Prepare list of spline subsegments from a spline list.
328
329 Output:
330 =======
331 ss_spline_d[spline #][mode][station]
332
333 where:
334 mode=0: incremental distance
335 mode=1: cumulative distance
336 mode=2: cumulative distance with trailing distance
337 '''
338
339 from numpy import zeros
340 ss_spline_d = []
341 #Prepare subsegment distances
342 for spline in range(len(splines)):
343 ss_spline_d.append([[],[],[]])
344 ss_spline_d[spline][0] = zeros(len(splines[spline])-1) #Incremental distance
345 ss_spline_d[spline][1] = zeros(len(splines[spline])-1) #Cumulative distance
346 ss_spline_d[spline][2] = zeros(len(splines[spline])) #Cumulative distance with trailing distance
347 for spline_p in range(len(splines[spline])):
348 if spline_p > (len(splines[spline]) - 2):
349 break
350 ss_spline_d[spline][0][spline_p] = ppd(splines[spline][spline_p][0],splines[spline][spline_p][1],splines[spline][(spline_p+1)][0],splines[spline][(spline_p+1)][1]) # TODO use points and remove ppd
351 ss_spline_d[spline][1][spline_p] = sum(ss_spline_d[spline][0][0:spline_p])
352 ss_spline_d[spline][2][spline_p] = sum(ss_spline_d[spline][0][0:spline_p])
353
354 ss_spline_d[spline][2][-1] = ss_spline_d[spline][2][-2] + ss_spline_d[spline][0][-1]
355
356 return ss_spline_d
357
358 def ppldb2p(qx,qy, p0x,p0y, p1x,p1y):
359 ''' Point-projection (Q) on line defined by 2 points (P0,P1).
360 http://cs.nyu.edu/~yap/classes/visual/03s/hw/h2/math.pdf
361 '''
362 if(p0x == p1x and p0y == p1y):
363 return False,False
364 try:
365 #Approximate slope singularity by giving some slope roundoff; account for roundoff error
366 if(round(p0x, 10) == round(p1x, 10)):
367 p1x += 0.0000000001
368 if(round(p0y, 10) == round(p1y, 10)):
369 p1y += 0.0000000001
370 #make the calculation
371 Y = (-(qx)*(p0y-p1y)-(qy*(p0y-p1y)**2)/(p0x-p1x)+p0x**2*(p0y-p1y)/(p0x-p1x)-p0x*p1x*(p0y-p1y)/(p0x-p1x)-p0y*(p0x-p1x))/(p1x-p0x-(p0y-p1y)**2/(p0x-p1x))
372 X = (-Y*(p1y-p0y)+qx*(p1x-p0x)+qy*(p1y-p0y))/(p1x-p0x)
373 except ZeroDivisionError:
374 print('Error: Division by zero in ppldb2p. Please report this error with the full traceback:')
375 print('qx={0}, qy={1}, p0x={2}, p0y={3}, p1x={4}, p1y={5}...'.format(qx, qy, p0x, p0y, p1x, p1y))
376 import pdb; pdb.set_trace()
377 return X,Y
378
379 def getSYfromXY(qx, qy, splines, spline_assumption_threshold=0.5, mode=0):
380 ''' Snap a point (coordinates qx, qy) to it's nearest subsegment of it's nearest spline (from the list splines).
381
382 To force snapping to a single spline, pass the spline alone through splines (e.g. splines=[splines[splineNum]]).
383
384 Output:
385 =======
386 [spline index,
387 subsegment leading point index,
388 snapped x coordinate,
389 snapped y coordinate,
390 subsegment distance,
391 spline distance,
392 orthogonal point offset]
393 '''
394
395 #(buckle in, it gets ugly from here on out)
396 ss_spline_d = subsec_spline_dist(splines)
397
398 temp_dist_min = float('inf')
399 #For each spline
400 for spline in range(len(splines)):
401 #For each spline point
402 for spline_p in range(len(splines[spline])):
403 if(spline_p > (len(splines[spline]) - 2)):
404 break
405 #Get point-intersection distance
406 X,Y = ppldb2p(qx,qy,splines[spline][spline_p][0],splines[spline][spline_p][1],splines[spline][spline_p+1][0],splines[spline][spline_p+1][1])
407 if(X == False and Y == False):
408 print('Error: Spline {0}, segment {1} has identical bounds and therefore is not a vector. Projection cannot continue.'.format(spline, spline_p))
409 return [False,False,False,False,False,False,False]
410 #Check to see if point is not contained by subspline
411 if ss_spline_d[spline][0][spline_p]*1.05 < max(ppd(splines[spline][spline_p][0],splines[spline][spline_p][1],X,Y),ppd(splines[spline][spline_p+1][0],splines[spline][spline_p+1][1],X,Y)): continue
412
413 #Ok
414 temp_dist = ppd(qx,qy,X,Y)
415 if(temp_dist < temp_dist_min):
416 temp_dist_min = temp_dist
417 snappedSpline = spline
418 snappedSplineLeadingPoint = spline_p
419 snapped_x = X
420 snapped_y = Y
421 #Jump loop if significantly close
422 if(temp_dist < spline_assumption_threshold): break
423
424 try:
425 #Get sub-segment distance
426 subsegmentDistance = ppd(splines[snappedSpline][snappedSplineLeadingPoint][0],splines[snappedSpline][snappedSplineLeadingPoint][1],snapped_x,snapped_y)
427 #Get total segment distance
428 splineDistanceS = ss_spline_d[snappedSpline][1][snappedSplineLeadingPoint] + subsegmentDistance
429 #Get offset distance
430 offsetY = ppd(qx,qy, snapped_x,snapped_y)
431
432 if(mode):
433 direction = getOrientation([splines[snappedSpline][snappedSplineLeadingPoint][0],splines[snappedSpline][snappedSplineLeadingPoint][1]] , [splines[snappedSpline][snappedSplineLeadingPoint+1][0],splines[snappedSpline][snappedSplineLeadingPoint+1][1]] , [qx,qy])
434 if(direction == 'left'): offsetY = -offsetY
435
436 return [snappedSpline, snappedSplineLeadingPoint, snapped_x, snapped_y, subsegmentDistance, splineDistanceS, offsetY]
437 except:
438 return [False,False,False,False,False,False,False]
439
440
441 def getXYfromSY(s, y, splineNum, splines, mode = 0):
442 ''' Find X,Y coordinate from S,Y data.
443 if Mode = 0 : return Snapped X,Y
444 if Mode !=0 : return Real X,Y
445 '''
446
447 #(buckle in, it gets ugly from here on out)
448 ss_spline_d = subsec_spline_dist(splines)
449
450 #Find subsegment
451 snapped_x = None
452 snapped_y = None
453 for spline_ss_index in range(len(ss_spline_d[splineNum][1])):
454 if(s < ss_spline_d[splineNum][1][spline_ss_index]):
455 ss_value = s - ss_spline_d[splineNum][1][spline_ss_index-1]
456 #Get normal vector and then snap
457 vector_l_x = (splines[splineNum][spline_ss_index][0] - splines[splineNum][spline_ss_index-1][0])
458 vector_l_y = (splines[splineNum][spline_ss_index][1] - splines[splineNum][spline_ss_index-1][1])
459 magnitude = sqrt(vector_l_x**2 + vector_l_y**2)
460 n_vector_x = vector_l_x/magnitude
461 n_vector_y = vector_l_y/magnitude
462 snapped_x = splines[splineNum][spline_ss_index-1][0] + ss_value*n_vector_x
463 snapped_y = splines[splineNum][spline_ss_index-1][1] + ss_value*n_vector_y
464
465 #Real values (including orthogonal projection of y))
466 real_x = snapped_x - y*n_vector_y
467 real_y = snapped_y + y*n_vector_x
468 break
469
470 if mode == 0 or (not snapped_x):
471 if(not snapped_x):
472 snapped_x = splines[splineNum][-1][0]
473 snapped_y = splines[splineNum][-1][1]
474 return [snapped_x,snapped_y]
475 else:
476 return [real_x,real_y]
477
310 478
311 class NormAngle(object): 479 class NormAngle(object):
312 '''Alternate encoding of a point, by its norm and orientation''' 480 '''Alternate encoding of a point, by its norm and orientation'''
313 481
314 def __init__(self, norm, angle): 482 def __init__(self, norm, angle):
875 def predictPosition(self, instant, nTimeSteps, externalAcceleration = Point(0,0)): 1043 def predictPosition(self, instant, nTimeSteps, externalAcceleration = Point(0,0)):
876 '''Predicts the position of object at instant+deltaT, 1044 '''Predicts the position of object at instant+deltaT,
877 at constant speed''' 1045 at constant speed'''
878 return predictPositionNoLimit(nTimeSteps, self.getPositionAtInstant(instant), self.getVelocityAtInstant(instant), externalAcceleration) 1046 return predictPositionNoLimit(nTimeSteps, self.getPositionAtInstant(instant), self.getVelocityAtInstant(instant), externalAcceleration)
879 1047
1048 def transformToCurvilinear(objects, alignments, ln_mv_av_win=3, verbose=0):
1049 ''' Add, for every object position, the class 'moving.CurvilinearTrajectory()'
1050 (curvilinearPositions instance) which holds information about the
1051 curvilinear coordinates using alignment metadata.
1052 From Paul St-Aubin's PVA tools
1053 ======
1054
1055 Input:
1056 ======
1057 objects = list of objects supplied by Traffic Intelligence
1058 alignments = a list of alignments, where each alignment is a list of
1059 points, where each point is a list of coordinates, e.g.
1060 alignments[alpha][1][x] is coordinate x for point number
1061 1 of the spline that represents alignment alpha.
1062 alignments can also be a compatible object that mimics a
1063 3-dimensional list using the __getitem__() method as is
1064 the case in the PVAT specification.
1065 ln_mv_av_win = moving average window (in points) in which to smooth
1066 lane changes. As per tools_math.cat_mvgavg(), this term
1067 is a search *radius* around the center of the window.
1068
1069 Output: (objects, dropped_traj)
1070 =======
1071 objects = modified list of objects
1072 dropped_traj = list of objects or object segments that were
1073 truncated. The format is identical to that of objects.
1074 '''
1075 if(len(objects) <= 0): return None, None
1076 if(verbose >= 2):
1077 print(' -------------')
1078 print(' Transforming coordinates...')
1079
1080 lane_readjustments = 0
1081 dropped_traj = []
1082 original_object_length = len(objects)
1083 reset_objects = 0
1084
1085 #if(not isinstance(objects, list)):
1086 # objects = [objects]
1087 # reset_objects = 1
1088 #if(not isinstance(objects[0], TrafIntMoving.MovingObject)):
1089 # return objects, dropped_traj
1090
1091 #For each object
1092 for i in range(len(objects)):
1093 objects[i].curvilinearPositions = CurvilinearTrajectory([],[],[])
1094
1095 #For each point
1096 for point in range(len(objects[i].getXCoordinates())):
1097 [align, alignPoint, snapped_x, snapped_y, subsegmentDistance, S, Y] = PvaTools.Geo.getSYfromXY(objects[i].getXCoordinates()[point],objects[i].getYCoordinates()[point],alignments)
1098
1099 # Error handling
1100 if(align is False):
1101 if(verbose >= 2): print(' Warning: trajectory '+str(i)+' at point '+str(point)+' has alignment errors (spline snapping)')
1102 dropped_traj.append(objects[i])
1103 objects[i] = None
1104 break
1105 else: objects[i].curvilinearPositions.addPosition(S, Y, align)
1106
1107 if(objects[i] == None): continue
1108
1109 ## Go back through points and correct lane
1110 #Run through objects looking for outlier point
1111 smoothed_lanes = PvaTools.Math.cat_mvgavg(objects[i].curvilinearPositions.getLanes(),window=ln_mv_av_win)
1112 ## Recalculate smoothed lanes
1113 if(objects[i].curvilinearPositions.getLanes() != smoothed_lanes):
1114 for point in range(len(objects[i].getXCoordinates())):
1115 if(objects[i].curvilinearPositions.getLanes()[point] != smoothed_lanes[point]):
1116 [align, alignPoint, snapped_x, snapped_y, subsegmentDistance, S, Y] = PvaTools.Geo.getSYfromXY(objects[i].getXCoordinates()[point],objects[i].getYCoordinates()[point],[alignments[smoothed_lanes[point]]])
1117
1118 # Error handling
1119 if(align is False):
1120 ## This can be triggered by tracking errors when the trajectory jumps around passed another alignment.
1121 if(verbose >= 4): print(' Warning: trajectory '+str(i)+' at point '+str(point)+' has alignment errors during trajectory smoothing and will not be corrected.')
1122 else:
1123 objects[i].curvilinearPositions.setPosition(point, S, Y, align)
1124
1125 #Resize objects
1126 if(len(dropped_traj) > 0):
1127 objects = filter(None, objects)
1128 if(verbose >= 2): print(' Filtering report: Trajectories dropped: '+str(len(dropped_traj)))
1129 if(verbose >= 2): print(' Filtering report: Lane observation corrections per object: '+str(lane_readjustments/original_object_length))
1130
1131 if(reset_objects and len(objects) > 0): return objects[0], dropped_traj
1132 else: return objects, dropped_traj
1133
1134
880 def computeSmoothTrajectory(self, minCommonIntervalLength): 1135 def computeSmoothTrajectory(self, minCommonIntervalLength):
881 '''Computes the trajectory as the mean of all features 1136 '''Computes the trajectory as the mean of all features
882 if a feature exists, its position is 1137 if a feature exists, its position is
883 1138
1139 Warning work in progress
884 TODO? not use the first/last 1-.. positions''' 1140 TODO? not use the first/last 1-.. positions'''
885 from numpy import array, median 1141 from numpy import array, median
886 nFeatures = len(self.features) 1142 nFeatures = len(self.features)
887 if nFeatures == 0: 1143 if nFeatures == 0:
888 print('Empty object features\nCannot compute smooth trajectory') 1144 print('Empty object features\nCannot compute smooth trajectory')