Mercurial Hosting > traffic-intelligence
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') |