Mercurial > hg > nsaunier > traffic-intelligence
comparison python/moving.py @ 614:5e09583275a4
Merged Nicolas/trafficintelligence into default
| author | Mohamed Gomaa <eng.m.gom3a@gmail.com> |
|---|---|
| date | Fri, 05 Dec 2014 12:13:53 -0500 |
| parents | c5406edbcf12 |
| children | 5800a87f11ae 0954aaf28231 |
comparison
equal
deleted
inserted
replaced
| 598:11f96bd08552 | 614:5e09583275a4 |
|---|---|
| 3 | 3 |
| 4 import utils | 4 import utils |
| 5 import cvutils | 5 import cvutils |
| 6 | 6 |
| 7 from math import sqrt | 7 from math import sqrt |
| 8 | 8 from numpy import median |
| 9 #from shapely.geometry import Polygon | 9 |
| 10 try: | |
| 11 from shapely.geometry import Polygon, Point as shapelyPoint | |
| 12 from shapely.prepared import prep | |
| 13 shapelyAvailable = True | |
| 14 except ImportError: | |
| 15 print('Shapely library could not be loaded') | |
| 16 shapelyAvailable = False | |
| 10 | 17 |
| 11 __metaclass__ = type | 18 __metaclass__ = type |
| 12 | 19 |
| 13 class Interval(object): | 20 class Interval(object): |
| 14 '''Generic interval: a subset of real numbers (not iterable)''' | 21 '''Generic interval: a subset of real numbers (not iterable)''' |
| 26 def __repr__(self): | 33 def __repr__(self): |
| 27 return self.__str__() | 34 return self.__str__() |
| 28 | 35 |
| 29 def empty(self): | 36 def empty(self): |
| 30 return self.first > self.last | 37 return self.first > self.last |
| 38 | |
| 39 def center(self): | |
| 40 return (self.first+self.last)/2. | |
| 31 | 41 |
| 32 def length(self): | 42 def length(self): |
| 33 '''Returns the length of the interval''' | 43 '''Returns the length of the interval''' |
| 34 return float(max(0,self.last-self.first)) | 44 return float(max(0,self.last-self.first)) |
| 35 | 45 |
| 88 def fromInterval(inter): | 98 def fromInterval(inter): |
| 89 return TimeInterval(inter.first, inter.last) | 99 return TimeInterval(inter.first, inter.last) |
| 90 | 100 |
| 91 def __getitem__(self, i): | 101 def __getitem__(self, i): |
| 92 if not self.empty(): | 102 if not self.empty(): |
| 93 return self.first+i | 103 if isinstance(i, int): |
| 104 return self.first+i | |
| 105 else: | |
| 106 raise TypeError, "Invalid argument type." | |
| 107 #elif isinstance( key, slice ): | |
| 94 | 108 |
| 95 def __iter__(self): | 109 def __iter__(self): |
| 96 self.iterInstantNum = -1 | 110 self.iterInstantNum = -1 |
| 97 return self | 111 return self |
| 98 | 112 |
| 126 self.boundingPolygon = boundingPolygon | 140 self.boundingPolygon = boundingPolygon |
| 127 | 141 |
| 128 def empty(self): | 142 def empty(self): |
| 129 return self.timeInterval.empty() or not self.boudingPolygon | 143 return self.timeInterval.empty() or not self.boudingPolygon |
| 130 | 144 |
| 131 def getId(self): | 145 def getNum(self): |
| 132 return self.num | 146 return self.num |
| 133 | 147 |
| 134 def getFirstInstant(self): | 148 def getFirstInstant(self): |
| 135 return self.timeInterval.first | 149 return self.timeInterval.first |
| 136 | 150 |
| 161 return Point(self.x+other.x, self.y+other.y) | 175 return Point(self.x+other.x, self.y+other.y) |
| 162 | 176 |
| 163 def __sub__(self, other): | 177 def __sub__(self, other): |
| 164 return Point(self.x-other.x, self.y-other.y) | 178 return Point(self.x-other.x, self.y-other.y) |
| 165 | 179 |
| 180 def __neg__(self): | |
| 181 return Point(-self.x, -self.y) | |
| 182 | |
| 183 def __getitem__(self, i): | |
| 184 if i == 0: | |
| 185 return self.x | |
| 186 elif i == 1: | |
| 187 return self.y | |
| 188 else: | |
| 189 raise IndexError() | |
| 190 | |
| 191 def orthogonal(self): | |
| 192 return Point(self.y, -self.x) | |
| 193 | |
| 166 def multiply(self, alpha): | 194 def multiply(self, alpha): |
| 195 'Warning, returns a new Point' | |
| 167 return Point(self.x*alpha, self.y*alpha) | 196 return Point(self.x*alpha, self.y*alpha) |
| 168 | 197 |
| 169 def draw(self, options = 'o', **kwargs): | 198 def plot(self, options = 'o', **kwargs): |
| 170 from matplotlib.pylab import plot | 199 from matplotlib.pylab import plot |
| 171 plot([self.x], [self.y], options, **kwargs) | 200 plot([self.x], [self.y], options, **kwargs) |
| 172 | 201 |
| 173 def norm2Squared(self): | 202 def norm2Squared(self): |
| 174 '''2-norm distance (Euclidean distance)''' | 203 '''2-norm distance (Euclidean distance)''' |
| 175 return self.x*self.x+self.y*self.y | 204 return self.x**2+self.y**2 |
| 176 | 205 |
| 177 def norm2(self): | 206 def norm2(self): |
| 178 '''2-norm distance (Euclidean distance)''' | 207 '''2-norm distance (Euclidean distance)''' |
| 179 return sqrt(self.norm2Squared()) | 208 return sqrt(self.norm2Squared()) |
| 180 | 209 |
| 190 def astuple(self): | 219 def astuple(self): |
| 191 return (self.x, self.y) | 220 return (self.x, self.y) |
| 192 | 221 |
| 193 def asint(self): | 222 def asint(self): |
| 194 return Point(int(self.x), int(self.y)) | 223 return Point(int(self.x), int(self.y)) |
| 224 | |
| 225 if shapelyAvailable: | |
| 226 def asShapely(self): | |
| 227 return shapelyPoint(self.x, self.y) | |
| 195 | 228 |
| 196 def project(self, homography): | 229 def project(self, homography): |
| 197 from numpy.core.multiarray import array | 230 from numpy.core.multiarray import array |
| 198 projected = cvutils.projectArray(homography, array([[self.x], [self.y]])) | 231 projected = cvutils.projectArray(homography, array([[self.x], [self.y]])) |
| 199 return Point(projected[0], projected[1]) | 232 return Point(projected[0], projected[1]) |
| 200 | 233 |
| 201 def inPolygon(self, poly): | 234 def inPolygonNoShapely(self, polygon): |
| 202 '''Returns if the point x, y is inside the polygon. | 235 '''Indicates if the point x, y is inside the polygon |
| 203 The polygon is defined by the ordered list of points in poly | 236 (array of Nx2 coordinates of the polygon vertices) |
| 204 | 237 |
| 205 taken from http://www.ariel.com.au/a/python-point-int-poly.html | 238 taken from http://www.ariel.com.au/a/python-point-int-poly.html |
| 206 | 239 |
| 207 Use points_inside_poly from matplotlib.nxutils''' | 240 Use Polygon.contains if Shapely is installed''' |
| 208 | 241 |
| 209 n = len(poly); | 242 n = polygon.shape[0]; |
| 210 counter = 0; | 243 counter = 0; |
| 211 | 244 |
| 212 p1 = poly[0]; | 245 p1 = polygon[0,:]; |
| 213 for i in range(n+1): | 246 for i in range(n+1): |
| 214 p2 = poly[i % n]; | 247 p2 = polygon[i % n,:]; |
| 215 if self.y > min(p1.y,p2.y): | 248 if self.y > min(p1[1],p2[1]): |
| 216 if self.y <= max(p1.y,p2.y): | 249 if self.y <= max(p1[1],p2[1]): |
| 217 if self.x <= max(p1.x,p2.x): | 250 if self.x <= max(p1[0],p2[0]): |
| 218 if p1.y != p2.y: | 251 if p1[1] != p2[1]: |
| 219 xinters = (self.y-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x; | 252 xinters = (self.y-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]; |
| 220 if p1.x == p2.x or self.x <= xinters: | 253 if p1[0] == p2[0] or self.x <= xinters: |
| 221 counter+=1; | 254 counter+=1; |
| 222 p1=p2 | 255 p1=p2 |
| 223 return (counter%2 == 1); | 256 return (counter%2 == 1); |
| 224 | 257 |
| 225 @staticmethod | 258 @staticmethod |
| 259 def fromList(p): | |
| 260 return Point(p[0], p[1]) | |
| 261 | |
| 262 @staticmethod | |
| 226 def dot(p1, p2): | 263 def dot(p1, p2): |
| 227 'Scalar product' | 264 'Scalar product' |
| 228 return p1.x*p2.x+p1.y*p2.y | 265 return p1.x*p2.x+p1.y*p2.y |
| 229 | 266 |
| 230 @staticmethod | 267 @staticmethod |
| 231 def cross(p1, p2): | 268 def cross(p1, p2): |
| 232 'Cross product' | 269 'Cross product' |
| 233 return p1.x*p2.y-p1.y*p2.x | 270 return p1.x*p2.y-p1.y*p2.x |
| 234 | 271 |
| 235 @staticmethod | 272 @staticmethod |
| 273 def cosine(p1, p2): | |
| 274 return Point.dot(p1,p2)/(p1.norm2()*p2.norm2()) | |
| 275 | |
| 276 @staticmethod | |
| 236 def distanceNorm2(p1, p2): | 277 def distanceNorm2(p1, p2): |
| 237 return (p1-p2).norm2() | 278 return (p1-p2).norm2() |
| 238 | 279 |
| 239 @staticmethod | 280 @staticmethod |
| 240 def plotAll(points, color='r'): | 281 def plotAll(points, **kwargs): |
| 241 from matplotlib.pyplot import scatter | 282 from matplotlib.pyplot import scatter |
| 242 scatter([p.x for p in points],[p.y for p in points], c=color) | 283 scatter([p.x for p in points],[p.y for p in points], **kwargs) |
| 284 | |
| 285 def similarOrientation(self, refDirection, cosineThreshold): | |
| 286 'Indicates whether the cosine of the vector and refDirection is smaller than cosineThreshold' | |
| 287 return Point.cosine(self, refDirection) >= cosineThreshold | |
| 288 | |
| 289 @staticmethod | |
| 290 def timeToCollision(p1, p2, v1, v2, collisionThreshold): | |
| 291 '''Computes exact time to collision with a distance threshold | |
| 292 The unknown of the equation is the time to reach the intersection | |
| 293 between the relative trajectory of one road user | |
| 294 and the circle of radius collisionThreshold around the other road user''' | |
| 295 from math import sqrt | |
| 296 dv = v1-v2 | |
| 297 dp = p1-p2 | |
| 298 a = dv.norm2Squared()#(v1.x-v2.x)**2 + (v1.y-v2.y)**2 | |
| 299 b = 2*Point.dot(dv, dp)#2 * ((p1.x-p2.x) * (v1.x-v2.x) + (p1.y-p2.y) * (v1.y-v2.y)) | |
| 300 c = dp.norm2Squared() - collisionThreshold**2#(p1.x-p2.x)**2 + (p1.y-p2.y)**2 - collisionThreshold**2 | |
| 301 | |
| 302 delta = b**2 - 4*a*c | |
| 303 if delta >= 0: | |
| 304 deltaRoot = sqrt(delta) | |
| 305 ttc1 = (-b + deltaRoot)/(2*a) | |
| 306 ttc2 = (-b - deltaRoot)/(2*a) | |
| 307 if ttc1 >= 0 and ttc2 >= 0: | |
| 308 ttc = min(ttc1,ttc2) | |
| 309 elif ttc1 >= 0: | |
| 310 ttc = ttc1 | |
| 311 elif ttc2 >= 0: | |
| 312 ttc = ttc2 | |
| 313 else: # ttc1 < 0 and ttc2 < 0: | |
| 314 ttc = None | |
| 315 else: | |
| 316 ttc = None | |
| 317 return ttc | |
| 318 | |
| 319 @staticmethod | |
| 320 def midPoint(p1, p2): | |
| 321 'Returns the middle of the segment [p1, p2]' | |
| 322 return Point(0.5*p1.x+0.5*p2.x, 0.5*p1.y+0.5*p2.y) | |
| 323 | |
| 324 if shapelyAvailable: | |
| 325 def pointsInPolygon(points, polygon): | |
| 326 '''Optimized tests of a series of points within (Shapely) polygon ''' | |
| 327 prepared_polygon = prep(polygon) | |
| 328 return filter(prepared_polygon.contains, points) | |
| 329 | |
| 330 # Functions for coordinate transformation | |
| 331 # From Paul St-Aubin's PVA tools | |
| 332 def subsec_spline_dist(splines): | |
| 333 ''' Prepare list of spline subsegments from a spline list. | |
| 334 | |
| 335 Output: | |
| 336 ======= | |
| 337 ss_spline_d[spline #][mode][station] | |
| 338 | |
| 339 where: | |
| 340 mode=0: incremental distance | |
| 341 mode=1: cumulative distance | |
| 342 mode=2: cumulative distance with trailing distance | |
| 343 ''' | |
| 344 | |
| 345 from numpy import zeros | |
| 346 ss_spline_d = [] | |
| 347 #Prepare subsegment distances | |
| 348 for spline in range(len(splines)): | |
| 349 ss_spline_d.append([[],[],[]]) | |
| 350 ss_spline_d[spline][0] = zeros(len(splines[spline])-1) #Incremental distance | |
| 351 ss_spline_d[spline][1] = zeros(len(splines[spline])-1) #Cumulative distance | |
| 352 ss_spline_d[spline][2] = zeros(len(splines[spline])) #Cumulative distance with trailing distance | |
| 353 for spline_p in range(len(splines[spline])): | |
| 354 if spline_p > (len(splines[spline]) - 2): | |
| 355 break | |
| 356 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]) | |
| 357 ss_spline_d[spline][1][spline_p] = sum(ss_spline_d[spline][0][0:spline_p]) | |
| 358 ss_spline_d[spline][2][spline_p] = ss_spline_d[spline][1][spline_p]#sum(ss_spline_d[spline][0][0:spline_p]) | |
| 359 | |
| 360 ss_spline_d[spline][2][-1] = ss_spline_d[spline][2][-2] + ss_spline_d[spline][0][-1] | |
| 361 | |
| 362 return ss_spline_d | |
| 363 | |
| 364 def ppldb2p(qx,qy, p0x,p0y, p1x,p1y): | |
| 365 ''' Point-projection (Q) on line defined by 2 points (P0,P1). | |
| 366 http://cs.nyu.edu/~yap/classes/visual/03s/hw/h2/math.pdf | |
| 367 ''' | |
| 368 if(p0x == p1x and p0y == p1y): | |
| 369 return None | |
| 370 try: | |
| 371 #Approximate slope singularity by giving some slope roundoff; account for roundoff error | |
| 372 if(round(p0x, 10) == round(p1x, 10)): | |
| 373 p1x += 0.0000000001 | |
| 374 if(round(p0y, 10) == round(p1y, 10)): | |
| 375 p1y += 0.0000000001 | |
| 376 #make the calculation | |
| 377 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)) | |
| 378 X = (-Y*(p1y-p0y)+qx*(p1x-p0x)+qy*(p1y-p0y))/(p1x-p0x) | |
| 379 except ZeroDivisionError: | |
| 380 print('Error: Division by zero in ppldb2p. Please report this error with the full traceback:') | |
| 381 print('qx={0}, qy={1}, p0x={2}, p0y={3}, p1x={4}, p1y={5}...'.format(qx, qy, p0x, p0y, p1x, p1y)) | |
| 382 import pdb; pdb.set_trace() | |
| 383 return Point(X,Y) | |
| 384 | |
| 385 def getSYfromXY(p, splines, goodEnoughSplineDistance = 0.5): | |
| 386 ''' 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. | |
| 387 | |
| 388 Output: | |
| 389 ======= | |
| 390 [spline index, | |
| 391 subsegment leading point index, | |
| 392 snapped point, | |
| 393 subsegment distance, | |
| 394 spline distance, | |
| 395 orthogonal point offset] | |
| 396 ''' | |
| 397 minOffsetY = float('inf') | |
| 398 #For each spline | |
| 399 for spline in range(len(splines)): | |
| 400 #For each spline point index | |
| 401 for spline_p in range(len(splines[spline])-1): | |
| 402 #Get closest point on spline | |
| 403 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]) | |
| 404 if closestPoint == None: | |
| 405 print('Error: Spline {0}, segment {1} has identical bounds and therefore is not a vector. Projection cannot continue.'.format(spline, spline_p)) | |
| 406 return None | |
| 407 # check if the | |
| 408 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): | |
| 409 offsetY = Point.distanceNorm2(closestPoint, p) | |
| 410 if offsetY < minOffsetY: | |
| 411 minOffsetY = offsetY | |
| 412 snappedSpline = spline | |
| 413 snappedSplineLeadingPoint = spline_p | |
| 414 snappedPoint = Point(closestPoint.x, closestPoint.y) | |
| 415 #Jump loop if significantly close | |
| 416 if offsetY < goodEnoughSplineDistance: | |
| 417 break | |
| 418 #Get sub-segment distance | |
| 419 if minOffsetY != float('inf'): | |
| 420 subsegmentDistance = Point.distanceNorm2(snappedPoint, splines[snappedSpline][snappedSplineLeadingPoint]) | |
| 421 #Get cumulative alignment distance (total segment distance) | |
| 422 splineDistanceS = splines[snappedSpline].getCumulativeDistance(snappedSplineLeadingPoint) + subsegmentDistance | |
| 423 orthogonalSplineVector = (splines[snappedSpline][snappedSplineLeadingPoint+1]-splines[snappedSpline][snappedSplineLeadingPoint]).orthogonal() | |
| 424 offsetVector = p-snappedPoint | |
| 425 if Point.dot(orthogonalSplineVector, offsetVector) < 0: | |
| 426 minOffsetY = -minOffsetY | |
| 427 return [snappedSpline, snappedSplineLeadingPoint, snappedPoint, subsegmentDistance, splineDistanceS, minOffsetY] | |
| 428 else: | |
| 429 return None | |
| 430 | |
| 431 def getXYfromSY(s, y, splineNum, splines, mode = 0): | |
| 432 ''' Find X,Y coordinate from S,Y data. | |
| 433 if mode = 0 : return Snapped X,Y | |
| 434 if mode !=0 : return Real X,Y | |
| 435 ''' | |
| 436 | |
| 437 #(buckle in, it gets ugly from here on out) | |
| 438 ss_spline_d = subsec_spline_dist(splines) | |
| 439 | |
| 440 #Find subsegment | |
| 441 snapped_x = None | |
| 442 snapped_y = None | |
| 443 for spline_ss_index in range(len(ss_spline_d[splineNum][1])): | |
| 444 if(s < ss_spline_d[splineNum][1][spline_ss_index]): | |
| 445 ss_value = s - ss_spline_d[splineNum][1][spline_ss_index-1] | |
| 446 #Get normal vector and then snap | |
| 447 vector_l_x = (splines[splineNum][spline_ss_index][0] - splines[splineNum][spline_ss_index-1][0]) | |
| 448 vector_l_y = (splines[splineNum][spline_ss_index][1] - splines[splineNum][spline_ss_index-1][1]) | |
| 449 magnitude = sqrt(vector_l_x**2 + vector_l_y**2) | |
| 450 n_vector_x = vector_l_x/magnitude | |
| 451 n_vector_y = vector_l_y/magnitude | |
| 452 snapped_x = splines[splineNum][spline_ss_index-1][0] + ss_value*n_vector_x | |
| 453 snapped_y = splines[splineNum][spline_ss_index-1][1] + ss_value*n_vector_y | |
| 454 | |
| 455 #Real values (including orthogonal projection of y)) | |
| 456 real_x = snapped_x - y*n_vector_y | |
| 457 real_y = snapped_y + y*n_vector_x | |
| 458 break | |
| 459 | |
| 460 if mode == 0 or (not snapped_x): | |
| 461 if(not snapped_x): | |
| 462 snapped_x = splines[splineNum][-1][0] | |
| 463 snapped_y = splines[splineNum][-1][1] | |
| 464 return [snapped_x,snapped_y] | |
| 465 else: | |
| 466 return [real_x,real_y] | |
| 467 | |
| 243 | 468 |
| 244 class NormAngle(object): | 469 class NormAngle(object): |
| 245 '''Alternate encoding of a point, by its norm and orientation''' | 470 '''Alternate encoding of a point, by its norm and orientation''' |
| 246 | 471 |
| 247 def __init__(self, norm, angle): | 472 def __init__(self, norm, angle): |
| 252 def fromPoint(p): | 477 def fromPoint(p): |
| 253 from math import atan2 | 478 from math import atan2 |
| 254 norm = p.norm2() | 479 norm = p.norm2() |
| 255 if norm > 0: | 480 if norm > 0: |
| 256 angle = atan2(p.y, p.x) | 481 angle = atan2(p.y, p.x) |
| 482 else: | |
| 483 angle = 0. | |
| 257 return NormAngle(norm, angle) | 484 return NormAngle(norm, angle) |
| 258 | 485 |
| 259 def __add__(self, other): | 486 def __add__(self, other): |
| 260 'a norm cannot become negative' | 487 'a norm cannot become negative' |
| 261 return NormAngle(max(self.norm+other.norm, 0), self.angle+other.angle) | 488 return NormAngle(max(self.norm+other.norm, 0), self.angle+other.angle) |
| 292 return FlowVector(self.position+other.position, self.velocity+other.velocity) | 519 return FlowVector(self.position+other.position, self.velocity+other.velocity) |
| 293 | 520 |
| 294 def multiply(self, alpha): | 521 def multiply(self, alpha): |
| 295 return FlowVector(self.position.multiply(alpha), self.velocity.multiply(alpha)) | 522 return FlowVector(self.position.multiply(alpha), self.velocity.multiply(alpha)) |
| 296 | 523 |
| 297 def draw(self, options = '', **kwargs): | 524 def plot(self, options = '', **kwargs): |
| 298 from matplotlib.pylab import plot | 525 from matplotlib.pylab import plot |
| 299 plot([self.position.x, self.position.x+self.velocity.x], [self.position.y, self.position.y+self.velocity.y], options, **kwargs) | 526 plot([self.position.x, self.position.x+self.velocity.x], [self.position.y, self.position.y+self.velocity.y], options, **kwargs) |
| 300 self.position.draw(options+'x', **kwargs) | 527 self.position.plot(options+'x', **kwargs) |
| 301 | 528 |
| 302 @staticmethod | 529 @staticmethod |
| 303 def similar(f1, f2, maxDistance2, maxDeltavelocity2): | 530 def similar(f1, f2, maxDistance2, maxDeltavelocity2): |
| 304 return (f1.position-f2.position).norm2Squared()<maxDistance2 and (f1.velocity-f2.velocity).norm2Squared()<maxDeltavelocity2 | 531 return (f1.position-f2.position).norm2Squared()<maxDistance2 and (f1.velocity-f2.velocity).norm2Squared()<maxDeltavelocity2 |
| 305 | 532 |
| 533 def intersection(p1, p2, p3, p4): | |
| 534 ''' Intersection point (x,y) of lines formed by the vectors p1-p2 and p3-p4 | |
| 535 http://paulbourke.net/geometry/pointlineplane/''' | |
| 536 dp12 = p2-p1 | |
| 537 dp34 = p4-p3 | |
| 538 #det = (p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y) | |
| 539 det = dp34.y*dp12.x-dp34.x*dp12.y | |
| 540 if det == 0: | |
| 541 return None | |
| 542 else: | |
| 543 ua = (dp34.x*(p1.y-p3.y)-dp34.y*(p1.x-p3.x))/det | |
| 544 return p1+dp12.multiply(ua) | |
| 545 | |
| 546 # def intersection(p1, p2, dp1, dp2): | |
| 547 # '''Returns the intersection point between the two lines | |
| 548 # defined by the respective vectors (dp) and origin points (p)''' | |
| 549 # from numpy import matrix | |
| 550 # from numpy.linalg import linalg | |
| 551 # A = matrix([[dp1.y, -dp1.x], | |
| 552 # [dp2.y, -dp2.x]]) | |
| 553 # B = matrix([[dp1.y*p1.x-dp1.x*p1.y], | |
| 554 # [dp2.y*p2.x-dp2.x*p2.y]]) | |
| 555 | |
| 556 # if linalg.det(A) == 0: | |
| 557 # return None | |
| 558 # else: | |
| 559 # intersection = linalg.solve(A,B) | |
| 560 # return Point(intersection[0,0], intersection[1,0]) | |
| 561 | |
| 306 def segmentIntersection(p1, p2, p3, p4): | 562 def segmentIntersection(p1, p2, p3, p4): |
| 307 '''Returns the intersecting point of the segments [p1, p2] and [p3, p4], None otherwise''' | 563 '''Returns the intersecting point of the segments [p1, p2] and [p3, p4], None otherwise''' |
| 308 from numpy import matrix | |
| 309 from numpy.linalg import linalg, det | |
| 310 | 564 |
| 311 if (Interval.intersection(Interval(p1.x,p2.x,True), Interval(p3.x,p4.x,True)).empty()) or (Interval.intersection(Interval(p1.y,p2.y,True), Interval(p3.y,p4.y,True)).empty()): | 565 if (Interval.intersection(Interval(p1.x,p2.x,True), Interval(p3.x,p4.x,True)).empty()) or (Interval.intersection(Interval(p1.y,p2.y,True), Interval(p3.y,p4.y,True)).empty()): |
| 312 return None | 566 return None |
| 313 else: | 567 else: |
| 314 dp1 = p2-p1#[s1[0][1]-s1[0][0], s1[1][1]-s1[1][0]] | 568 inter = intersection(p1, p2, p3, p4) |
| 315 dp2 = p4-p3#[s2[0][1]-s2[0][0], s2[1][1]-s2[1][0]] | 569 if (inter != None |
| 316 | 570 and utils.inBetween(p1.x, p2.x, inter.x) |
| 317 A = matrix([[dp1.y, -dp1.x], | 571 and utils.inBetween(p3.x, p4.x, inter.x) |
| 318 [dp2.y, -dp2.x]]) | 572 and utils.inBetween(p1.y, p2.y, inter.y) |
| 319 B = matrix([[dp1.y*p1.x-dp1.x*p1.y], | 573 and utils.inBetween(p3.y, p4.y, inter.y)): |
| 320 [dp2.y*p3.x-dp2.x*p3.y]]) | 574 return inter |
| 321 | 575 else: |
| 322 if linalg.det(A) == 0:#crossProduct(ds1, ds2) == 0: | |
| 323 return None | 576 return None |
| 324 else: | |
| 325 intersection = linalg.solve(A,B) | |
| 326 if (utils.inBetween(p1.x, p2.x, intersection[0,0]) | |
| 327 and utils.inBetween(p3.x, p4.x, intersection[0,0]) | |
| 328 and utils.inBetween(p1.y, p2.y, intersection[1,0]) | |
| 329 and utils.inBetween(p3.y, p4.y, intersection[1,0])): | |
| 330 return Point(intersection[0,0], intersection[1,0]) | |
| 331 else: | |
| 332 return None | |
| 333 | |
| 334 # TODO: implement a better algorithm for intersections of sets of segments http://en.wikipedia.org/wiki/Line_segment_intersection | |
| 335 | 577 |
| 336 class Trajectory(object): | 578 class Trajectory(object): |
| 337 '''Class for trajectories: temporal sequence of positions | 579 '''Class for trajectories: temporal sequence of positions |
| 338 | 580 |
| 339 The class is iterable''' | 581 The class is iterable''' |
| 343 self.positions = positions | 585 self.positions = positions |
| 344 else: | 586 else: |
| 345 self.positions = [[],[]] | 587 self.positions = [[],[]] |
| 346 | 588 |
| 347 @staticmethod | 589 @staticmethod |
| 590 def generate(p, v, nPoints): | |
| 591 t = Trajectory() | |
| 592 p0 = Point(p.x, p.y) | |
| 593 t.addPosition(p0) | |
| 594 for i in xrange(nPoints-1): | |
| 595 p0 += v | |
| 596 t.addPosition(p0) | |
| 597 return t, Trajectory([[v.x]*nPoints, [v.y]*nPoints]) | |
| 598 | |
| 599 @staticmethod | |
| 348 def load(line1, line2): | 600 def load(line1, line2): |
| 349 return Trajectory([[float(n) for n in line1.split(' ')], | 601 return Trajectory([[float(n) for n in line1.split(' ')], |
| 350 [float(n) for n in line2.split(' ')]]) | 602 [float(n) for n in line2.split(' ')]]) |
| 351 | 603 |
| 352 @staticmethod | 604 @staticmethod |
| 353 def fromPointList(points): | 605 def fromPointList(points): |
| 354 t = Trajectory() | 606 t = Trajectory() |
| 355 for p in points: | 607 if isinstance(points[0], list) or isinstance(points[0], tuple): |
| 356 t.addPosition(p) | 608 for p in points: |
| 609 t.addPositionXY(p[0],p[1]) | |
| 610 else: | |
| 611 for p in points: | |
| 612 t.addPosition(p) | |
| 357 return t | 613 return t |
| 614 | |
| 615 def __len__(self): | |
| 616 return len(self.positions[0]) | |
| 617 | |
| 618 def length(self): | |
| 619 return self.__len__() | |
| 620 | |
| 621 def empty(self): | |
| 622 return self.__len__() == 0 | |
| 623 | |
| 624 def __getitem__(self, i): | |
| 625 if isinstance(i, int): | |
| 626 return Point(self.positions[0][i], self.positions[1][i]) | |
| 627 else: | |
| 628 raise TypeError, "Invalid argument type." | |
| 629 #elif isinstance( key, slice ): | |
| 358 | 630 |
| 359 def __str__(self): | 631 def __str__(self): |
| 360 return ' '.join([self.__getitem__(i).__str__() for i in xrange(self.length())]) | 632 return ' '.join([self.__getitem__(i).__str__() for i in xrange(self.length())]) |
| 361 | 633 |
| 362 def __repr__(self): | 634 def __repr__(self): |
| 363 return str(self) | 635 return self.__str__() |
| 364 | 636 |
| 365 def __getitem__(self, i): | |
| 366 return Point(self.positions[0][i], self.positions[1][i]) | |
| 367 | 637 |
| 368 def __iter__(self): | 638 def __iter__(self): |
| 369 self.iterInstantNum = 0 | 639 self.iterInstantNum = 0 |
| 370 return self | 640 return self |
| 371 | 641 |
| 374 raise StopIteration | 644 raise StopIteration |
| 375 else: | 645 else: |
| 376 self.iterInstantNum += 1 | 646 self.iterInstantNum += 1 |
| 377 return self[self.iterInstantNum-1] | 647 return self[self.iterInstantNum-1] |
| 378 | 648 |
| 379 def length(self): | 649 def setPositionXY(self, i, x, y): |
| 380 return len(self.positions[0]) | 650 if i < self.__len__(): |
| 381 | 651 self.positions[0][i] = x |
| 382 def __len__(self): | 652 self.positions[1][i] = y |
| 383 return self.length() | 653 |
| 654 def setPosition(self, i, p): | |
| 655 self.setPositionXY(i, p.x, p.y) | |
| 384 | 656 |
| 385 def addPositionXY(self, x, y): | 657 def addPositionXY(self, x, y): |
| 386 self.positions[0].append(x) | 658 self.positions[0].append(x) |
| 387 self.positions[1].append(y) | 659 self.positions[1].append(y) |
| 388 | 660 |
| 389 def addPosition(self, p): | 661 def addPosition(self, p): |
| 390 self.addPositionXY(p.x, p.y) | 662 self.addPositionXY(p.x, p.y) |
| 391 | 663 |
| 392 @staticmethod | 664 def duplicateLastPosition(self): |
| 393 def _draw(positions, options = '', withOrigin = False, lastCoordinate = None, timeStep = 1, **kwargs): | 665 self.positions[0].append(self.positions[0][-1]) |
| 666 self.positions[1].append(self.positions[1][-1]) | |
| 667 | |
| 668 @staticmethod | |
| 669 def _plot(positions, options = '', withOrigin = False, lastCoordinate = None, timeStep = 1, **kwargs): | |
| 394 from matplotlib.pylab import plot | 670 from matplotlib.pylab import plot |
| 395 if lastCoordinate == None: | 671 if lastCoordinate == None: |
| 396 plot(positions[0][::timeStep], positions[1][::timeStep], options, **kwargs) | 672 plot(positions[0][::timeStep], positions[1][::timeStep], options, **kwargs) |
| 397 elif 0 <= lastCoordinate <= len(positions[0]): | 673 elif 0 <= lastCoordinate <= len(positions[0]): |
| 398 plot(positions[0][:lastCoordinate:timeStep], positions[1][:lastCoordinate:timeStep], options, **kwargs) | 674 plot(positions[0][:lastCoordinate:timeStep], positions[1][:lastCoordinate:timeStep], options, **kwargs) |
| 399 if withOrigin: | 675 if withOrigin: |
| 400 plot([positions[0][0]], [positions[1][0]], 'ro', **kwargs) | 676 plot([positions[0][0]], [positions[1][0]], 'ro', **kwargs) |
| 401 | 677 |
| 402 def project(self, homography): | 678 def project(self, homography): |
| 403 from numpy.core.multiarray import array | 679 return Trajectory(cvutils.projectTrajectory(homography, self.positions)) |
| 404 projected = cvutils.projectArray(homography, array(self.positions)) | 680 |
| 405 return Trajectory(projected) | 681 def plot(self, options = '', withOrigin = False, timeStep = 1, **kwargs): |
| 406 | 682 Trajectory._plot(self.positions, options, withOrigin, None, timeStep, **kwargs) |
| 407 def draw(self, options = '', withOrigin = False, timeStep = 1, **kwargs): | 683 |
| 408 Trajectory._draw(self.positions, options, withOrigin, None, timeStep, **kwargs) | 684 def plotAt(self, lastCoordinate, options = '', withOrigin = False, timeStep = 1, **kwargs): |
| 409 | 685 Trajectory._plot(self.positions, options, withOrigin, lastCoordinate, timeStep, **kwargs) |
| 410 def drawAt(self, lastCoordinate, options = '', withOrigin = False, timeStep = 1, **kwargs): | 686 |
| 411 Trajectory._draw(self.positions, options, withOrigin, lastCoordinate, timeStep, **kwargs) | 687 def plotOnWorldImage(self, nPixelsPerUnitDistance, imageHeight, options = '', withOrigin = False, timeStep = 1, **kwargs): |
| 412 | |
| 413 def drawOnWorldImage(self, nPixelsPerUnitDistance, imageHeight, options = '', withOrigin = False, timeStep = 1, **kwargs): | |
| 414 from matplotlib.pylab import plot | 688 from matplotlib.pylab import plot |
| 415 imgPositions = [[x*nPixelsPerUnitDistance for x in self.positions[0]], | 689 imgPositions = [[x*nPixelsPerUnitDistance for x in self.positions[0]], |
| 416 [-x*nPixelsPerUnitDistance+imageHeight for x in self.positions[1]]] | 690 [-x*nPixelsPerUnitDistance+imageHeight for x in self.positions[1]]] |
| 417 Trajectory._draw(imgPositions, options, withOrigin, timeStep, **kwargs) | 691 Trajectory._plot(imgPositions, options, withOrigin, timeStep, **kwargs) |
| 418 | 692 |
| 419 def getXCoordinates(self): | 693 def getXCoordinates(self): |
| 420 return self.positions[0] | 694 return self.positions[0] |
| 421 | 695 |
| 422 def getYCoordinates(self): | 696 def getYCoordinates(self): |
| 449 print 'Trajectories of different lengths' | 723 print 'Trajectories of different lengths' |
| 450 return None | 724 return None |
| 451 else: | 725 else: |
| 452 return Trajectory([[a-b for a,b in zip(self.getXCoordinates(),traj2.getXCoordinates())], | 726 return Trajectory([[a-b for a,b in zip(self.getXCoordinates(),traj2.getXCoordinates())], |
| 453 [a-b for a,b in zip(self.getYCoordinates(),traj2.getYCoordinates())]]) | 727 [a-b for a,b in zip(self.getYCoordinates(),traj2.getYCoordinates())]]) |
| 728 | |
| 729 def differentiate(self, doubleLastPosition = False): | |
| 730 diff = Trajectory() | |
| 731 for i in xrange(1, self.length()): | |
| 732 diff.addPosition(self[i]-self[i-1]) | |
| 733 if doubleLastPosition: | |
| 734 diff.addPosition(diff[-1]) | |
| 735 return diff | |
| 454 | 736 |
| 455 def norm(self): | 737 def norm(self): |
| 456 '''Returns the list of the norms at each instant''' | 738 '''Returns the list of the norms at each instant''' |
| 457 # def add(x, y): return x+y | 739 # def add(x, y): return x+y |
| 458 # sq = map(add, [x*x for x in self.positions[0]], [y*y for y in self.positions[1]]) | 740 # sq = map(add, [x*x for x in self.positions[0]], [y*y for y in self.positions[1]]) |
| 459 # return sqrt(sq) | 741 # return sqrt(sq) |
| 460 from numpy import hypot | 742 from numpy import hypot |
| 461 return hypot(self.positions[0], self.positions[1]) | 743 return hypot(self.positions[0], self.positions[1]) |
| 462 | 744 |
| 463 def cumulatedDisplacement(self): | 745 # def cumulatedDisplacement(self): |
| 464 'Returns the sum of the distances between each successive point' | 746 # 'Returns the sum of the distances between each successive point' |
| 465 displacement = 0 | 747 # displacement = 0 |
| 748 # for i in xrange(self.length()-1): | |
| 749 # displacement += Point.distanceNorm2(self.__getitem__(i),self.__getitem__(i+1)) | |
| 750 # return displacement | |
| 751 | |
| 752 def computeCumulativeDistances(self): | |
| 753 '''Computes the distance from each point to the next and the cumulative distance up to the point | |
| 754 Can be accessed through getDistance(idx) and getCumulativeDistance(idx)''' | |
| 755 self.distances = [] | |
| 756 self.cumulativeDistances = [0.] | |
| 757 p1 = self[0] | |
| 758 cumulativeDistance = 0. | |
| 466 for i in xrange(self.length()-1): | 759 for i in xrange(self.length()-1): |
| 467 displacement += Point.distanceNorm2(self.__getitem__(i),self.__getitem__(i+1)) | 760 p2 = self[i+1] |
| 468 return displacement | 761 self.distances.append(Point.distanceNorm2(p1,p2)) |
| 762 cumulativeDistance += self.distances[-1] | |
| 763 self.cumulativeDistances.append(cumulativeDistance) | |
| 764 p1 = p2 | |
| 765 | |
| 766 def getDistance(self,i): | |
| 767 '''Return the distance between points i and i+1''' | |
| 768 if i < self.length()-1: | |
| 769 return self.distances[i] | |
| 770 else: | |
| 771 print('Index {} beyond trajectory length {}-1'.format(i, self.length())) | |
| 772 | |
| 773 def getCumulativeDistance(self, i): | |
| 774 '''Return the cumulative distance between the beginning and point i''' | |
| 775 if i < self.length(): | |
| 776 return self.cumulativeDistances[i] | |
| 777 else: | |
| 778 print('Index {} beyond trajectory length {}'.format(i, self.length())) | |
| 779 | |
| 780 def similarOrientation(self, refDirection, cosineThreshold, minProportion = 0.5): | |
| 781 '''Indicates whether the minProportion (<=1.) (eg half) of the trajectory elements (vectors for velocity) | |
| 782 have a cosine with refDirection is smaller than cosineThreshold''' | |
| 783 count = 0 | |
| 784 lengthThreshold = float(self.length())*minProportion | |
| 785 for p in self: | |
| 786 if p.similarOrientation(refDirection, cosineThreshold): | |
| 787 count += 1 | |
| 788 if count > lengthThreshold: | |
| 789 return True | |
| 790 return False | |
| 469 | 791 |
| 470 def wiggliness(self): | 792 def wiggliness(self): |
| 471 return self.cumulatedDisplacement()/float(Point.distanceNorm2(self.__getitem__(0),self.__getitem__(self.length()-1))) | 793 return self.cumulatedDisplacement()/float(Point.distanceNorm2(self.__getitem__(0),self.__getitem__(self.length()-1))) |
| 472 | 794 |
| 473 def getIntersections(self, p1, p2): | 795 def getIntersections(self, p1, p2): |
| 495 return Trajectory([self.positions[0][inter.first:inter.last], | 817 return Trajectory([self.positions[0][inter.first:inter.last], |
| 496 self.positions[1][inter.first:inter.last]]) | 818 self.positions[1][inter.first:inter.last]]) |
| 497 else: | 819 else: |
| 498 return None | 820 return None |
| 499 | 821 |
| 500 def getTrajectoryInPolygon(self, polygon): | 822 def getTrajectoryInPolygonNoShapely(self, polygon): |
| 501 '''Returns the set of points inside the polygon | 823 '''Returns the trajectory built with the set of points inside the polygon |
| 502 (array of Nx2 coordinates of the polygon vertices)''' | 824 (array of Nx2 coordinates of the polygon vertices)''' |
| 503 import matplotlib.nxutils as nx | |
| 504 traj = Trajectory() | 825 traj = Trajectory() |
| 505 result = nx.points_inside_poly(self.asArray().T, polygon) | 826 for p in self: |
| 506 for i in xrange(self.length()): | 827 if p.inPolygonNoShapely(polygon): |
| 507 if result[i]: | 828 traj.addPosition(p) |
| 508 traj.addPositionXY(self.positions[0][i], self.positions[1][i]) | |
| 509 return traj | 829 return traj |
| 510 | 830 |
| 511 # version 2: use shapely polygon contains | 831 if shapelyAvailable: |
| 512 | 832 def getTrajectoryInPolygon(self, polygon): |
| 513 @staticmethod | 833 '''Returns the trajectory built with the set of points inside the (shapely) polygon''' |
| 514 def norm2LCSS(t1, t2, threshold): | 834 traj = Trajectory() |
| 515 return utils.LCSS(t1, t2, threshold, Point.distanceNorm2) | 835 points = [p.asShapely() for p in self] |
| 516 | 836 for p in pointsInPolygon(points, polygon): |
| 517 @staticmethod | 837 traj.addPositionXY(p.x, p.y) |
| 518 def normMaxLCSS(t1, t2, threshold): | 838 return traj |
| 519 return utils.LCSS(t1, t2, threshold, lambda p1, p2: (p1-p2).normMax()) | 839 |
| 840 @staticmethod | |
| 841 def lcss(t1, t2, lcss): | |
| 842 return lcss.compute(t1, t2) | |
| 843 | |
| 844 class CurvilinearTrajectory(Trajectory): | |
| 845 '''Sub class of trajectory for trajectories with curvilinear coordinates and lane assignements | |
| 846 longitudinal coordinate is stored as first coordinate (exterior name S) | |
| 847 lateral coordiante is stored as second coordinate''' | |
| 848 | |
| 849 def __init__(self, S = None, Y = None, lanes = None): | |
| 850 if S == None or Y == None or len(S) != len(Y): | |
| 851 self.positions = [[],[]] | |
| 852 if S != None and Y != None and len(S) != len(Y): | |
| 853 print("S and Y coordinates of different lengths\nInitializing to empty lists") | |
| 854 else: | |
| 855 self.positions = [S,Y] | |
| 856 if lanes == None or len(lanes) != self.length(): | |
| 857 self.lanes = [] | |
| 858 else: | |
| 859 self.lanes = lanes | |
| 860 | |
| 861 def __getitem__(self,i): | |
| 862 if isinstance(i, int): | |
| 863 return [self.positions[0][i], self.positions[1][i], self.lanes[i]] | |
| 864 else: | |
| 865 raise TypeError, "Invalid argument type." | |
| 866 #elif isinstance( key, slice ): | |
| 867 | |
| 868 def getSCoordinates(self): | |
| 869 return self.getXCoordinates() | |
| 870 | |
| 871 def getLanes(self): | |
| 872 return self.lanes | |
| 873 | |
| 874 def addPositionSYL(self, s, y, lane): | |
| 875 self.addPositionXY(s,y) | |
| 876 self.lanes.append(lane) | |
| 877 | |
| 878 def addPosition(self, p): | |
| 879 'Adds position in the point format for curvilinear of list with 3 values' | |
| 880 self.addPositionSYL(p[0], p[1], p[2]) | |
| 881 | |
| 882 def setPosition(self, i, s, y, lane): | |
| 883 self.setPositionXY(i, s, y) | |
| 884 if i < self.__len__(): | |
| 885 self.lanes[i] = lane | |
| 886 | |
| 887 def differentiate(self, doubleLastPosition = False): | |
| 888 diff = CurvilinearTrajectory() | |
| 889 p1 = self[0] | |
| 890 for i in xrange(1, self.length()): | |
| 891 p2 = self[i] | |
| 892 diff.addPositionSYL(p2[0]-p1[0], p2[1]-p1[1], p1[2]) | |
| 893 p1=p2 | |
| 894 if doubleLastPosition and self.length() > 1: | |
| 895 diff.addPosition(diff[-1]) | |
| 896 return diff | |
| 897 | |
| 898 def getIntersections(self, S1, lane = None): | |
| 899 '''Returns a list of the indices at which the trajectory | |
| 900 goes past the curvilinear coordinate S1 | |
| 901 (in provided lane if lane != None) | |
| 902 the list is empty if there is no crossing''' | |
| 903 indices = [] | |
| 904 for i in xrange(self.length()-1): | |
| 905 q1=self.__getitem__(i) | |
| 906 q2=self.__getitem__(i+1) | |
| 907 if q1[0] <= S1 < q2[0] and (lane == None or (self.lanes[i] == lane and self.lanes[i+1] == lane)): | |
| 908 indices.append(i+(S1-q1[0])/(q2[0]-q1[0])) | |
| 909 return indices | |
| 520 | 910 |
| 521 ################## | 911 ################## |
| 522 # Moving Objects | 912 # Moving Objects |
| 523 ################## | 913 ################## |
| 524 | 914 |
| 525 userTypeNames = ['car', | 915 userTypeNames = ['unknown', |
| 916 'car', | |
| 526 'pedestrian', | 917 'pedestrian', |
| 527 'twowheels', | 918 'motorcycle', |
| 919 'bicycle', | |
| 528 'bus', | 920 'bus', |
| 529 'truck'] | 921 'truck'] |
| 530 | 922 |
| 531 userType2Num = utils.inverseEnumeration(userTypeNames) | 923 userType2Num = utils.inverseEnumeration(userTypeNames) |
| 532 | 924 |
| 533 class MovingObject(STObject): | 925 class MovingObject(STObject): |
| 534 '''Class for moving objects: a spatio-temporal object | 926 '''Class for moving objects: a spatio-temporal object |
| 535 with a trajectory and a geometry (constant volume over time) and a usertype (e.g. road user) | 927 with a trajectory and a geometry (constant volume over time) |
| 928 and a usertype (e.g. road user) coded as a number (see userTypeNames) | |
| 536 ''' | 929 ''' |
| 537 | 930 |
| 538 def __init__(self, num = None, timeInterval = None, positions = None, geometry = None, userType = None): | 931 def __init__(self, num = None, timeInterval = None, positions = None, velocities = None, geometry = None, userType = userType2Num['unknown']): |
| 539 super(MovingObject, self).__init__(num, timeInterval) | 932 super(MovingObject, self).__init__(num, timeInterval) |
| 540 self.positions = positions | 933 self.positions = positions |
| 934 self.velocities = velocities | |
| 541 self.geometry = geometry | 935 self.geometry = geometry |
| 542 self.userType = userType | 936 self.userType = userType |
| 937 self.features = [] | |
| 543 # compute bounding polygon from trajectory | 938 # compute bounding polygon from trajectory |
| 544 | 939 |
| 940 @staticmethod | |
| 941 def generate(p, v, timeInterval): | |
| 942 positions, velocities = Trajectory.generate(p, v, int(timeInterval.length())) | |
| 943 return MovingObject(timeInterval = timeInterval, positions = positions, velocities = velocities) | |
| 944 | |
| 545 def getObjectInTimeInterval(self, inter): | 945 def getObjectInTimeInterval(self, inter): |
| 546 '''Returns a new object extracted from self, | 946 '''Returns a new object extracted from self, |
| 547 restricted to time interval inter''' | 947 restricted to time interval inter''' |
| 548 intersection = TimeInterval.intersection(inter, self.getTimeInterval()) | 948 intersection = TimeInterval.intersection(inter, self.getTimeInterval()) |
| 549 if not intersection.empty(): | 949 if not intersection.empty(): |
| 563 return self.positions | 963 return self.positions |
| 564 | 964 |
| 565 def getVelocities(self): | 965 def getVelocities(self): |
| 566 return self.velocities | 966 return self.velocities |
| 567 | 967 |
| 968 def getUserType(self): | |
| 969 return self.userType | |
| 970 | |
| 971 def getCurvilinearPositions(self): | |
| 972 if hasattr(self, 'curvilinearPositions'): | |
| 973 return self.curvilinearPositions | |
| 974 else: | |
| 975 return None | |
| 976 | |
| 977 def setUserType(self, userType): | |
| 978 self.userType = userType | |
| 979 | |
| 568 def setFeatures(self, features): | 980 def setFeatures(self, features): |
| 569 self.features = [features[i] for i in self.featureNumbers] | 981 self.features = [features[i] for i in self.featureNumbers] |
| 570 | 982 |
| 571 def getSpeeds(self): | 983 def getSpeeds(self): |
| 572 return self.getVelocities().norm() | 984 return self.getVelocities().norm() |
| 587 return self.positions.getXCoordinates() | 999 return self.positions.getXCoordinates() |
| 588 | 1000 |
| 589 def getYCoordinates(self): | 1001 def getYCoordinates(self): |
| 590 return self.positions.getYCoordinates() | 1002 return self.positions.getYCoordinates() |
| 591 | 1003 |
| 592 def draw(self, options = '', withOrigin = False, timeStep = 1, **kwargs): | 1004 def plot(self, options = '', withOrigin = False, timeStep = 1, withFeatures = False, **kwargs): |
| 593 self.positions.draw(options, withOrigin, timeStep, **kwargs) | 1005 if withFeatures: |
| 594 | 1006 for f in self.features: |
| 595 def drawOnWorldImage(self, nPixelsPerUnitDistance, imageHeight, options = '', withOrigin = False, timeStep = 1, **kwargs): | 1007 f.positions.plot('r', True, timeStep, **kwargs) |
| 596 self.positions.drawOnWorldImage(nPixelsPerUnitDistance, imageHeight, options, withOrigin, timeStep, **kwargs) | 1008 self.positions.plot('bx-', True, timeStep, **kwargs) |
| 1009 else: | |
| 1010 self.positions.plot(options, withOrigin, timeStep, **kwargs) | |
| 1011 | |
| 1012 def plotOnWorldImage(self, nPixelsPerUnitDistance, imageHeight, options = '', withOrigin = False, timeStep = 1, **kwargs): | |
| 1013 self.positions.plotOnWorldImage(nPixelsPerUnitDistance, imageHeight, options, withOrigin, timeStep, **kwargs) | |
| 597 | 1014 |
| 598 def play(self, videoFilename, homography = None): | 1015 def play(self, videoFilename, homography = None): |
| 599 cvutils.displayTrajectories(videoFilename, [self], homography, self.getFirstInstant(), self.getLastInstant()) | 1016 cvutils.displayTrajectories(videoFilename, [self], homography, self.getFirstInstant(), self.getLastInstant()) |
| 600 | 1017 |
| 1018 def speedDiagnostics(self, framerate = 1., display = False): | |
| 1019 from numpy import std | |
| 1020 from scipy.stats import scoreatpercentile | |
| 1021 speeds = framerate*self.getSpeeds() | |
| 1022 coef = utils.linearRegression(range(len(speeds)), speeds) | |
| 1023 print('min/5th perc speed: {} / {}\nspeed diff: {}\nspeed stdev: {}\nregression: {}'.format(min(speeds), scoreatpercentile(speeds, 5), speeds[-2]-speeds[1], std(speeds), coef[0])) | |
| 1024 if display: | |
| 1025 from matplotlib.pyplot import figure, plot, axis | |
| 1026 figure(1) | |
| 1027 self.plot() | |
| 1028 axis('equal') | |
| 1029 figure(2) | |
| 1030 plot(list(self.getTimeInterval()), speeds) | |
| 1031 | |
| 1032 @staticmethod | |
| 1033 def distances(obj1, obj2, instant1, _instant2 = None): | |
| 1034 from scipy.spatial.distance import cdist | |
| 1035 if _instant2 == None: | |
| 1036 instant2 = instant1 | |
| 1037 else: | |
| 1038 instant2 = _instant2 | |
| 1039 positions1 = [f.getPositionAtInstant(instant1).astuple() for f in obj1.features if f.existsAtInstant(instant1)] | |
| 1040 positions2 = [f.getPositionAtInstant(instant2).astuple() for f in obj2.features if f.existsAtInstant(instant2)] | |
| 1041 return cdist(positions1, positions2, metric = 'euclidean') | |
| 1042 | |
| 1043 @staticmethod | |
| 1044 def minDistance(obj1, obj2, instant1, instant2 = None): | |
| 1045 return MovingObject.distances(obj1, obj2, instant1, instant2).min() | |
| 1046 | |
| 1047 @staticmethod | |
| 1048 def maxDistance(obj1, obj2, instant, instant2 = None): | |
| 1049 return MovingObject.distances(obj1, obj2, instant1, instant2).max() | |
| 1050 | |
| 1051 def maxSize(self): | |
| 1052 '''Returns the max distance between features | |
| 1053 at instant there are the most features''' | |
| 1054 if hasattr(self, 'features'): | |
| 1055 nFeatures = -1 | |
| 1056 tMaxFeatures = 0 | |
| 1057 for t in self.getTimeInterval(): | |
| 1058 n = len([f for f in self.features if f.existsAtInstant(t)]) | |
| 1059 if n > nFeatures: | |
| 1060 nFeatures = n | |
| 1061 tMaxFeatures = t | |
| 1062 return MovingObject.maxDistance(self, self, tMaxFeatures) | |
| 1063 else: | |
| 1064 print('Load features to compute a maximum size') | |
| 1065 return None | |
| 1066 | |
| 1067 def setRoutes(self, startRouteID, endRouteID): | |
| 1068 self.startRouteID = startRouteID | |
| 1069 self.endRouteID = endRouteID | |
| 1070 | |
| 601 def getInstantsCrossingLane(self, p1, p2): | 1071 def getInstantsCrossingLane(self, p1, p2): |
| 602 '''Returns the instant(s) | 1072 '''Returns the instant(s) |
| 603 at which the object passes from one side of the segment to the other | 1073 at which the object passes from one side of the segment to the other |
| 604 empty list if there is no crossing''' | 1074 empty list if there is no crossing''' |
| 605 indices = self.positions.getIntersections(p1, p2) | 1075 indices = self.positions.getIntersections(p1, p2) |
| 608 def predictPosition(self, instant, nTimeSteps, externalAcceleration = Point(0,0)): | 1078 def predictPosition(self, instant, nTimeSteps, externalAcceleration = Point(0,0)): |
| 609 '''Predicts the position of object at instant+deltaT, | 1079 '''Predicts the position of object at instant+deltaT, |
| 610 at constant speed''' | 1080 at constant speed''' |
| 611 return predictPositionNoLimit(nTimeSteps, self.getPositionAtInstant(instant), self.getVelocityAtInstant(instant), externalAcceleration) | 1081 return predictPositionNoLimit(nTimeSteps, self.getPositionAtInstant(instant), self.getVelocityAtInstant(instant), externalAcceleration) |
| 612 | 1082 |
| 1083 def projectCurvilinear(self, alignments, ln_mv_av_win=3): | |
| 1084 ''' Add, for every object position, the class 'moving.CurvilinearTrajectory()' | |
| 1085 (curvilinearPositions instance) which holds information about the | |
| 1086 curvilinear coordinates using alignment metadata. | |
| 1087 From Paul St-Aubin's PVA tools | |
| 1088 ====== | |
| 1089 | |
| 1090 Input: | |
| 1091 ====== | |
| 1092 alignments = a list of alignments, where each alignment is a list of | |
| 1093 points (class Point). | |
| 1094 ln_mv_av_win = moving average window (in points) in which to smooth | |
| 1095 lane changes. As per tools_math.cat_mvgavg(), this term | |
| 1096 is a search *radius* around the center of the window. | |
| 1097 | |
| 1098 ''' | |
| 1099 | |
| 1100 self.curvilinearPositions = CurvilinearTrajectory() | |
| 1101 | |
| 1102 #For each point | |
| 1103 for i in xrange(int(self.length())): | |
| 1104 result = getSYfromXY(self.getPositionAt(i), alignments) | |
| 1105 | |
| 1106 # Error handling | |
| 1107 if(result == None): | |
| 1108 print('Warning: trajectory {} at point {} {} has alignment errors (spline snapping)\nCurvilinear trajectory could not be computed'.format(self.getNum(), i, self.getPositionAt(i))) | |
| 1109 else: | |
| 1110 [align, alignPoint, snappedPoint, subsegmentDistance, S, Y] = result | |
| 1111 self.curvilinearPositions.addPositionSYL(S, Y, align) | |
| 1112 | |
| 1113 ## Go back through points and correct lane | |
| 1114 #Run through objects looking for outlier point | |
| 1115 smoothed_lanes = utils.cat_mvgavg(self.curvilinearPositions.getLanes(),ln_mv_av_win) | |
| 1116 ## Recalculate projected point to new lane | |
| 1117 lanes = self.curvilinearPositions.getLanes() | |
| 1118 if(lanes != smoothed_lanes): | |
| 1119 for i in xrange(len(lanes)): | |
| 1120 if(lanes[i] != smoothed_lanes[i]): | |
| 1121 result = getSYfromXY(self.getPositionAt(i),[alignments[smoothed_lanes[i]]]) | |
| 1122 | |
| 1123 # Error handling | |
| 1124 if(result == None): | |
| 1125 ## This can be triggered by tracking errors when the trajectory jumps around passed another alignment. | |
| 1126 print(' Warning: trajectory {} at point {} {} has alignment errors during trajectory smoothing and will not be corrected.'.format(self.getNum(), i, self.getPositionAt(i))) | |
| 1127 else: | |
| 1128 [align, alignPoint, snappedPoint, subsegmentDistance, S, Y] = result | |
| 1129 self.curvilinearPositions.setPosition(i, S, Y, align) | |
| 1130 | |
| 1131 def computeSmoothTrajectory(self, minCommonIntervalLength): | |
| 1132 '''Computes the trajectory as the mean of all features | |
| 1133 if a feature exists, its position is | |
| 1134 | |
| 1135 Warning work in progress | |
| 1136 TODO? not use the first/last 1-.. positions''' | |
| 1137 from numpy import array, median | |
| 1138 nFeatures = len(self.features) | |
| 1139 if nFeatures == 0: | |
| 1140 print('Empty object features\nCannot compute smooth trajectory') | |
| 1141 else: | |
| 1142 # compute the relative position vectors | |
| 1143 relativePositions = {} # relativePositions[(i,j)] is the position of j relative to i | |
| 1144 for i in xrange(nFeatures): | |
| 1145 for j in xrange(i): | |
| 1146 fi = self.features[i] | |
| 1147 fj = self.features[j] | |
| 1148 inter = fi.commonTimeInterval(fj) | |
| 1149 if inter.length() >= minCommonIntervalLength: | |
| 1150 xi = array(fi.getXCoordinates()[inter.first-fi.getFirstInstant():int(fi.length())-(fi.getLastInstant()-inter.last)]) | |
| 1151 yi = array(fi.getYCoordinates()[inter.first-fi.getFirstInstant():int(fi.length())-(fi.getLastInstant()-inter.last)]) | |
| 1152 xj = array(fj.getXCoordinates()[inter.first-fj.getFirstInstant():int(fj.length())-(fj.getLastInstant()-inter.last)]) | |
| 1153 yj = array(fj.getYCoordinates()[inter.first-fj.getFirstInstant():int(fj.length())-(fj.getLastInstant()-inter.last)]) | |
| 1154 relativePositions[(i,j)] = Point(median(xj-xi), median(yj-yi)) | |
| 1155 relativePositions[(j,i)] = -relativePositions[(i,j)] | |
| 1156 | |
| 1157 ### | |
| 1158 # User Type Classification | |
| 1159 ### | |
| 1160 def classifyUserTypeSpeedMotorized(self, threshold, aggregationFunc = median, ignoreNInstantsAtEnds = 0): | |
| 1161 '''Classifies slow and fast road users | |
| 1162 slow: non-motorized -> pedestrians | |
| 1163 fast: motorized -> cars''' | |
| 1164 if ignoreNInstantsAtEnds > 0: | |
| 1165 speeds = self.getSpeeds()[ignoreNInstantsAtEnds:-ignoreNInstantsAtEnds] | |
| 1166 else: | |
| 1167 speeds = self.getSpeeds() | |
| 1168 if aggregationFunc(speeds) >= threshold: | |
| 1169 self.setUserType(userType2Num['car']) | |
| 1170 else: | |
| 1171 self.setUserType(userType2Num['pedestrian']) | |
| 1172 | |
| 1173 def classifyUserTypeSpeed(self, speedProbabilities, aggregationFunc = median): | |
| 1174 '''Classifies road user per road user type | |
| 1175 speedProbabilities are functions return P(speed|class) | |
| 1176 in a dictionary indexed by user type names | |
| 1177 Returns probabilities for each class | |
| 1178 | |
| 1179 for simple threshold classification, simply pass non-overlapping indicator functions (membership) | |
| 1180 e.g. def indic(x): | |
| 1181 if abs(x-mu) < sigma: | |
| 1182 return 1 | |
| 1183 else: | |
| 1184 return x''' | |
| 1185 if not hasattr(self, aggregatedSpeed): | |
| 1186 self.aggregatedSpeed = aggregationFunc(self.getSpeeds()) | |
| 1187 userTypeProbabilities = {} | |
| 1188 for userTypename in speedProbabilities: | |
| 1189 userTypeProbabilities[userType2Num[userTypename]] = speedProbabilities[userTypename](self.aggregatedSpeed) | |
| 1190 self.setUserType(utils.argmaxDict(userTypeProbabilities)) | |
| 1191 return userTypeProbabilities | |
| 1192 | |
| 1193 def initClassifyUserTypeHoGSVM(self, aggregationFunc = median): | |
| 1194 '''Initializes the data structures for classification | |
| 1195 | |
| 1196 TODO? compute speed for longest feature? | |
| 1197 Skip beginning and end of feature for speed? Offer options instead of median''' | |
| 1198 self.aggregatedSpeed = aggregationFunc(self.getSpeeds()) | |
| 1199 self.userTypes = {} | |
| 1200 | |
| 1201 def classifyUserTypeHoGSVMAtInstant(self, img, pedBikeCarSVM, instant, homography, width, height, bikeCarSVM = None, pedBikeSpeedTreshold = float('Inf'), bikeCarSpeedThreshold = float('Inf'), px = 0.2, py = 0.2, pixelThreshold = 800): | |
| 1202 '''Extract the image box around the object and | |
| 1203 applies the SVM model on it''' | |
| 1204 from numpy import array | |
| 1205 croppedImg, yCropMin, yCropMax, xCropMin, xCropMax = imageBox(img, self, instant, homography, width, height, px, py, pixelThreshold) | |
| 1206 if len(croppedImg) > 0: # != [] | |
| 1207 hog = array([cvutils.HOG(croppedImg)], dtype = np.float32) | |
| 1208 if self.aggregatedSpeed < pedBikeSpeedTreshold or bikeCarSVM == None: | |
| 1209 self.userTypes[instant] = int(pedBikeCarSVM.predict(hog)) | |
| 1210 elif self.aggregatedSpeed < bikeCarSpeedTreshold: | |
| 1211 self.userTypes[instant] = int(bikeCarSVM.predict(hog)) | |
| 1212 else: | |
| 1213 self.userTypes[instant] = userType2Num['car'] | |
| 1214 else: | |
| 1215 self.userTypes[instant] = userType2Num['unknown'] | |
| 1216 | |
| 1217 def classifyUserTypeHoGSVM(self, images, pedBikeCarSVM, homography, width, height, bikeCarSVM = None, pedBikeSpeedTreshold = float('Inf'), bikeCarSpeedThreshold = float('Inf'), speedProbabilities = None, aggregationFunc = median, px = 0.2, py = 0.2, pixelThreshold = 800): | |
| 1218 '''Agregates SVM detections in each image and returns probability | |
| 1219 (proportion of instants with classification in each category) | |
| 1220 | |
| 1221 iamges is a dictionary of images indexed by instant | |
| 1222 With default parameters, the general (ped-bike-car) classifier will be used | |
| 1223 TODO? consider all categories?''' | |
| 1224 if not hasattr(self, aggregatedSpeed) or not hasattr(self, userTypes): | |
| 1225 print('Initilize the data structures for classification by HoG-SVM') | |
| 1226 self.initClassifyUserTypeHoGSVM(aggregationFunc) | |
| 1227 | |
| 1228 if len(self.userTypes) != self.length(): # if classification has not been done previously | |
| 1229 for t in self.getTimeInterval(): | |
| 1230 if t not in self.userTypes: | |
| 1231 self.classifyUserTypeHoGSVMAtInstant(images[t], pedBikeCarSVM, t, homography, width, height, bikeCarSVM, pedBikeSpeedTreshold, bikeCarSpeedThreshold, px, py, pixelThreshold) | |
| 1232 # compute P(Speed|Class) | |
| 1233 if speedProbabilities == None: # equiprobable information from speed | |
| 1234 userTypeProbabilities = {userType2Num['car']: 1., userType2Num['pedestrian']: 1., userType2Num['bicycle']: 1.} | |
| 1235 else: | |
| 1236 userTypeProbabilities = {userType2Num[userTypename]: speedProbabilities[userTypename](self.aggregatedSpeed) for userTypename in speedProbabilities} | |
| 1237 # result is P(Class|Appearance) x P(Speed|Class) | |
| 1238 nInstantsUserType = {userType2Num[userTypename]: 0 for userTypename in userTypeProbabilities}# number of instants the object is classified as userTypename | |
| 1239 for t in self.userTypes: | |
| 1240 nInstantsUserType[self.userTypes[t]] += 1 | |
| 1241 for userTypename in userTypeProbabilities: | |
| 1242 userTypeProbabilities[userTypename] *= nInstantsUserType[userTypename] | |
| 1243 # class is the user type that maximizes usertype probabilities | |
| 1244 self.setUserType(utils.argmaxDict(userTypeProbabilities)) | |
| 1245 | |
| 1246 def classifyUserTypeArea(self, areas, homography): | |
| 1247 '''Classifies the object based on its location (projected to image space) | |
| 1248 areas is a dictionary of matrix of the size of the image space | |
| 1249 for different road users possible locations, indexed by road user type names | |
| 1250 | |
| 1251 TODO: areas could be a wrapper object with a contains method that would work for polygons and images (with wrapper class) | |
| 1252 skip frames at beginning/end?''' | |
| 1253 print('not implemented/tested yet') | |
| 1254 if not hasattr(self, projectedPositions): | |
| 1255 if homography != None: | |
| 1256 self.projectedPositions = obj.positions.project(homography) | |
| 1257 else: | |
| 1258 self.projectedPositions = obj.positions | |
| 1259 possibleUserTypes = {userType: 0 for userType in range(len(userTypenames))} | |
| 1260 for p in self.projectedPositions: | |
| 1261 for userTypename in areas: | |
| 1262 if areas[userTypename][p.x, p.y] != 0: | |
| 1263 possibleUserTypes[userType2Enum[userTypename]] += 1 | |
| 1264 # what to do: threshold for most common type? self.setUserType() | |
| 1265 return possibleUserTypes | |
| 1266 | |
| 613 @staticmethod | 1267 @staticmethod |
| 614 def collisionCourseDotProduct(movingObject1, movingObject2, instant): | 1268 def collisionCourseDotProduct(movingObject1, movingObject2, instant): |
| 615 'A positive result indicates that the road users are getting closer' | 1269 'A positive result indicates that the road users are getting closer' |
| 616 deltap = movingObject1.getPositionAtInstant(instant)-movingObject2.getPositionAtInstant(instant) | 1270 deltap = movingObject1.getPositionAtInstant(instant)-movingObject2.getPositionAtInstant(instant) |
| 617 deltav = movingObject2.getVelocityAtInstant(instant)-movingObject1.getVelocityAtInstant(instant) | 1271 deltav = movingObject2.getVelocityAtInstant(instant)-movingObject1.getVelocityAtInstant(instant) |
| 618 return Point.dot(deltap, deltav) | 1272 return Point.dot(deltap, deltav) |
| 619 | 1273 |
| 620 @staticmethod | 1274 @staticmethod |
| 621 def collisionCourseCosine(movingObject1, movingObject2, instant): | 1275 def collisionCourseCosine(movingObject1, movingObject2, instant): |
| 622 'A positive result indicates that the road users are getting closer' | 1276 'A positive result indicates that the road users are getting closer' |
| 623 deltap = movingObject1.getPositionAtInstant(instant)-movingObject2.getPositionAtInstant(instant) | 1277 return Point.cosine(movingObject1.getPositionAtInstant(instant)-movingObject2.getPositionAtInstant(instant), #deltap |
| 624 deltav = movingObject2.getVelocityAtInstant(instant)-movingObject1.getVelocityAtInstant(instant) | 1278 movingObject2.getVelocityAtInstant(instant)-movingObject1.getVelocityAtInstant(instant)) #deltav |
| 625 return Point.dot(deltap, deltav)/(deltap.norm2()*deltav.norm2()) | 1279 |
| 1280 | |
| 1281 ################## | |
| 1282 # Annotations | |
| 1283 ################## | |
| 1284 | |
| 1285 class BBAnnotation(MovingObject): | |
| 1286 '''Class for : a series of ground truth annotations using bounding boxes | |
| 1287 Its center is the center of the containing shape | |
| 1288 ''' | |
| 1289 | |
| 1290 def __init__(self, num = None, timeInterval = None, topPositions = None, bottomPositions = None, userType = userType2Num['unknown']): | |
| 1291 super(BBAnnotation, self).__init__(num, timeInterval, Trajectory(), userType = userType) | |
| 1292 self.topPositions = topPositions.getPositions() | |
| 1293 self.bottomPositions = bottomPositions.getPositions() | |
| 1294 for i in xrange(int(topPositions.length())): | |
| 1295 self.positions.addPosition((topPositions.getPositionAt(i) + bottomPositions.getPositionAt(i)).multiply(0.5)) | |
| 1296 | |
| 626 | 1297 |
| 627 def plotRoadUsers(objects, colors): | 1298 def plotRoadUsers(objects, colors): |
| 628 '''Colors is a PlottingPropertyValues instance''' | 1299 '''Colors is a PlottingPropertyValues instance''' |
| 629 from matplotlib.pyplot import figure, axis | 1300 from matplotlib.pyplot import figure, axis |
| 630 figure() | 1301 figure() |
| 631 for obj in objects: | 1302 for obj in objects: |
| 632 obj.draw(colors.get(obj.userType)) | 1303 obj.plot(colors.get(obj.userType)) |
| 633 axis('equal') | 1304 axis('equal') |
| 634 | 1305 |
| 635 | 1306 |
| 636 if __name__ == "__main__": | 1307 if __name__ == "__main__": |
| 637 import doctest | 1308 import doctest |
