Mercurial Hosting > traffic-intelligence
changeset 312:6c068047edbf
merged with Mohamed s work
author | Nicolas Saunier <nicolas.saunier@polymtl.ca> |
---|---|
date | Thu, 11 Apr 2013 22:46:33 -0400 |
parents | 273c200ec32e (diff) 80cbafd69109 (current diff) |
children | 43e62b9cb652 |
files | python/indicators.py python/ml.py python/utils.py |
diffstat | 19 files changed, 579 insertions(+), 223 deletions(-) [+] |
line wrap: on
line diff
--- a/python/calibration-translation.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/calibration-translation.py Thu Apr 11 22:46:33 2013 -0400 @@ -58,7 +58,7 @@ frameFilename = utils.removeExtension(f)+'-frame.png' # TODO if frame image already exists, no need to search for it again if not os.path.exists(frameFilename): key = -1 - while key != cvutils.cvKeyNumbers['y']: + while chr(key&255) != 'y': (ret, img) = captures[f].read() cv2.imshow('Image',img) print('Can one see the reference points in the image? (y/n)') @@ -91,15 +91,15 @@ cv2.circle(displayImg, tuple(p+t[0]), 3, (255,0,0)) cv2.imshow('Image',displayImg) - while not(key == cvutils.cvKeyNumbers['y'] or key == cvutils.cvKeyNumbers['n']): + while not(chr(key&255) == 'y' or chr(key&255) == 'n'): print('Are the translated points rightly located (y/n)?') key = cv2.waitKey(0) - if key == cvutils.cvKeyNumbers['y']: # compute homography with translated numbers + if chr(key&255) == 'y': # compute homography with translated numbers newImgPts = np.array([p+t[0] for p in imgPts]) else: print('No translation could be found automatically. You will have to manually input world reference points.') - if t==None or key != cvutils.cvKeyNumbers['y']:# if no translation could computed or it is not satisfactory + if t==None or chr(key&255) != 'y':# if no translation could computed or it is not satisfactory print('Select the corresponding points in the same order as in the reference image') plt.figure(1) plt.imshow(displayRef)
--- a/python/compute-homography.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/compute-homography.py Thu Apr 11 22:46:33 2013 -0400 @@ -71,7 +71,7 @@ homography = np.array([]) if '-p' in options.keys(): - worldPts, videoPts = cvutils.loadPointCorrespondences(args[0]) + worldPts, videoPts = cvutils.loadPointCorrespondences(options['-p']) homography, mask = cv2.findHomography(videoPts, worldPts) # method=0, ransacReprojThreshold=3 elif '-i' in options.keys() and '-w' in options.keys(): nPoints = 4
--- a/python/cvutils.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/cvutils.py Thu Apr 11 22:46:33 2013 -0400 @@ -24,9 +24,11 @@ cvGreen, cvBlue]) -cvKeyNumbers = {'a':1048673, - 'n': 1048686, - 'y': 1048697} +def quitKey(key): + return chr(key&255)== 'q' or chr(key&255) == 'Q' + +def saveKey(key): + return chr(key&255) == 's' def drawLines(filename, origins, destinations, w = 1, resultFilename='image.png'): '''Draws lines over the image ''' @@ -59,14 +61,6 @@ points = loadtxt(filename, dtype=float32) return (points[:2,:].T, points[2:,:].T) # (world points, image points) -def computeHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=0.0): - '''Returns the homography matrix mapping from srcPoints to dstPoints (dimension Nx2)''' - #cvSrcPoints = arrayToCvMat(srcPoints); - #cvDstPoints = arrayToCvMat(dstPoints); - #H = cv.CreateMat(3, 3, cv.CV_64FC1) - H, mask = cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold) - return H - def cvMatToArray(cvmat): '''Converts an OpenCV CvMat to numpy array.''' from numpy.core.multiarray import zeros @@ -77,6 +71,11 @@ return a if opencvExists: + def computeHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=0.0): + '''Returns the homography matrix mapping from srcPoints to dstPoints (dimension Nx2)''' + H, mask = cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold) + return H + def arrayToCvMat(a, t = cv2.cv.CV_64FC1): '''Converts a numpy array to an OpenCV CvMat, with default type CV_64FC1.''' cvmat = cv2.cv.CreateMat(a.shape[0], a.shape[1], t) @@ -92,21 +91,24 @@ for i in range(0, last-1): cv2.line(img, positions[i].asint().astuple(), positions[i+1].asint().astuple(), color) - def playVideo(filename, firstFrameNum = 0): + def playVideo(filename, firstFrameNum = 0, frameRate = -1): '''Plays the video''' + wait = 5 + if frameRate > 0: + wait = int(round(1000./frameRate)) capture = cv2.VideoCapture(filename) if capture.isOpened(): key = -1 ret = True frameNum = firstFrameNum capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum) - while ret and key!= 113: # 'q' + while ret and not quitKey(key): ret, img = capture.read() if ret: print('frame {0}'.format(frameNum)) frameNum+=1 cv2.imshow('frame', img) - key = cv2.waitKey(5) + key = cv2.waitKey(wait) def getImagesFromVideo(filename, nImages = 1, saveImage = False): '''Returns nImages images from the video sequence''' @@ -142,7 +144,7 @@ lastFrameNum = maxint else: lastFrameNum = lastFrameNumArg - while ret and key!= 113 and frameNum < lastFrameNum: # 'q' + while ret and not quitKey(key) and frameNum < lastFrameNum: ret, img = capture.read() if ret: print('frame {0}'.format(frameNum)) @@ -157,7 +159,7 @@ cv2.putText(img, '{0}'.format(obj.num), obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.FONT_HERSHEY_PLAIN, 1, cvRed) cv2.imshow('frame', img) key = cv2.waitKey() - if key == 115: + if saveKey(key): cv2.imwrite('image.png', img) frameNum += 1
--- a/python/display-trajectories.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/display-trajectories.py Thu Apr 11 22:46:33 2013 -0400 @@ -4,30 +4,47 @@ import storage import cvutils +from utils import FakeSecHead from numpy.linalg.linalg import inv from numpy import loadtxt +from ConfigParser import ConfigParser options, args = getopt.getopt(sys.argv[1:], 'hi:d:t:o:f:',['help']) # alternative long names are a pain to support ,'video-filename=','database-filename=', 'type=' -# todo parse the cfg file (problem, python ConfigParser needs section headers) + options = dict(options) -if '--help' in options.keys() or '-h' in options.keys() or len(sys.argv) == 1 or not '-i' in options.keys() or not '-d' in options.keys(): - print('Usage: {0} --help|-h -i video-filename -d database-filename [-t object_type] [-o image2world_homography] [-f first_frame]'.format(sys.argv[0])) +print options, args + +if '--help' in options.keys() or '-h' in options.keys() or len(sys.argv) == 1: + print('Usage: '+sys.argv[0]+' --help|-h -i video-filename -d database-filename [-t object_type] [-o image2world_homography] [-f first_frame]\n' + +'Or : '+sys.argv[0]+' [-t object_type] config_file.cfg\n\n' + 'Order matters between positional and named arguments\n' + 'object_type can be feature or object') sys.exit() objectType = 'feature' if '-t' in options.keys(): objectType = options['-t'] -objects = storage.loadTrajectoriesFromSqlite(options['-d'], objectType) +if len(args)>0: # consider there is a configuration file + config = ConfigParser() + config.readfp(FakeSecHead(open(args[0]))) + sectionHeader = config.sections()[0] + videoFilename = config.get(sectionHeader, 'video-filename') + databaseFilename = config.get(sectionHeader, 'database-filename') + homography = inv(loadtxt(config.get(sectionHeader, 'homography-filename'))) + firstFrameNum = config.getint(sectionHeader, 'frame1') +else: + videoFilename = options['-i'] + databaseFilename = options['-d'] + homography = None + if '-o' in options.keys(): + homography = inv(loadtxt(options['-o'])) + firstFrameNum = 0 + if '-f' in options.keys(): + firstFrameNum = int(options['-f']) -homography = None -if '-o' in options.keys(): - homography = inv(loadtxt(options['-o'])) -firstFrameNum = 0 -if '-f' in options.keys(): - firstFrameNum = int(options['-f']) - -cvutils.displayTrajectories(options['-i'], objects, homography, firstFrameNum) +objects = storage.loadTrajectoriesFromSqlite(databaseFilename, objectType) +cvutils.displayTrajectories(videoFilename, objects, homography, firstFrameNum)
--- a/python/event.py Tue Dec 25 02:24:21 2012 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -#! /usr/bin/env python -'''Libraries for events -Interactions, pedestrian crossing...''' - -#import utils; -import moving - -__metaclass__ = type - -class Interaction(moving.STObject): - '''Class for an interaction between two road users - or a road user and an obstacle - - link to the moving objects - ''' - - categories = {'headon': 0, - 'rearend': 1, - 'side': 2, - 'parallel': 3} - - def __init__(self, num = None, timeInterval = None, roaduserNum1 = None, roaduserNum2 = None, movingObject1 = None, movingObject2 = None, categoryNum = None): - moving.STObject.__init__(self, num, timeInterval) - self.roaduserNumbers = set([roaduserNum1, roaduserNum2]) - self.movingObject1 = movingObject1 - self.movingObject2 = movingObject2 - self.categoryNum = categoryNum - - def getIndicator(self, indicatorName): - if hasattr(self, 'indicators'): - for i in self.indicators: - if i.name == indicatorName: - return i - else: - return None - - def computeIndicators(self): - '''Computes the collision course cosine only if the cosine is positive''' - collisionCourseDotProduct = [0]*int(self.timeInterval.length()) - collisionCourseCosine = {} - distances = [0]*int(self.timeInterval.length()) - for i,instant in enumerate(self.timeInterval): - deltap = self.movingObject1.getPositionAtInstant(instant)-self.movingObject2.getPositionAtInstant(instant) - deltav = self.movingObject2.getVelocityAtInstant(instant)-self.movingObject1.getVelocityAtInstant(instant) - collisionCourseDotProduct[i] = moving.Point.dot(deltap, deltav) - distances[i] = deltap.norm2() - if collisionCourseDotProduct[i] > 0: - collisionCourseCosine[instant] = collisionCourseDotProduct[i]/(distances[i]*deltav.norm2()) - self.indicators = [moving.SeverityIndicator('Collision Course Dot Product', collisionCourseDotProduct, self.timeInterval), - moving.SeverityIndicator('Distances', distances, self.timeInterval), - moving.SeverityIndicator('Collision Course Cosine', collisionCourseCosine)] - -def createInteractions(objects): - '''Create all interactions of two co-existing road users - - todo add test to compute categories?''' - interactions = [] - num = 0 - for i in xrange(len(objects)): - for j in xrange(i): - commonTimeInterval = objects[i].commonTimeInterval(objects[j]) - if not commonTimeInterval.empty(): - interactions.append(Interaction(num, commonTimeInterval, objects[i].num, objects[j].num, objects[i], objects[j])) - num += 1 - return interactions - -class Crossing(moving.STObject): - '''Class for the event of a street crossing - - TODO: detecter passage sur la chaussee - identifier origines et destination (ou uniquement chaussee dans FOV) - carac traversee - detecter proximite veh (retirer si trop similaire simultanement - carac interaction''' - - def __init__(self, roaduserNum = None, num = None, timeInterval = None): - moving.STObject.__init__(self, num, timeInterval) - self.roaduserNum = roaduserNum - - - -if __name__ == "__main__": - import doctest - import unittest - #suite = doctest.DocFileSuite('tests/moving.txt') - suite = doctest.DocTestSuite() - unittest.TextTestRunner().run(suite) -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/events.py Thu Apr 11 22:46:33 2013 -0400 @@ -0,0 +1,235 @@ +#! /usr/bin/env python +'''Libraries for events +Interactions, pedestrian crossing...''' + +import numpy as np +from numpy import arccos + +import multiprocessing +import itertools + +import moving +import prediction +import indicators + +__metaclass__ = type + +class Interaction(moving.STObject): + '''Class for an interaction between two road users + or a road user and an obstacle + + link to the moving objects + contains the indicators in a dictionary with the names as keys + ''' + + categories = {'headon': 0, + 'rearend': 1, + 'side': 2, + 'parallel': 3} + + def __init__(self, num = None, timeInterval = None, roaduserNum1 = None, roaduserNum2 = None, movingObject1 = None, movingObject2 = None, categoryNum = None): + moving.STObject.__init__(self, num, timeInterval) + self.roaduserNumbers = set([roaduserNum1, roaduserNum2]) + self.movingObject1 = movingObject1 + self.movingObject2 = movingObject2 + self.categoryNum = categoryNum + self.indicators = {} + + def getIndicator(self, indicatorName): + return self.indicators[indicatorName] + + def addIndicator(self, indicator): + self.indicators[indicator.name] = indicator + + def computeIndicators(self): + '''Computes the collision course cosine only if the cosine is positive''' + collisionCourseDotProducts = {}#[0]*int(self.timeInterval.length()) + collisionCourseCosines = {} + distances = {}#[0]*int(self.timeInterval.length()) + speedDifferentials = {} + for instant in self.timeInterval: + deltap = self.movingObject1.getPositionAtInstant(instant)-self.movingObject2.getPositionAtInstant(instant) + deltav = self.movingObject2.getVelocityAtInstant(instant)-self.movingObject1.getVelocityAtInstant(instant) + collisionCourseDotProducts[instant] = moving.Point.dot(deltap, deltav) + distances[instant] = deltap.norm2() # todo compute closest feature distance, if features + speedDifferentials[instant] = deltav.norm2() + if collisionCourseDotProducts[instant] > 0: + collisionCourseCosines[instant] = arccos(collisionCourseDotProducts[instant]/(distances[instant]*speedDifferentials[instant])) + + # todo shorten the time intervals based on the interaction definition + self.addIndicator(indicators.SeverityIndicator('Collision Course Dot Product', collisionCourseDotProducts)) + self.addIndicator(indicators.SeverityIndicator('Distance', distances)) + self.addIndicator(indicators.SeverityIndicator('Speed Differential', speedDifferentials)) + self.addIndicator(indicators.SeverityIndicator('Collision Course Cosine', collisionCourseCosines)) + + # todo test for interaction instants and interval, compute indicators + + def addVideoFilename(self,videoFilename): + self.videoFilename= videoFilename + + def addInteractionType(self,interactionType): + ''' interaction types: conflict or collision if they are known''' + self.interactionType= interactionType + +def createInteractions(objects): + '''Create all interactions of two co-existing road users + + todo add test to compute categories?''' + interactions = [] + num = 0 + for i in xrange(len(objects)): + for j in xrange(i): + commonTimeInterval = objects[i].commonTimeInterval(objects[j]) + if not commonTimeInterval.empty(): + interactions.append(Interaction(num, commonTimeInterval, objects[i].num, objects[j].num, objects[i], objects[j])) + num += 1 + return interactions + + +# TODO: +#http://stackoverflow.com/questions/3288595/multiprocessing-using-pool-map-on-a-function-defined-in-a-class +#http://www.rueckstiess.net/research/snippets/show/ca1d7d90 +def calculateIndicatorPipe(pairs, predParam, timeHorizon=75,collisionDistanceThreshold=1.8): + collisionPoints, crossingZones = prediction.computeCrossingsCollisions(pairs.movingObject1, pairs.movingObject2, predParam, collisionDistanceThreshold, timeHorizon) + #print pairs.num + # Ignore empty collision points + empty = 1 + for i in collisionPoints: + if(collisionPoints[i] != []): + empty = 0 + if(empty == 1): + pairs.hasCP = 0 + else: + pairs.hasCP = 1 + pairs.CP = collisionPoints + + # Ignore empty crossing zones + empty = 1 + for i in crossingZones: + if(crossingZones[i] != []): + empty = 0 + if(empty == 1): + pairs.hasCZ = 0 + else: + pairs.hasCZ = 1 + pairs.CZ = crossingZones + return pairs + +def calculateIndicatorPipe_star(a_b): + """Convert `f([1,2])` to `f(1,2)` call.""" + return calculateIndicatorPipe(*a_b) + +class VehPairs(): + '''Create a veh-pairs object from objects list''' + def __init__(self,objects): + self.pairs = createInteractions(objects) + self.interactionCount = 0 + self.CPcount = 0 + self.CZcount = 0 + + # Process indicator calculation with support for multi-threading + def calculateIndicators(self,predParam,threads=1,timeHorizon=75,collisionDistanceThreshold=1.8): + if(threads > 1): + pool = multiprocessing.Pool(threads) + self.pairs = pool.map(calculateIndicatorPipe_star, itertools.izip(self.pairs, itertools.repeat(predParam))) + pool.close() + else: + #prog = Tools.ProgressBar(0, len(self.pairs), 77) #Removed in traffic-intelligenc port + for j in xrange(len(self.pairs)): + #prog.updateAmount(j) #Removed in traffic-intelligenc port + collisionPoints, crossingZones = prediction.computeCrossingsCollisions(self.pairs[j].movingObject1, self.pairs[j].movingObject2, predParam, collisionDistanceThreshold, timeHorizon) + + # Ignore empty collision points + empty = 1 + for i in collisionPoints: + if(collisionPoints[i] != []): + empty = 0 + if(empty == 1): + self.pairs[j].hasCP = 0 + else: + self.pairs[j].hasCP = 1 + self.pairs[j].CP = collisionPoints + + # Ignore empty crossing zones + empty = 1 + for i in crossingZones: + if(crossingZones[i] != []): + empty = 0 + if(empty == 1): + self.pairs[j].hasCZ = 0 + else: + self.pairs[j].hasCZ = 1 + self.pairs[j].CZ = crossingZones + + for j in self.pairs: + self.interactionCount = self.interactionCount + len(j.CP) + self.CPcount = len(self.getCPlist()) + self.Czcount = len(self.getCZlist()) + + + def getPairsWCP(self): + lists = [] + for j in self.pairs: + if(j.hasCP): + lists.append(j.num) + return lists + + def getPairsWCZ(self): + lists = [] + for j in self.pairs: + if(j.hasCZ): + lists.append(j.num) + return lists + + def getCPlist(self,indicatorThreshold=99999): + lists = [] + for j in self.pairs: + if(j.hasCP): + for k in j.CP: + if(j.CP[k] != [] and j.CP[k][0].indicator < indicatorThreshold): + lists.append([k,j.CP[k][0]]) + return lists + + def getCZlist(self,indicatorThreshold=99999): + lists = [] + for j in self.pairs: + if(j.hasCZ): + for k in j.CZ: + if(j.CZ[k] != [] and j.CZ[k][0].indicator < indicatorThreshold): + lists.append([k,j.CZ[k][0]]) + return lists + + def genIndicatorHistogram(self, CPlist=False, bins=range(0,100,1)): + if(not CPlist): + CPlist = self.getCPlist() + if(not CPlist): + return False + TTC_list = [] + for i in CPlist: + TTC_list.append(i[1].indicator) + histo = np.histogram(TTC_list,bins=bins) + histo += (histo[0].astype(float)/np.sum(histo[0]),) + return histo + +class Crossing(moving.STObject): + '''Class for the event of a street crossing + + TODO: detecter passage sur la chaussee + identifier origines et destination (ou uniquement chaussee dans FOV) + carac traversee + detecter proximite veh (retirer si trop similaire simultanement + carac interaction''' + + def __init__(self, roaduserNum = None, num = None, timeInterval = None): + moving.STObject.__init__(self, num, timeInterval) + self.roaduserNum = roaduserNum + + + +if __name__ == "__main__": + import doctest + import unittest + #suite = doctest.DocFileSuite('tests/moving.txt') + suite = doctest.DocTestSuite() + unittest.TextTestRunner().run(suite) +
--- a/python/indicators.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/indicators.py Thu Apr 11 22:46:33 2013 -0400 @@ -4,7 +4,7 @@ __metaclass__ = type import moving -from utils import LCSS + # need for a class representing the indicators, their units, how to print them in graphs... class TemporalIndicator: '''Class for temporal indicators @@ -18,7 +18,7 @@ def __init__(self, name, values, timeInterval=None, maxValue = None): self.name = name - self.isCosine = name.find('Cosine') + self.isCosine = (name.find('Cosine') >= 0) if timeInterval: assert len(values) == timeInterval.length() self.timeInterval = timeInterval @@ -34,15 +34,26 @@ self.timeInterval = moving.TimeInterval() self.maxValue = maxValue + def __len__(self): + return len(self.values) + def empty(self): return len(self.values) == 0 def __getitem__(self, i): + 'Returns ith value in time interval' if i in self.values.keys(): return self.values[i] else: return None + def getIthValue(self, i): + sortedKeys = sorted(self.values.keys()) + if 0<=i<len(sortedKeys): + return self.values[sortedKeys[i]] + else: + return None + def __iter__(self): self.iterInstantNum = 0 # index in the interval or keys of the dict return self @@ -53,13 +64,13 @@ raise StopIteration else: self.iterInstantNum += 1 - return self.values[self.values.keys()[self.iterInstantNum-1]] + return self.getIthValue(self.iterInstantNum-1) def getTimeInterval(self): return self.timeInterval def getValues(self): - return self.values.values() + return [self.__getitem__(t) for t in self.timeInterval] def getAngleValues(self): '''if the indicator is a function of an angle, @@ -67,7 +78,7 @@ (no transformation otherwise)''' from numpy import arccos values = self.getValues() - if self.isCosine >= 0: + if self.isCosine: return [arccos(c) for c in values] else: return values @@ -84,13 +95,15 @@ ylim(ymax = self.maxValue) def valueSorted(self): - ''' return the values after sort the keys in the indicator''' + ''' return the values after sort the keys in the indicator + This should probably not be used: to delete''' values=[] keys = self.values.keys() keys.sort() for key in keys: values.append(self.values[key]) return values + @staticmethod def getDLCSS(TemporalIndicator1,TemporalIndicator2, threshold, delta= np.inf , method='min' ): ''' compute the distance between two indicators using LCSS @@ -104,25 +117,43 @@ DLCSS= 1- ((LCSS(l1,l2, threshold, delta, distance))/average) return DLCSS + +def computeDLCSS(indicator1, indicator2, threshold, delta = float('inf'), method= 'min'): + ''' compute the distance between two indicators using LCSS + two common methods are used: min or mean of the indicators length''' + from utils import LCSS + + def distance(x, y): # lambda x,y:abs(x-y) + if x == None or y == None: + return float('inf') + else: + return abs(x-y) + + lcss = LCSS(indicator1.getValues(), indicator2.getValues(), threshold, distance, delta) + if method == 'min': + denominator = min(len(indicator1), len(indicator2)) + elif method == 'mean': + denominator = float(len(indicator1) + len(indicator2))/2 + else: + print('Unknown denominator method name') + denominator = 1. + return 1-float(lcss)/denominator + class SeverityIndicator(TemporalIndicator): '''Class for severity indicators field mostSevereIsMax is True if the most severe value taken by the indicator is the maximum''' - def __init__(self, name, values, timeInterval=None, mostSevereIsMax=True, ignoredValue = None, maxValue = None): + def __init__(self, name, values, timeInterval=None, mostSevereIsMax=True, maxValue = None): TemporalIndicator.__init__(self, name, values, timeInterval, maxValue) self.mostSevereIsMax = mostSevereIsMax - self.ignoredValue = ignoredValue - def getMostSevereValue(self, minNInstants=1): + def getMostSevereValue(self, minNInstants=1): # TODO use scoreatpercentile from matplotlib.mlab import find from numpy.core.multiarray import array from numpy.core.fromnumeric import mean values = array(self.values.values()) - if self.ignoredValue: - indices = find(values != self.ignoredValue) - else: - indices = range(len(values)) + indices = range(len(values)) if len(indices) >= minNInstants: values = sorted(values[indices], reverse = self.mostSevereIsMax) # inverted if most severe is max -> take the first values return mean(values[:minNInstants])
--- a/python/ml.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/ml.py Thu Apr 11 22:46:33 2013 -0400 @@ -58,10 +58,9 @@ return centroids - -def spectralClustering(similarityMatrix,k): - ''' Steps of Spectral Clustering''' - n= len(similarityMatrix) +def spectralClustering(similarityMatrix, k, iter=20): + '''Spectral Clustering algorithm''' + n = len(similarityMatrix) # create Laplacian matrix rowsum = np.sum(similarityMatrix,axis=0) D = np.diag(1 / np.sqrt(rowsum)) @@ -75,6 +74,6 @@ # k-means from scipy.cluster.vq import kmeans, whiten, vq features = whiten(features) - centroids,distortion = kmeans(features,k,iter=20) # default iter = 20 + centroids,distortion = kmeans(features,k, iter) code,distance = vq(features,centroids) # code starting from 0 (represent first cluster) to k-1 (last cluster) return code,sigma
--- a/python/moving.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/moving.py Thu Apr 11 22:46:33 2013 -0400 @@ -10,10 +10,9 @@ __metaclass__ = type -class Interval: - '''Generic Interval''' +class Interval(object): + '''Generic interval: a subset of real numbers (not iterable)''' def __init__(self, first=0, last=-1, revert = False): - 'Warning, do not revert if last<first, it contradicts the definition of empty' if revert and last<first: self.first=last self.last=first @@ -44,19 +43,21 @@ return (self.first<=instant and self.last>=instant) def inside(self, interval2): - 'indicates if the temporal interval of self is comprised in interval2' + '''Indicates if the temporal interval of self is comprised in interval2''' return (self.first >= interval2.first) and (self.last <= interval2.last) - def union(self, interval2): + @classmethod + def union(cls, interval1, interval2): '''Smallest interval comprising self and interval2''' - return Interval(min(self.first, interval2.first), max(self.last, interval2.last)) + return cls(min(interval1.first, interval2.first), max(interval2.last, interval2.last)) - def intersection(self, interval2): + @classmethod + def intersection(cls, interval1, interval2): '''Largest interval comprised in both self and interval2''' - return Interval(max(self.first, interval2.first), min(self.last, interval2.last)) + return cls(max(interval1.first, interval2.first), min(interval1.last, interval2.last)) def distance(self, interval2): - if not self.intersection(interval2).empty(): + if not Interval.intersection(self, interval2).empty(): return 0 elif self.first > interval2.last: return self.first - interval2.last @@ -70,16 +71,22 @@ 'returns the smallest interval containing all intervals' inter = intervals[0] for i in intervals[1:]: - inter = inter.union(i) + inter = Interval.union(inter, i) return inter class TimeInterval(Interval): - '''Temporal interval based on frame numbers (hence the modified length method) - may be modified directly by setting first and last''' + '''Temporal interval: set of instants at fixed time step, between first and last, included + + For example: based on frame numbers (hence the modified length method) + It may be modified directly by setting first and last''' def __init__(self, first=0, last=-1): - Interval.__init__(self, first, last, False) + super(TimeInterval, self).__init__(first, last, False) + + @staticmethod + def fromInterval(inter): + return TimeInterval(inter.first, inter.last) def __getitem__(self, i): if not self.empty(): @@ -106,11 +113,11 @@ # ''' # We will use the polygon class of Shapely -class STObject: - '''Class for spatio-temporal object - i.e. with temporal and spatial existence +class STObject(object): + '''Class for spatio-temporal object, i.e. with temporal and spatial existence (time interval and bounding polygon for positions (e.g. rectangle)). - It does not mean that the object is defined + + It may not mean that the object is defined for all time instants within the time interval''' def __init__(self, num = None, timeInterval = None, boundingPolygon = None): @@ -137,9 +144,9 @@ return self.timeInterval.contains(t) def commonTimeInterval(self, obj2): - return self.getTimeInterval().intersection(obj2.getTimeInterval()) + return TimeInterval.intersection(self.getTimeInterval(), obj2.getTimeInterval()) -class Point: +class Point(object): def __init__(self, x, y): self.x = x self.y = y @@ -168,7 +175,7 @@ return self.x*self.x+self.y*self.y def norm2(self): - '2-norm distance (Euclidean distance)' + '''2-norm distance (Euclidean distance)''' return sqrt(self.norm2Squared()) def norm1(self): @@ -234,8 +241,8 @@ from matplotlib.pyplot import scatter scatter([p.x for p in points],[p.y for p in points], c=color) -class NormAngle: - 'alternate encoding of a point, by its norm and orientation' +class NormAngle(object): + '''Alternate encoding of a point, by its norm and orientation''' def __init__(self, norm, angle): self.norm = norm @@ -273,7 +280,7 @@ return predictedPosition, predictedSpeedTheta -class FlowVector: +class FlowVector(object): '''Class to represent 4-D flow vectors, ie a position and a velocity''' def __init__(self, position, velocity): @@ -301,8 +308,7 @@ from numpy import matrix from numpy.linalg import linalg, det - if (Interval(p1.x,p2.x, True).intersection(Interval(p3.x,p4.x, True)).empty() - or Interval(p1.y,p2.y, True).intersection(Interval(p3.y,p4.y, True)).empty()): + 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()): return None else: dp1 = p2-p1#[s1[0][1]-s1[0][0], s1[1][1]-s1[1][0]] @@ -327,11 +333,10 @@ # TODO: implement a better algorithm for intersections of sets of segments http://en.wikipedia.org/wiki/Line_segment_intersection -class Trajectory: - '''Class for trajectories - i.e. a temporal sequence of positions +class Trajectory(object): + '''Class for trajectories: temporal sequence of positions - the class is iterable.''' + The class is iterable''' def __init__(self, positions=None): if positions != None: @@ -526,22 +531,21 @@ userType2Num = utils.inverseEnumeration(userTypeNames) class MovingObject(STObject): - '''Class for moving objects - i.e. with a trajectory and a geometry (volume) (constant) - and a usertype (e.g. road user) + '''Class for moving objects: a spatio-temporal object + with a trajectory and a geometry (constant volume over time) and a usertype (e.g. road user) ''' def __init__(self, num = None, timeInterval = None, positions = None, geometry = None, userType = None): - STObject.__init__(self, num, timeInterval) + super(MovingObject, self).__init__(num, timeInterval) self.positions = positions self.geometry = geometry self.userType = userType # compute bounding polygon from trajectory - + def getObjectInTimeInterval(self, inter): '''Returns a new object extracted from self, restricted to time interval inter''' - intersection = inter.intersection(self.getTimeInterval()) + intersection = TimeInterval.intersection(inter, self.getTimeInterval()) if not intersection.empty(): trajectoryInterval = TimeInterval(intersection.first-self.getFirstInstant(), intersection.last-self.getFirstInstant()) obj = MovingObject(self.num, intersection, self.positions.getTrajectoryInInterval(trajectoryInterval), self.geometry, self.userType) @@ -637,4 +641,3 @@ unittest.TextTestRunner().run(suite) #doctest.testmod() #doctest.testfile("example.txt") -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/play-video.py Thu Apr 11 22:46:33 2013 -0400 @@ -0,0 +1,22 @@ +#! /usr/bin/env python + +import sys,getopt +import cvutils + +options, args = getopt.getopt(sys.argv[1:], 'hi:f:',['help', 'fps=']) +options = dict(options) +print options + +if '--help' in options.keys() or '-h' in options.keys() or len(sys.argv) == 1: + print('Usage: '+sys.argv[0]+' --help|-h -i video-filename [-f first_frame] [--fps frame_rate]') + sys.exit() + +firstFrameNum = 0 +if '-f' in options.keys(): + firstFrameNum = int(options['-f']) + +frameRate = -1 +if '--fps' in options.keys(): + frameRate = int(options['--fps']) + +cvutils.playVideo(options['-i'], firstFrameNum, frameRate)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/poly_utils.py Thu Apr 11 22:46:33 2013 -0400 @@ -0,0 +1,49 @@ +#! /usr/bin/env python +'''Various utilities to load data saved by the POLY new output(s)''' +import sys +import utils +from moving import TimeInterval +import numpy as np + +__metaclass__ = type + +# inputs variables +#dirname= 'G:/0-phdstart/Code/trial/indicatorsNew/' +#extension= '-indicatorsNew.csv' +#indicatorsNames= {1:'Distance',2:'Cosine',3:'collision Course Angle',4:'Velocity Cosine',5:'Velocity Angle',6:'Speed Differential',7:'Collision Probability',8:'Severity Index',9:'TTC'} +''' min Distance case''' +dirname= 'G:/0-phdstart/Code/trial/minDistanceIndicator/' +extension= '-minDistanceInd.csv' +indicatorsNames= {1:'minDistance'} + +def loadNewInteractions(videoFilename,interactionType, roaduserNum1,roaduserNum2, selectedIndicators=[]): + '''Loads interactions from the POLY traffic event format''' + from events import Interaction + from indicators import SeverityIndicator + #filename= dirname + videoFilename + extension + filename= dirname + interactionType+ '-' + videoFilename + extension # case of min distance todo: change the saving format to be matched with all outputs + file = utils.openCheck(filename) + if (not file): + return [] + interactions = [] + interactionNum = 0 + data= np.loadtxt(filename) + indicatorFrameNums= data[:,0] + inter = Interaction(interactionNum, TimeInterval(indicatorFrameNums[0],indicatorFrameNums[-1]), roaduserNum1, roaduserNum2) + inter.addVideoFilename(videoFilename) + inter.addInteractionType(interactionType) + for key in indicatorsNames.keys(): + values= {} + for i,t in enumerate(indicatorFrameNums): + values[t] = data[i,key] + inter.addIndicator(SeverityIndicator(indicatorsNames[key], values)) + if selectedIndicators !=[]: + values= {} + for i,t in enumerate(indicatorFrameNums): + values[t] = [data[i,index] for index in selectedIndicators] + inter.addIndicator(SeverityIndicator('selectedIndicators', values)) + + interactions.append(inter) + file.close() + return interactions +
--- a/python/prediction.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/prediction.py Thu Apr 11 22:46:33 2013 -0400 @@ -15,8 +15,8 @@ self.probability = 0. self.predictedPositions = {} self.predictedSpeedOrientations = {} - self.collisionPoints = {} - self.crossingZones = {} + #self.collisionPoints = {} + #self.crossingZones = {} def predictPosition(self, nTimeSteps): if nTimeSteps > 0 and not nTimeSteps in self.predictedPositions.keys(): @@ -167,16 +167,20 @@ self.probability = probability self.indicator = indicator + def __str__(self): + return '{0} {1} {2} {3}'.format(self.x, self.y, self.probability, self.indicator) + @staticmethod - def save(out, points, objNum1, objNum2, instant): + def save(out, points, predictionInstant, objNum1, objNum2): for p in points: - out.write('{0} {1} {2} {3} {4} {5} {6}\n'.format(objNum1, objNum2, instant, p.x, p.y, p.probability, p.indicator)) + out.write('{0} {1} {2} {3}\n'.format(objNum1, objNum2, predictionInstant, p)) def computeExpectedIndicator(points): from numpy import sum return sum([p.indicator*p.probability for p in points])/sum([p.probability for p in points]) def computeCollisionTime(predictedTrajectory1, predictedTrajectory2, collisionDistanceThreshold, timeHorizon): + '''Computes the first instant at which two predicted trajectories are within some distance threshold''' t = 1 p1 = predictedTrajectory1.predictPosition(t) p2 = predictedTrajectory2.predictPosition(t) @@ -186,12 +190,12 @@ t += 1 return t, p1, p2 -def computeCrossingsCollisionsAtInstant(i, obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, debug = False): +def computeCrossingsCollisionsAtInstant(currentInstant, obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, computeCZ = False, debug = False): '''returns the lists of collision points and crossing zones Check: Predicting all the points together, as if they represent the whole vehicle''' - predictedTrajectories1 = predictionParameters.generatePredictedTrajectories(obj1, i) - predictedTrajectories2 = predictionParameters.generatePredictedTrajectories(obj2, i) + predictedTrajectories1 = predictionParameters.generatePredictedTrajectories(obj1, currentInstant) + predictedTrajectories2 = predictionParameters.generatePredictedTrajectories(obj2, currentInstant) collisionPoints = [] crossingZones = [] @@ -201,7 +205,7 @@ if t <= timeHorizon: collisionPoints.append(SafetyPoint((p1+p2).multiply(0.5), et1.probability*et2.probability, t)) - else: # check if there is a crossing zone + elif computeCZ: # check if there is a crossing zone # TODO? zone should be around the points at which the traj are the closest # look for CZ at different times, otherwise it would be a collision # an approximation would be to look for close points at different times, ie the complementary of collision points @@ -235,7 +239,8 @@ return collisionPoints, crossingZones -def computeCrossingsCollisions(obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, outCP, outCZ, debug = False, timeInterval = None): +def computeCrossingsCollisions(obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, computeCZ = False, debug = False, timeInterval = None): + '''Computes all crossing and collision points at each common instant for two road users. ''' collisionPoints={} crossingZones={} if timeInterval: @@ -243,14 +248,13 @@ else: commonTimeInterval = obj1.commonTimeInterval(obj2) for i in list(commonTimeInterval)[:-1]: # do not look at the 1 last position/velocities, often with errors - print(obj1.num, obj2.num, i) - collisionPoints[i], crossingZones[i] = computeCrossingsCollisionsAtInstant(i, obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, debug) - SafetyPoint.save(outCP, collisionPoints[i], obj1.num, obj2.num, i) - SafetyPoint.save(outCZ, crossingZones[i], obj1.num, obj2.num, i) + collisionPoints[i], crossingZones[i] = computeCrossingsCollisionsAtInstant(i, obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, computeCZ, debug) return collisionPoints, crossingZones -def computeCollisionProbability(obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, out, debug = False, timeInterval = None): +def computeCollisionProbability(obj1, obj2, predictionParameters, collisionDistanceThreshold, timeHorizon, debug = False, timeInterval = None): + '''Computes only collision probabilities + Returns for each instant the collision probability and number of samples drawn''' collisionProbabilities = {} if timeInterval: commonTimeInterval = timeInterval @@ -268,8 +272,7 @@ nCollisions += 1 # take into account probabilities ?? nSamples = float(len(predictedTrajectories1)*len(predictedTrajectories2)) - collisionProbabilities[i] = float(nCollisions)/nSamples - out.write('{0} {1} {2} {3} {4}\n'.format(obj1.num, obj2.num, nSamples, i, collisionProbabilities[i])) + collisionProbabilities[i] = [nSamples, float(nCollisions)/nSamples] if debug: from matplotlib.pyplot import figure, axis, title
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/requirements.txt Thu Apr 11 22:46:33 2013 -0400 @@ -0,0 +1,11 @@ +matplotlib +numpy + +The following libraries are necessary for (sometimes very) specific classes/functions. + +CV functionalities (cvutils.py): opencv +Image functionalities (cvutils.py): Python Image Library + +Machine learning (ml.py): scipy + +Moving object geometry (currently commented) (moving.py) and plotting shapely polygons (utils.py): shapely
--- a/python/tests/indicators.txt Tue Dec 25 02:24:21 2012 -0500 +++ b/python/tests/indicators.txt Thu Apr 11 22:46:33 2013 -0400 @@ -4,17 +4,20 @@ >>> indic1 = TemporalIndicator('bla', [0,3,-4], TimeInterval(4,6)) >>> indic1.empty() False ->>> indic1[5] +>>> indic1.getIthValue(1) 3 ->>> indic1[3] +>>> indic1.getIthValue(3) +>>> indic1[6] +-4 +>>> indic1[7] >>> [v for v in indic1] [0, 3, -4] >>> indic1 = TemporalIndicator('bla', {2:0,4:3,5:-5}) ->>> indic1[4] +>>> indic1.getIthValue(1) 3 ->>> indic1[3] ->>> [v for v in indic1] -[0, 3, -5] +>>> indic1.getIthValue(3) +>>> indic1[2] +0 >>> t1 = Trajectory([[0.5,1.5,2.5],[0.5,3.5,6.5]]) >>> indicatorMap([1,2,3], t1, 1)
--- a/python/tests/prediction.txt Tue Dec 25 02:24:21 2012 -0500 +++ b/python/tests/prediction.txt Thu Apr 11 22:46:33 2013 -0400 @@ -23,3 +23,8 @@ >>> from numpy import max >>> max(et.getPredictedSpeeds()) <= 2. True + +>>> p = moving.Point(3,4) +>>> sp = prediction.SafetyPoint(p, 0.1, 0) +>>> print(sp) +3 4 0.1 0
--- a/python/tests/utils.txt Tue Dec 25 02:24:21 2012 -0500 +++ b/python/tests/utils.txt Thu Apr 11 22:46:33 2013 -0400 @@ -40,3 +40,14 @@ 71.5... >>> values[-1] 6.0 + +>>> stepPlot([3, 5, 7, 8], 1, 10, 0) +([1, 3, 3, 5, 5, 7, 7, 8, 8, 10], [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) + +>>> LCSS(range(5), range(5), 0.1, lambda x,y:abs(x-y)) +5 +>>> LCSS(range(1,5), range(5), 0.1, lambda x,y:abs(x-y)) +4 +>>> LCSS(range(5,10), range(5), 0.1, lambda x,y:abs(x-y)) +0 +
--- a/python/traffic_engineering.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/traffic_engineering.py Thu Apr 11 22:46:33 2013 -0400 @@ -3,6 +3,8 @@ from math import ceil +import prediction + __metaclass__ = type @@ -11,21 +13,34 @@ ######################### class Vehicle: - 'Generic vehicle class' - def __init__(self, position = 0, speed = 0, acceleration = 0, prt = 2.5, leader = None, log=True): - self.position = position - self.speed = speed - self.acceleration = acceleration + '''Generic vehicle class + 1D coordinates for now''' + class PredictedTrajectory1D(prediction.PredictedTrajectory): + def __init__(self, initialPosition, initialSpeed, control, maxSpeed = None): + self.control = control + self.maxSpeed = maxSpeed + self.probability = None + self.predictedPositions = {0: moving.Point(initialPosition)} + self.predictedSpeedOrientations = {0: moving.NormAngle(initialSpeed, 0)} + + def setAcceleration(self, acceleration): + self.control = moving.NormAngle(acceleration, 0) + + def getControl(self): + return self.control + + + def __init__(self, initialPosition = 0, initialSpeed = 0, acceleration = 0, prt = 2.5, leader = None): + self.positions = PredictedTrajectory1D(initialPosition, initialSpeed) + self.prt = prt self.leader = leader - self.log = log - if log: - self.positions = [position] - self.speeds = [speed] - self.accelerations = [acceleration] - # todo add microModel + # todo add microModel (Treiber) - def updatePosition(self, dt): + def setAcceleration(self, acceleration): + self.positions.setAcceleration(acceleration) + + def updatePosition(self, dt): # knowledge of time outside of vehicle ?? speed = self.speed self.speed += self.acceleration*dt self.position += speed*dt @@ -146,6 +161,7 @@ self.equivalents = equivalents self.nLanes = nLanes # unused else: + print('Proportions do not sum to 1') pass def getPCUVolume(self):
--- a/python/ubc_utils.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/ubc_utils.py Thu Apr 11 22:46:33 2013 -0400 @@ -170,7 +170,7 @@ def loadInteractions(filename, nInteractions = -1): 'Loads interactions from the old UBC traffic event format' - from event import Interaction + from events import Interaction from indicators import SeverityIndicator file = utils.openCheck(filename) if (not file): @@ -184,12 +184,12 @@ inter = Interaction(interactionNum, TimeInterval(parsedLine[1],parsedLine[2]), parsedLine[3], parsedLine[4], categoryNum = parsedLine[5]) indicatorFrameNums = [int(n) for n in lines[1].split(' ')] - inter.indicators = [] for indicatorNum,line in enumerate(lines[2:]): values = {} for i,v in enumerate([float(n) for n in line.split(' ')]): - values[indicatorFrameNums[i]] = v - inter.indicators.append(SeverityIndicator(severityIndicatorNames[indicatorNum], values, None, mostSevereIsMax[indicatorNum], ignoredValue[indicatorNum])) + if not ignoredValue[indicatorNum] or v != ignoredValue[indicatorNum]: + values[indicatorFrameNums[i]] = v + inter.addIndicator(SeverityIndicator(severityIndicatorNames[indicatorNum], values, None, mostSevereIsMax[indicatorNum])) interactions.append(inter) interactionNum+=1
--- a/python/utils.py Tue Dec 25 02:24:21 2012 -0500 +++ b/python/utils.py Thu Apr 11 22:46:33 2013 -0400 @@ -58,6 +58,15 @@ # simple statistics ######################### +def confidenceInterval(mean, stdev, nSamples, percentConfidence, printLatex = False): + from math import sqrt + from scipy.stats.distributions import norm + k = round(norm.ppf(0.5+percentConfidence/200., 0, 1)*100)/100. # 1.-(100-percentConfidence)/200. + e = k*stdev/sqrt(nSamples) + if printLatex: + print('${0} \pm {1}\\frac{{{2}}}{{\sqrt{{{3}}}}}$'.format(mean, k, stdev, nSamples)) + return mean-e, mean+e + def computeChi2(expected, observed): '''Returns the Chi2 statistics''' result = 0. @@ -165,22 +174,20 @@ # maths section ######################### -def LCSS(l1, l2, threshold, delta, distance): +def LCSS(l1, l2, threshold, distance, delta = float('inf')): '''returns the longest common subsequence similarity - based on the threshold on distance between two elements of lists l1, l2. - looks for points that are sampled within a delta time window, can put 'inf' value to cancl this condition''' + based on the threshold on distance between two elements of lists l1, l2 + ''' from numpy import zeros, int as npint m = len(l1) n = len(l2) similarity = zeros((m+1,n+1), dtype = npint) for i in xrange(1,m+1): - for j in xrange(1,n+1): - from math import fabs - while fabs(i-j)<= delta: - if distance(l1[i-1], l2[j-1])<threshold: - similarity[i][j] = similarity[i-1][j-1]+1 - else: - similarity[i][j] = max(similarity[i-1][j], similarity[i][j-1]) + for j in xrange(max(1,i-delta),min(n+1,i+delta)): + if distance(l1[i-1], l2[j-1])<=threshold: + similarity[i][j] = similarity[i-1][j-1]+1 + else: + similarity[i][j] = max(similarity[i-1][j], similarity[i][j-1]) return similarity[-1][-1] def framesToTime(nFrames, frameRate, initialTime = (0.,0.,0.)): @@ -247,6 +254,20 @@ # plotting section ######################### +def stepPlot(X, firstX, lastX, initialCount = 0): + '''for each value in x, increment by one the initial count + returns the lists that can be plotted + to obtain a step plot increasing by one for each value in x, from first to last value''' + + sortedX = [] + counts = [initialCount] + for x in sorted(X): + sortedX += [x,x] + counts.append(counts[-1]) + counts.append(counts[-1]+1) + counts.append(counts[-1]) + return [firstX]+sortedX+[lastX], counts + class PlottingPropertyValues: def __init__(self, values): self.values = values @@ -309,6 +330,22 @@ s = readline(f) return dataStrings +class FakeSecHead(object): + '''Add fake section header [asection] + + from http://stackoverflow.com/questions/2819696/parsing-properties-file-in-python/2819788#2819788 + use read_file in Python 3.2+ + ''' + def __init__(self, fp): + self.fp = fp + self.sechead = '[main]\n' + + def readline(self): + if self.sechead: + try: return self.sechead + finally: self.sechead = None + else: return self.fp.readline() + def removeExtension(filename, delimiter = '.'): '''Returns the filename minus the extension (all characters after last .)''' i = filename.rfind(delimiter)