Mercurial > hg > nsaunier > traffic-intelligence
comparison python/moving.py @ 795:a34ec862371f
merged with dev branch
| author | Nicolas Saunier <nicolas.saunier@polymtl.ca> |
|---|---|
| date | Mon, 09 May 2016 15:33:11 -0400 |
| parents | 3aa6102ccc12 |
| children | 21f10332c72b |
comparison
equal
deleted
inserted
replaced
| 758:0a05883216cf | 795:a34ec862371f |
|---|---|
| 10 from scipy.stats import scoreatpercentile | 10 from scipy.stats import scoreatpercentile |
| 11 from scipy.spatial.distance import cdist | 11 from scipy.spatial.distance import cdist |
| 12 | 12 |
| 13 try: | 13 try: |
| 14 from shapely.geometry import Polygon, Point as shapelyPoint | 14 from shapely.geometry import Polygon, Point as shapelyPoint |
| 15 from shapely.prepared import prep | 15 from shapely.prepared import prep, PreparedGeometry |
| 16 shapelyAvailable = True | 16 shapelyAvailable = True |
| 17 except ImportError: | 17 except ImportError: |
| 18 print('Shapely library could not be loaded') | 18 print('Shapely library could not be loaded') |
| 19 shapelyAvailable = False | 19 shapelyAvailable = False |
| 20 | 20 |
| 33 return '[{0}, {1}]'.format(self.first, self.last) | 33 return '[{0}, {1}]'.format(self.first, self.last) |
| 34 | 34 |
| 35 def __repr__(self): | 35 def __repr__(self): |
| 36 return self.__str__() | 36 return self.__str__() |
| 37 | 37 |
| 38 def __eq__(self, other): | |
| 39 return ((self.first == other.first) and (self.last == other.last)) or ((self.first == other.last) and (self.last == other.first)) | |
| 40 | |
| 38 def empty(self): | 41 def empty(self): |
| 39 return self.first > self.last | 42 return self.first > self.last |
| 40 | 43 |
| 41 def center(self): | 44 def center(self): |
| 42 return (self.first+self.last)/2. | 45 return (self.first+self.last)/2. |
| 55 return (self.first<=instant and self.last>=instant) | 58 return (self.first<=instant and self.last>=instant) |
| 56 | 59 |
| 57 def inside(self, interval2): | 60 def inside(self, interval2): |
| 58 '''Indicates if the temporal interval of self is comprised in interval2''' | 61 '''Indicates if the temporal interval of self is comprised in interval2''' |
| 59 return (self.first >= interval2.first) and (self.last <= interval2.last) | 62 return (self.first >= interval2.first) and (self.last <= interval2.last) |
| 63 | |
| 64 def shift(self, offset): | |
| 65 self.first += offset | |
| 66 self.last += offset | |
| 60 | 67 |
| 61 @classmethod | 68 @classmethod |
| 62 def union(cls, interval1, interval2): | 69 def union(cls, interval1, interval2): |
| 63 '''Smallest interval comprising self and interval2''' | 70 '''Smallest interval comprising self and interval2''' |
| 64 return cls(min(interval1.first, interval2.first), max(interval2.last, interval2.last)) | 71 return cls(min(interval1.first, interval2.first), max(interval2.last, interval2.last)) |
| 169 return self.timeInterval.contains(t) | 176 return self.timeInterval.contains(t) |
| 170 | 177 |
| 171 def commonTimeInterval(self, obj2): | 178 def commonTimeInterval(self, obj2): |
| 172 return TimeInterval.intersection(self.getTimeInterval(), obj2.getTimeInterval()) | 179 return TimeInterval.intersection(self.getTimeInterval(), obj2.getTimeInterval()) |
| 173 | 180 |
| 181 def shiftTimeInterval(self, offset): | |
| 182 self.timeInterval.shift(offset) | |
| 183 | |
| 174 class Point(object): | 184 class Point(object): |
| 175 def __init__(self, x, y): | 185 def __init__(self, x, y): |
| 176 self.x = x | 186 self.x = x |
| 177 self.y = y | 187 self.y = y |
| 178 | 188 |
| 179 def __str__(self): | 189 def __str__(self): |
| 180 return '({:f},{:f})'.format(self.x,self.y) | 190 return '({:f},{:f})'.format(self.x,self.y) |
| 181 | 191 |
| 182 def __repr__(self): | 192 def __repr__(self): |
| 183 return self.__str__() | 193 return self.__str__() |
| 194 | |
| 195 def __eq__(self, other): | |
| 196 return (self.x == other.x) and (self.y == other.y) | |
| 184 | 197 |
| 185 def __add__(self, other): | 198 def __add__(self, other): |
| 186 return Point(self.x+other.x, self.y+other.y) | 199 return Point(self.x+other.x, self.y+other.y) |
| 187 | 200 |
| 188 def __sub__(self, other): | 201 def __sub__(self, other): |
| 214 'Warning, returns a new Point' | 227 'Warning, returns a new Point' |
| 215 return Point(self.x/alpha, self.y/alpha) | 228 return Point(self.x/alpha, self.y/alpha) |
| 216 | 229 |
| 217 def plot(self, options = 'o', **kwargs): | 230 def plot(self, options = 'o', **kwargs): |
| 218 plot([self.x], [self.y], options, **kwargs) | 231 plot([self.x], [self.y], options, **kwargs) |
| 232 | |
| 233 @staticmethod | |
| 234 def plotSegment(p1, p2, options = 'o', **kwargs): | |
| 235 plot([p1.x, p2.x], [p1.y, p2.y], options, **kwargs) | |
| 219 | 236 |
| 220 def norm2Squared(self): | 237 def norm2Squared(self): |
| 221 '''2-norm distance (Euclidean distance)''' | 238 '''2-norm distance (Euclidean distance)''' |
| 222 return self.x**2+self.y**2 | 239 return self.x**2+self.y**2 |
| 223 | 240 |
| 337 'Returns the middle of the segment [p1, p2]' | 354 'Returns the middle of the segment [p1, p2]' |
| 338 return Point(0.5*p1.x+0.5*p2.x, 0.5*p1.y+0.5*p2.y) | 355 return Point(0.5*p1.x+0.5*p2.x, 0.5*p1.y+0.5*p2.y) |
| 339 | 356 |
| 340 if shapelyAvailable: | 357 if shapelyAvailable: |
| 341 def pointsInPolygon(points, polygon): | 358 def pointsInPolygon(points, polygon): |
| 342 '''Optimized tests of a series of points within (Shapely) polygon ''' | 359 '''Optimized tests of a series of points within (Shapely) polygon (not prepared)''' |
| 343 prepared_polygon = prep(polygon) | 360 if type(polygon) == PreparedGeometry: |
| 361 prepared_polygon = polygon | |
| 362 else: | |
| 363 prepared_polygon = prep(polygon) | |
| 344 return filter(prepared_polygon.contains, points) | 364 return filter(prepared_polygon.contains, points) |
| 345 | 365 |
| 346 # Functions for coordinate transformation | 366 # Functions for coordinate transformation |
| 347 # From Paul St-Aubin's PVA tools | 367 # From Paul St-Aubin's PVA tools |
| 348 def subsec_spline_dist(splines): | 368 def subsec_spline_dist(splines): |
| 358 mode=2: cumulative distance with trailing distance | 378 mode=2: cumulative distance with trailing distance |
| 359 ''' | 379 ''' |
| 360 ss_spline_d = [] | 380 ss_spline_d = [] |
| 361 #Prepare subsegment distances | 381 #Prepare subsegment distances |
| 362 for spline in range(len(splines)): | 382 for spline in range(len(splines)): |
| 363 ss_spline_d.append([[],[],[]]) | 383 ss_spline_d[spline]=[]#.append([[],[],[]]) |
| 364 ss_spline_d[spline][0] = zeros(len(splines[spline])-1) #Incremental distance | 384 ss_spline_d[spline].append(zeros(len(splines[spline])-1)) #Incremental distance |
| 365 ss_spline_d[spline][1] = zeros(len(splines[spline])-1) #Cumulative distance | 385 ss_spline_d[spline].append(zeros(len(splines[spline])-1)) #Cumulative distance |
| 366 ss_spline_d[spline][2] = zeros(len(splines[spline])) #Cumulative distance with trailing distance | 386 ss_spline_d[spline].append(zeros(len(splines[spline]))) #Cumulative distance with trailing distance |
| 367 for spline_p in range(len(splines[spline])): | 387 for spline_p in range(len(splines[spline])): |
| 368 if spline_p > (len(splines[spline]) - 2): | 388 if spline_p > (len(splines[spline]) - 2): |
| 369 break | 389 break |
| 370 ss_spline_d[spline][0][spline_p] = utils.pointDistanceL2(splines[spline][spline_p][0],splines[spline][spline_p][1],splines[spline][(spline_p+1)][0],splines[spline][(spline_p+1)][1]) | 390 ss_spline_d[spline][0][spline_p] = utils.pointDistanceL2(splines[spline][spline_p][0],splines[spline][spline_p][1],splines[spline][(spline_p+1)][0],splines[spline][(spline_p+1)][1]) |
| 371 ss_spline_d[spline][1][spline_p] = sum(ss_spline_d[spline][0][0:spline_p]) | 391 ss_spline_d[spline][1][spline_p] = sum(ss_spline_d[spline][0][0:spline_p]) |
| 372 ss_spline_d[spline][2][spline_p] = ss_spline_d[spline][1][spline_p]#sum(ss_spline_d[spline][0][0:spline_p]) | 392 ss_spline_d[spline][2][spline_p] = ss_spline_d[spline][1][spline_p]#sum(ss_spline_d[spline][0][0:spline_p]) |
| 373 | 393 |
| 374 ss_spline_d[spline][2][-1] = ss_spline_d[spline][2][-2] + ss_spline_d[spline][0][-1] | 394 ss_spline_d[spline][2][-1] = ss_spline_d[spline][2][-2] + ss_spline_d[spline][0][-1] |
| 375 | 395 |
| 376 return ss_spline_d | 396 return ss_spline_d |
| 397 | |
| 398 def prepareSplines(splines): | |
| 399 'Approximates slope singularity by giving some slope roundoff; account for roundoff error' | |
| 400 for spline in splines: | |
| 401 p1 = spline[0] | |
| 402 for i in xrange(len(spline)-1): | |
| 403 p2 = spline[i+1] | |
| 404 if(round(p1.x, 10) == round(p2.x, 10)): | |
| 405 p2.x += 0.0000000001 | |
| 406 if(round(p1.y, 10) == round(p2.y, 10)): | |
| 407 p2.y += 0.0000000001 | |
| 408 p1 = p2 | |
| 377 | 409 |
| 378 def ppldb2p(qx,qy, p0x,p0y, p1x,p1y): | 410 def ppldb2p(qx,qy, p0x,p0y, p1x,p1y): |
| 379 ''' Point-projection (Q) on line defined by 2 points (P0,P1). | 411 ''' Point-projection (Q) on line defined by 2 points (P0,P1). |
| 380 http://cs.nyu.edu/~yap/classes/visual/03s/hw/h2/math.pdf | 412 http://cs.nyu.edu/~yap/classes/visual/03s/hw/h2/math.pdf |
| 381 ''' | 413 ''' |
| 382 if(p0x == p1x and p0y == p1y): | 414 if(p0x == p1x and p0y == p1y): |
| 383 return None | 415 return None |
| 384 try: | 416 try: |
| 385 #Approximate slope singularity by giving some slope roundoff; account for roundoff error | 417 #Approximate slope singularity by giving some slope roundoff; account for roundoff error |
| 386 if(round(p0x, 10) == round(p1x, 10)): | 418 # if(round(p0x, 10) == round(p1x, 10)): |
| 387 p1x += 0.0000000001 | 419 # p1x += 0.0000000001 |
| 388 if(round(p0y, 10) == round(p1y, 10)): | 420 # if(round(p0y, 10) == round(p1y, 10)): |
| 389 p1y += 0.0000000001 | 421 # p1y += 0.0000000001 |
| 390 #make the calculation | 422 #make the calculation |
| 391 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)) | 423 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)) |
| 392 X = (-Y*(p1y-p0y)+qx*(p1x-p0x)+qy*(p1y-p0y))/(p1x-p0x) | 424 X = (-Y*(p1y-p0y)+qx*(p1x-p0x)+qy*(p1y-p0y))/(p1x-p0x) |
| 393 except ZeroDivisionError: | 425 except ZeroDivisionError: |
| 394 print('Error: Division by zero in ppldb2p. Please report this error with the full traceback:') | 426 print('Error: Division by zero in ppldb2p. Please report this error with the full traceback:') |
| 395 print('qx={0}, qy={1}, p0x={2}, p0y={3}, p1x={4}, p1y={5}...'.format(qx, qy, p0x, p0y, p1x, p1y)) | 427 print('qx={0}, qy={1}, p0x={2}, p0y={3}, p1x={4}, p1y={5}...'.format(qx, qy, p0x, p0y, p1x, p1y)) |
| 396 import pdb; pdb.set_trace() | 428 import pdb; pdb.set_trace() |
| 397 return Point(X,Y) | 429 return Point(X,Y) |
| 398 | 430 |
| 399 def getSYfromXY(p, splines, goodEnoughSplineDistance = 0.5): | 431 def getSYfromXY(p, splines, goodEnoughSplineDistance = 0.5): |
| 400 ''' Snap a point p to it's nearest subsegment of it's nearest spline (from the list splines). A spline is a list of points (class Point), most likely a trajectory. | 432 ''' Snap a point p to it's nearest subsegment of it's nearest spline (from the list splines). |
| 401 | 433 A spline is a list of points (class Point), most likely a trajectory. |
| 434 | |
| 402 Output: | 435 Output: |
| 403 ======= | 436 ======= |
| 404 [spline index, | 437 [spline index, |
| 405 subsegment leading point index, | 438 subsegment leading point index, |
| 406 snapped point, | 439 snapped point, |
| 410 | 443 |
| 411 or None | 444 or None |
| 412 ''' | 445 ''' |
| 413 minOffsetY = float('inf') | 446 minOffsetY = float('inf') |
| 414 #For each spline | 447 #For each spline |
| 415 for spline in range(len(splines)): | 448 for splineIdx in range(len(splines)): |
| 416 #For each spline point index | 449 #For each spline point index |
| 417 for spline_p in range(len(splines[spline])-1): | 450 for spline_p in range(len(splines[splineIdx])-1): |
| 418 #Get closest point on spline | 451 #Get closest point on spline |
| 419 closestPoint = ppldb2p(p.x,p.y,splines[spline][spline_p][0],splines[spline][spline_p][1],splines[spline][spline_p+1][0],splines[spline][spline_p+1][1]) | 452 closestPoint = ppldb2p(p.x,p.y,splines[splineIdx][spline_p][0],splines[splineIdx][spline_p][1],splines[splineIdx][spline_p+1][0],splines[splineIdx][spline_p+1][1]) |
| 420 if closestPoint is None: | 453 if closestPoint is None: |
| 421 print('Error: Spline {0}, segment {1} has identical bounds and therefore is not a vector. Projection cannot continue.'.format(spline, spline_p)) | 454 print('Error: Spline {0}, segment {1} has identical bounds and therefore is not a vector. Projection cannot continue.'.format(splineIdx, spline_p)) |
| 422 return None | 455 return None |
| 423 # check if the | 456 # check if the projected point is in between the current segment of the alignment bounds |
| 424 if utils.inBetween(splines[spline][spline_p][0], splines[spline][spline_p+1][0], closestPoint.x) and utils.inBetween(splines[spline][spline_p][1], splines[spline][spline_p+1][1], closestPoint.y): | 457 if utils.inBetween(splines[splineIdx][spline_p][0], splines[splineIdx][spline_p+1][0], closestPoint.x) and utils.inBetween(splines[splineIdx][spline_p][1], splines[splineIdx][spline_p+1][1], closestPoint.y): |
| 425 offsetY = Point.distanceNorm2(closestPoint, p) | 458 offsetY = Point.distanceNorm2(closestPoint, p) |
| 426 if offsetY < minOffsetY: | 459 if offsetY < minOffsetY: |
| 427 minOffsetY = offsetY | 460 minOffsetY = offsetY |
| 428 snappedSpline = spline | 461 snappedSplineIdx = splineIdx |
| 429 snappedSplineLeadingPoint = spline_p | 462 snappedSplineLeadingPoint = spline_p |
| 430 snappedPoint = Point(closestPoint.x, closestPoint.y) | 463 snappedPoint = Point(closestPoint.x, closestPoint.y) |
| 431 #Jump loop if significantly close | 464 #Jump loop if significantly close |
| 432 if offsetY < goodEnoughSplineDistance: | 465 if offsetY < goodEnoughSplineDistance: |
| 433 break | 466 break |
| 467 | |
| 434 #Get sub-segment distance | 468 #Get sub-segment distance |
| 435 if minOffsetY != float('inf'): | 469 if minOffsetY != float('inf'): |
| 436 subsegmentDistance = Point.distanceNorm2(snappedPoint, splines[snappedSpline][snappedSplineLeadingPoint]) | 470 subsegmentDistance = Point.distanceNorm2(snappedPoint, splines[snappedSplineIdx][snappedSplineLeadingPoint]) |
| 437 #Get cumulative alignment distance (total segment distance) | 471 #Get cumulative alignment distance (total segment distance) |
| 438 splineDistanceS = splines[snappedSpline].getCumulativeDistance(snappedSplineLeadingPoint) + subsegmentDistance | 472 splineDistanceS = splines[snappedSplineIdx].getCumulativeDistance(snappedSplineLeadingPoint) + subsegmentDistance |
| 439 orthogonalSplineVector = (splines[snappedSpline][snappedSplineLeadingPoint+1]-splines[snappedSpline][snappedSplineLeadingPoint]).orthogonal() | 473 orthogonalSplineVector = (splines[snappedSplineIdx][snappedSplineLeadingPoint+1]-splines[snappedSplineIdx][snappedSplineLeadingPoint]).orthogonal() |
| 440 offsetVector = p-snappedPoint | 474 offsetVector = p-snappedPoint |
| 441 if Point.dot(orthogonalSplineVector, offsetVector) < 0: | 475 if Point.dot(orthogonalSplineVector, offsetVector) < 0: |
| 442 minOffsetY = -minOffsetY | 476 minOffsetY = -minOffsetY |
| 443 return [snappedSpline, snappedSplineLeadingPoint, snappedPoint, subsegmentDistance, splineDistanceS, minOffsetY] | 477 return [snappedSplineIdx, snappedSplineLeadingPoint, snappedPoint, subsegmentDistance, splineDistanceS, minOffsetY] |
| 444 else: | 478 else: |
| 479 print('Offset for point {} is infinite (check with prepareSplines if some spline segments are aligned with axes)'.format(p)) | |
| 445 return None | 480 return None |
| 446 | 481 |
| 447 def getXYfromSY(s, y, splineNum, splines, mode = 0): | 482 def getXYfromSY(s, y, splineNum, splines, mode = 0): |
| 448 ''' Find X,Y coordinate from S,Y data. | 483 ''' Find X,Y coordinate from S,Y data. |
| 449 if mode = 0 : return Snapped X,Y | 484 if mode = 0 : return Snapped X,Y |
| 654 return ' '.join([self.__getitem__(i).__str__() for i in xrange(self.length())]) | 689 return ' '.join([self.__getitem__(i).__str__() for i in xrange(self.length())]) |
| 655 | 690 |
| 656 def __repr__(self): | 691 def __repr__(self): |
| 657 return self.__str__() | 692 return self.__str__() |
| 658 | 693 |
| 659 | |
| 660 def __iter__(self): | 694 def __iter__(self): |
| 661 self.iterInstantNum = 0 | 695 self.iterInstantNum = 0 |
| 662 return self | 696 return self |
| 663 | 697 |
| 664 def next(self): | 698 def next(self): |
| 665 if self.iterInstantNum >= self.length(): | 699 if self.iterInstantNum >= self.length(): |
| 666 raise StopIteration | 700 raise StopIteration |
| 667 else: | 701 else: |
| 668 self.iterInstantNum += 1 | 702 self.iterInstantNum += 1 |
| 669 return self[self.iterInstantNum-1] | 703 return self[self.iterInstantNum-1] |
| 704 | |
| 705 def __eq__(self, other): | |
| 706 if self.length() == other.length(): | |
| 707 result = True | |
| 708 for p, po in zip(self, other): | |
| 709 result = result and (p == po) | |
| 710 return result | |
| 711 else: | |
| 712 return False | |
| 670 | 713 |
| 671 def setPositionXY(self, i, x, y): | 714 def setPositionXY(self, i, x, y): |
| 672 if i < self.__len__(): | 715 if i < self.__len__(): |
| 673 self.positions[0][i] = x | 716 self.positions[0][i] = x |
| 674 self.positions[1][i] = y | 717 self.positions[1][i] = y |
| 755 for i in xrange(1, self.length()): | 798 for i in xrange(1, self.length()): |
| 756 diff.addPosition(self[i]-self[i-1]) | 799 diff.addPosition(self[i]-self[i-1]) |
| 757 if doubleLastPosition: | 800 if doubleLastPosition: |
| 758 diff.addPosition(diff[-1]) | 801 diff.addPosition(diff[-1]) |
| 759 return diff | 802 return diff |
| 803 | |
| 804 def differentiateSG(self, window_length, polyorder, deriv=0, delta=1.0, axis=-1, mode='interp', cval=0.0, removeBothEnds = 2): | |
| 805 '''Differentiates the trajectory using the Savitsky Golay filter | |
| 806 | |
| 807 window_length : The length of the filter window (i.e. the number of coefficients). window_length must be a positive odd integer. | |
| 808 polyorder : The order of the polynomial used to fit the samples. polyorder must be less than window_length. | |
| 809 deriv : The order of the derivative to compute. This must be a nonnegative integer. The default is 0, which means to filter the data without differentiating. | |
| 810 delta : The spacing of the samples to which the filter will be applied. This is only used if deriv > 0. Default is 1.0. | |
| 811 axis : The axis of the array x along which the filter is to be applied. Default is -1. | |
| 812 mode : Must be mirror, constant, nearest, wrap or interp. This determines the type of extension to use for the padded signal to which the filter is applied. When mode is constant, the padding value is given by cval. See the Notes for more details on mirror, constant, wrap, and nearest. When the interp mode is selected (the default), no extension is used. Instead, a degree polyorder polynomial is fit to the last window_length values of the edges, and this polynomial is used to evaluate the last window_length // 2 output values. | |
| 813 cval : Value to fill past the edges of the input if mode is constant. Default is 0.0.''' | |
| 814 from scipy.signal import savgol_filter | |
| 815 | |
| 816 if removeBothEnds >=1: | |
| 817 pos = [self.positions[0][removeBothEnds:-removeBothEnds], | |
| 818 self.positions[1][removeBothEnds:-removeBothEnds]] | |
| 819 else: | |
| 820 pos = self.positions | |
| 821 filtered = savgol_filter(pos, window_length, polyorder, deriv, delta, axis, mode, cval) | |
| 822 return Trajectory(filtered) | |
| 760 | 823 |
| 761 def norm(self): | 824 def norm(self): |
| 762 '''Returns the list of the norms at each instant''' | 825 '''Returns the list of the norms at each instant''' |
| 763 # def add(x, y): return x+y | 826 # def add(x, y): return x+y |
| 764 # sq = map(add, [x*x for x in self.positions[0]], [y*y for y in self.positions[1]]) | 827 # sq = map(add, [x*x for x in self.positions[0]], [y*y for y in self.positions[1]]) |
| 798 if i < self.length(): | 861 if i < self.length(): |
| 799 return self.cumulativeDistances[i] | 862 return self.cumulativeDistances[i] |
| 800 else: | 863 else: |
| 801 print('Index {} beyond trajectory length {}'.format(i, self.length())) | 864 print('Index {} beyond trajectory length {}'.format(i, self.length())) |
| 802 | 865 |
| 866 def getMaxDistance(self, metric): | |
| 867 'Returns the maximum distance between points in the trajectory' | |
| 868 positions = self.getPositions().asArray().T | |
| 869 return cdist(positions, positions, metric = metric).max() | |
| 870 | |
| 803 def similarOrientation(self, refDirection, cosineThreshold, minProportion = 0.5): | 871 def similarOrientation(self, refDirection, cosineThreshold, minProportion = 0.5): |
| 804 '''Indicates whether the minProportion (<=1.) (eg half) of the trajectory elements (vectors for velocity) | 872 '''Indicates whether the minProportion (<=1.) (eg half) of the trajectory elements (vectors for velocity) |
| 805 have a cosine with refDirection is smaller than cosineThreshold''' | 873 have a cosine with refDirection is smaller than cosineThreshold''' |
| 806 count = 0 | 874 count = 0 |
| 807 lengthThreshold = float(self.length())*minProportion | 875 lengthThreshold = float(self.length())*minProportion |
| 809 if p.similarOrientation(refDirection, cosineThreshold): | 877 if p.similarOrientation(refDirection, cosineThreshold): |
| 810 count += 1 | 878 count += 1 |
| 811 return count >= lengthThreshold | 879 return count >= lengthThreshold |
| 812 | 880 |
| 813 def wiggliness(self): | 881 def wiggliness(self): |
| 814 return self.getCumulativeDistance(self.length()-1)/float(Point.distanceNorm2(self.__getitem__(0),self.__getitem__(self.length()-1))) | 882 straightDistance = Point.distanceNorm2(self.__getitem__(0),self.__getitem__(self.length()-1)) |
| 883 if straightDistance > 0: | |
| 884 return self.getCumulativeDistance(self.length()-1)/float(straightDistance) | |
| 885 else: | |
| 886 return None | |
| 815 | 887 |
| 816 def getIntersections(self, p1, p2): | 888 def getIntersections(self, p1, p2): |
| 817 '''Returns a list of the indices at which the trajectory | 889 '''Returns a list of the indices at which the trajectory |
| 818 intersects with the segment of extremities p1 and p2 | 890 intersects with the segment of extremities p1 and p2 |
| 819 the list is empty if there is no crossing''' | 891 Returns an empty list if there is no crossing''' |
| 820 indices = [] | 892 indices = [] |
| 821 intersections = [] | 893 intersections = [] |
| 822 | 894 |
| 823 for i in xrange(self.length()-1): | 895 for i in xrange(self.length()-1): |
| 824 q1=self.__getitem__(i) | 896 q1=self.__getitem__(i) |
| 825 q2=self.__getitem__(i+1) | 897 q2=self.__getitem__(i+1) |
| 826 p = utils.segmentIntersection(q1, q2, p1, p2) | 898 p = segmentIntersection(q1, q2, p1, p2) |
| 827 if p is not None: | |
| 828 if q1.x != q2.x: | |
| 829 ratio = (p.x-q1.x)/(q2.x-q1.x) | |
| 830 elif q1.y != q2.y: | |
| 831 ratio = (p.y-q1.y)/(q2.y-q1.y) | |
| 832 else: | |
| 833 ratio = 0 | |
| 834 indices.append(i+ratio) | |
| 835 intersections.append(p) | |
| 836 return indices | |
| 837 | |
| 838 def getLineIntersections(self, p1, p2): | |
| 839 '''Returns a list of the indices at which the trajectory | |
| 840 intersects with the segment of extremities p1 and p2 | |
| 841 the list is empty if there is no crossing''' | |
| 842 indices = [] | |
| 843 intersections = [] | |
| 844 | |
| 845 for i in xrange(self.length()-1): | |
| 846 q1=self.__getitem__(i) | |
| 847 q2=self.__getitem__(i+1) | |
| 848 p = utils.segmentLineIntersection(p1, p2, q1, q2) | |
| 849 if p is not None: | 899 if p is not None: |
| 850 if q1.x != q2.x: | 900 if q1.x != q2.x: |
| 851 ratio = (p.x-q1.x)/(q2.x-q1.x) | 901 ratio = (p.x-q1.x)/(q2.x-q1.x) |
| 852 elif q1.y != q2.y: | 902 elif q1.y != q2.y: |
| 853 ratio = (p.y-q1.y)/(q2.y-q1.y) | 903 ratio = (p.y-q1.y)/(q2.y-q1.y) |
| 855 ratio = 0 | 905 ratio = 0 |
| 856 indices.append(i+ratio) | 906 indices.append(i+ratio) |
| 857 intersections.append(p) | 907 intersections.append(p) |
| 858 return indices, intersections | 908 return indices, intersections |
| 859 | 909 |
| 910 def getLineIntersections(self, p1, p2): | |
| 911 '''Returns a list of the indices at which the trajectory | |
| 912 intersects with the line going through p1 and p2 | |
| 913 Returns an empty list if there is no crossing''' | |
| 914 indices = [] | |
| 915 intersections = [] | |
| 916 | |
| 917 for i in xrange(self.length()-1): | |
| 918 q1=self.__getitem__(i) | |
| 919 q2=self.__getitem__(i+1) | |
| 920 p = segmentLineIntersection(p1, p2, q1, q2) | |
| 921 if p is not None: | |
| 922 if q1.x != q2.x: | |
| 923 ratio = (p.x-q1.x)/(q2.x-q1.x) | |
| 924 elif q1.y != q2.y: | |
| 925 ratio = (p.y-q1.y)/(q2.y-q1.y) | |
| 926 else: | |
| 927 ratio = 0 | |
| 928 indices.append(i+ratio) | |
| 929 intersections.append(p) | |
| 930 return indices, intersections | |
| 931 | |
| 860 def getTrajectoryInInterval(self, inter): | 932 def getTrajectoryInInterval(self, inter): |
| 861 'Returns all position between index inter.first and index.last (included)' | 933 'Returns all position between index inter.first and index.last (included)' |
| 862 if inter.first >=0 and inter.last<= self.length(): | 934 if inter.first >=0 and inter.last<= self.length(): |
| 863 return Trajectory([self.positions[0][inter.first:inter.last+1], | 935 return Trajectory([self.positions[0][inter.first:inter.last+1], |
| 864 self.positions[1][inter.first:inter.last+1]]) | 936 self.positions[1][inter.first:inter.last+1]]) |
| 865 else: | 937 else: |
| 866 return None | 938 return None |
| 867 | 939 |
| 940 def subSample(self, step): | |
| 941 'Returns the positions very step' | |
| 942 return Trajectory([self.positions[0][::step], | |
| 943 self.positions[1][::step]]) | |
| 944 | |
| 868 if shapelyAvailable: | 945 if shapelyAvailable: |
| 869 def getTrajectoryInPolygon(self, polygon): | 946 def getTrajectoryInPolygon(self, polygon, t2 = None): |
| 870 '''Returns the trajectory built with the set of points inside the (shapely) polygon''' | 947 '''Returns the trajectory built with the set of points inside the (shapely) polygon |
| 948 The polygon could be a prepared polygon (faster) from prepared.prep | |
| 949 | |
| 950 t2 is another trajectory (could be velocities) | |
| 951 which is filtered based on the first (self) trajectory''' | |
| 871 traj = Trajectory() | 952 traj = Trajectory() |
| 872 points = [p.asShapely() for p in self] | 953 inPolygon = [] |
| 873 for p in pointsInPolygon(points, polygon): | 954 for x, y in zip(self.positions[0], self.positions[1]): |
| 874 traj.addPositionXY(p.x, p.y) | 955 inPolygon.append(polygon.contains(shapelyPoint(x, y))) |
| 875 return traj | 956 if inPolygon[-1]: |
| 957 traj.addPositionXY(x, y) | |
| 958 traj2 = Trajectory() | |
| 959 if t2 is not None: | |
| 960 for inp, x, y in zip(inPolygon, t2.positions[0], t2.positions[1]): | |
| 961 if inp: | |
| 962 traj2.addPositionXY(x, y) | |
| 963 return traj, traj2 | |
| 876 | 964 |
| 877 def proportionInPolygon(self, polygon, minProportion = 0.5): | 965 def proportionInPolygon(self, polygon, minProportion = 0.5): |
| 878 pointsIn = pointsInPolygon([p.asShapely() for p in self], polygon) | 966 inPolygon = [polygon.contains(shapelyPoint(x, y)) for x, y in zip(self.positions[0], self.positions[1])] |
| 879 lengthThreshold = float(self.length())*minProportion | 967 lengthThreshold = float(self.length())*minProportion |
| 880 return len(pointsIn) >= lengthThreshold | 968 return sum(inPolygon) >= lengthThreshold |
| 881 else: | 969 else: |
| 882 def getTrajectoryInPolygon(self, polygon): | 970 def getTrajectoryInPolygon(self, polygon, t2 = None): |
| 883 '''Returns the trajectory built with the set of points inside the polygon | 971 '''Returns the trajectory built with the set of points inside the polygon |
| 884 (array of Nx2 coordinates of the polygon vertices)''' | 972 (array of Nx2 coordinates of the polygon vertices)''' |
| 885 traj = Trajectory() | 973 traj = Trajectory() |
| 974 inPolygon = [] | |
| 886 for p in self: | 975 for p in self: |
| 887 if p.inPolygon(polygon): | 976 inPolygon.append(p.inPolygon(polygon)) |
| 977 if inPolygon[-1]: | |
| 888 traj.addPosition(p) | 978 traj.addPosition(p) |
| 889 return traj | 979 traj2 = Trajectory() |
| 980 if t2 is not None: | |
| 981 for inp, x, y in zip(inPolygon, t2.positions[0], t2.positions[1]): | |
| 982 if inp: | |
| 983 traj2.addPositionXY(p.x, p.y) | |
| 984 return traj, traj2 | |
| 890 | 985 |
| 891 def proportionInPolygon(self, polygon, minProportion = 0.5): | 986 def proportionInPolygon(self, polygon, minProportion = 0.5): |
| 892 pointsInPolygon = [p.inPolygon(polygon) for p in self] | 987 inPolygon = [p.inPolygon(polygon) for p in self] |
| 893 lengthThreshold = float(self.length())*minProportion | 988 lengthThreshold = float(self.length())*minProportion |
| 894 return len(pointsInPolygon) >= lengthThreshold | 989 return sum(inPolygon) >= lengthThreshold |
| 895 | 990 |
| 896 @staticmethod | 991 @staticmethod |
| 897 def lcss(t1, t2, lcss): | 992 def lcss(t1, t2, lcss): |
| 898 return lcss.compute(t1, t2) | 993 return lcss.compute(t1, t2) |
| 899 | 994 |
| 953 | 1048 |
| 954 def getIntersections(self, S1, lane = None): | 1049 def getIntersections(self, S1, lane = None): |
| 955 '''Returns a list of the indices at which the trajectory | 1050 '''Returns a list of the indices at which the trajectory |
| 956 goes past the curvilinear coordinate S1 | 1051 goes past the curvilinear coordinate S1 |
| 957 (in provided lane if lane is not None) | 1052 (in provided lane if lane is not None) |
| 958 the list is empty if there is no crossing''' | 1053 Returns an empty list if there is no crossing''' |
| 959 indices = [] | 1054 indices = [] |
| 960 for i in xrange(self.length()-1): | 1055 for i in xrange(self.length()-1): |
| 961 q1=self.__getitem__(i) | 1056 q1=self.__getitem__(i) |
| 962 q2=self.__getitem__(i+1) | 1057 q2=self.__getitem__(i+1) |
| 963 if q1[0] <= S1 < q2[0] and (lane is None or (self.lanes[i] == lane and self.lanes[i+1] == lane)): | 1058 if q1[0] <= S1 < q2[0] and (lane is None or (self.lanes[i] == lane and self.lanes[i+1] == lane)): |
| 1524 | 1619 |
| 1525 ################## | 1620 ################## |
| 1526 # Annotations | 1621 # Annotations |
| 1527 ################## | 1622 ################## |
| 1528 | 1623 |
| 1529 class BBAnnotation(MovingObject): | 1624 class BBMovingObject(MovingObject): |
| 1530 '''Class for a series of ground truth annotations using bounding boxes | 1625 '''Class for a moving object represented as a bounding box |
| 1531 Its center is the center of the containing shape | 1626 used for series of ground truth annotations using bounding boxes |
| 1627 and for the output of Urban Tracker http://www.jpjodoin.com/urbantracker/ | |
| 1532 | 1628 |
| 1533 By default in image space | 1629 By default in image space |
| 1630 | |
| 1631 Its center is the center of the box (generalize to other shapes?) | |
| 1632 (computed after projecting if homography available) | |
| 1534 ''' | 1633 ''' |
| 1535 | 1634 |
| 1536 def __init__(self, num = None, timeInterval = None, topLeftPositions = None, bottomRightPositions = None, userType = userType2Num['unknown']): | 1635 def __init__(self, num = None, timeInterval = None, topLeftPositions = None, bottomRightPositions = None, userType = userType2Num['unknown']): |
| 1537 super(BBAnnotation, self).__init__(num, timeInterval, userType = userType) | 1636 super(BBMovingObject, self).__init__(num, timeInterval, userType = userType) |
| 1538 self.topLeftPositions = topLeftPositions.getPositions() | 1637 self.topLeftPositions = topLeftPositions.getPositions() |
| 1539 self.bottomRightPositions = bottomRightPositions.getPositions() | 1638 self.bottomRightPositions = bottomRightPositions.getPositions() |
| 1540 | 1639 |
| 1541 def computeCentroidTrajectory(self, homography = None): | 1640 def computeCentroidTrajectory(self, homography = None): |
| 1542 self.positions = self.topLeftPositions.add(self.bottomRightPositions).multiply(0.5) | 1641 self.positions = self.topLeftPositions.add(self.bottomRightPositions).multiply(0.5) |
| 1559 | 1658 |
| 1560 Reference: | 1659 Reference: |
| 1561 Keni, Bernardin, and Stiefelhagen Rainer. "Evaluating multiple object tracking performance: the CLEAR MOT metrics." EURASIP Journal on Image and Video Processing 2008 (2008) | 1660 Keni, Bernardin, and Stiefelhagen Rainer. "Evaluating multiple object tracking performance: the CLEAR MOT metrics." EURASIP Journal on Image and Video Processing 2008 (2008) |
| 1562 | 1661 |
| 1563 objects and annotations are supposed to in the same space | 1662 objects and annotations are supposed to in the same space |
| 1564 current implementation is BBAnnotations (bounding boxes) | 1663 current implementation is BBMovingObject (bounding boxes) |
| 1565 mathingDistance is threshold on matching between annotation and object | 1664 mathingDistance is threshold on matching between annotation and object |
| 1566 | 1665 |
| 1567 TO: tracker output (objects) | 1666 TO: tracker output (objects) |
| 1568 GT: ground truth (annotations) | 1667 GT: ground truth (annotations) |
| 1569 | 1668 |
