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