changeset 369:027e254f0b53

lcss subclass for indicators
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Mon, 15 Jul 2013 16:47:09 -0400
parents 2db4e76599a1
children 97e8fa0ee9a1
files python/indicators.py python/moving.py python/tests/moving.txt python/tests/utils.txt python/utils.py
diffstat 5 files changed, 161 insertions(+), 150 deletions(-) [+]
line wrap: on
line diff
--- a/python/indicators.py	Mon Jul 15 15:08:53 2013 -0400
+++ b/python/indicators.py	Mon Jul 15 16:47:09 2013 -0400
@@ -103,57 +103,31 @@
     else:
         return abs(x-y)
 
-# non-aligned LCSS computations, ok for delta = inf
-def computeLCSS(indicator1, indicator2, threshold, delta = float('inf')):
-    ''' compute the LCSS between two indicators using LCSS'''
-    from utils import LCSS
-    if indicator1 and indicator2:
-        return LCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta)
-    else:
-        return 0
+from utils import LCSS as utilsLCSS
 
-def computeNormalizedLCSS(indicator1, indicator2, threshold, delta = float('inf'), method= min):
-    ''' compute the normalized LCSS between two indicators using LCSS
-    ie, the LCSS divided by the min or mean of the indicator lengths'''
-    from utils import normalizedLCSS
-    if indicator1 and indicator2:
-        return normalizedLCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta, method)
-    else:
-        return 0.
+class LCSS(utilsLCSS):
+    '''Adapted LCSS class for indicators, same pattern'''
+    def __init__(self, threshold, delta = float('inf'), aligned = False, lengthFunc = min):
+        utilsLCSS.__init__(self, lambda x,y: (distanceForLCSS(x,y) <= threshold), delta, aligned, lengthFunc)
 
-def computeDLCSS(indicator1, indicator2, threshold, delta = float('inf'), method = min):
-    ''' compute the LCSS distance between two indicators using LCSS'''
-    from utils import DLCSS
-    if indicator1 and indicator2:
-        return DLCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta, method)
-    else:
-        return 1.
+    def get(self, indicator1, indicator2):
+        if indicator1 and indicator2:
+            return self.compute(indicator1.getValues(), indicator2.getValues())
+        else:
+            return 0
 
-# aligned LCSS computations
-def computeAlignedLCSS(indicator1, indicator2, threshold, delta = float('inf')):
-    ''' compute the aligned LCSS between two indicators using LCSS'''
-    from utils import alignedLCSS
-    if indicator1 and indicator2:
-        return alignedLCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta)
-    else:
-        return 0
+    def getNormalized(self, indicator1, indicator2):
+        if indicator1 and indicator2:
+            return self.computeNormalized(indicator1.getValues(), indicator2.getValues())
+        else:
+            return 0.
 
-def computeNormalizedAlignedLCSS(indicator1, indicator2, threshold, delta = float('inf'), method= min):
-    ''' compute the normalized aligned LCSS between two indicators using LCSS
-    ie, the LCSS divided by the min or mean of the indicator lengths'''
-    from utils import normalizedAlignedLCSS
-    if indicator1 and indicator2:
-        return normalizedAlignedLCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta, method)
-    else:
-        return 0.
-
-def computeAlignedDLCSS(indicator1, indicator2, threshold, delta = float('inf'), method = min):
-    ''' compute the aligned LCSS distance between two indicators using LCSS'''
-    from utils import alignedDLCSS
-    if indicator1 and indicator2:
-        return alignedDLCSS(indicator1.getValues(), indicator2.getValues(), lambda x,y: (distanceForLCSS(x,y) <= threshold), delta, method)
-    else:
-        return 1.
+    def getDistance(self, indicator1, indicator2):
+        if indicator1 and indicator2:
+            return self.computeDistance(indicator1.getValues(), indicator2.getValues())
+        else:
+            return 1.
+        
 
 class SeverityIndicator(TemporalIndicator):
     '''Class for severity indicators 
--- a/python/moving.py	Mon Jul 15 15:08:53 2013 -0400
+++ b/python/moving.py	Mon Jul 15 16:47:09 2013 -0400
@@ -539,12 +539,8 @@
     # version 2: use shapely polygon contains
 
     @staticmethod
-    def norm2LCSS(t1, t2, threshold):
-        return utils.LCSS(t1, t2, lambda x,y: Point.distanceNorm2(x,y) <= threshold)
-
-    @staticmethod
-    def normMaxLCSS(t1, t2, threshold):
-        return utils.LCSS(t1, t2, lambda p1, p2: (p1-p2).normMax() <= threshold)
+    def lcss(t1, t2, lcss):
+        return lcss.compute(t1, t2)
 
 class CurvilinearTrajectory(Trajectory):
     '''Sub class of trajectory for trajectories with curvilinear coordinates and lane assignements
--- a/python/tests/moving.txt	Mon Jul 15 15:08:53 2013 -0400
+++ b/python/tests/moving.txt	Mon Jul 15 16:47:09 2013 -0400
@@ -75,9 +75,12 @@
 >>> t1.getTrajectoryInPolygon(np.array([[10,10],[14,10],[14,13],[10,13]])).length()
 0
 
->>> Trajectory.norm2LCSS(t1, t1, 0.1)
+>>> from utils import LCSS
+>>> lcss = LCSS(lambda x,y: Point.distanceNorm2(x,y) <= 0.1)
+>>> Trajectory.lcss(t1, t1, lcss)
 3
->>> Trajectory.normMaxLCSS(t1, t1, 0.1)
+>>> lcss = LCSS(lambda p1, p2: (p1-p2).normMax() <= 0.1)
+>>> Trajectory.lcss(t1, t1, lcss)
 3
 
 >>> o1 = MovingObject(positions = Trajectory([[0]*3,[2]*3]), velocities = Trajectory([[0]*3,[1]*3]))
--- a/python/tests/utils.txt	Mon Jul 15 15:08:53 2013 -0400
+++ b/python/tests/utils.txt	Mon Jul 15 16:47:09 2013 -0400
@@ -44,46 +44,65 @@
 >>> 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), lambda x,y:(abs(x-y) <= 0.1))
+>>> lcss = LCSS(lambda x,y: abs(x-y) <= 0.1)
+>>> lcss.compute(range(5), range(5))
 5
->>> LCSS(range(1,5), range(5), lambda x,y:(abs(x-y) <= 0.1))
+>>> lcss.compute(range(1,5), range(5))
 4
->>> LCSS(range(5,10), range(5), lambda x,y:(abs(x-y) <= 0.1))
+>>> lcss.compute(range(5,10), range(5))
 0
->>> LCSS(range(5), range(10), lambda x,y:(abs(x-y) <= 0.1))
+>>> lcss.compute(range(5), range(10))
 5
->>> LCSS(range(5), range(10), lambda x,y:(abs(x-y) <= 0.1), 2)
+>>> lcss.compute(range(5), range(10), 2)
 5
->>> LCSS(['a','b','c'], ['a','b','c', 'd'], lambda x,y: x == y)
-3
->>> LCSS(['a','b','c'], ['a','b','c', 'd'], lambda x,y: x == y, 2)
+>>> lcss.similarityFunc = lambda x,y: x == y
+>>> lcss.compute(['a','b','c'], ['a','b','c', 'd'])
 3
->>> LCSS(['a','x','b','c'], ['a','b','c','d','x'], lambda x,y: x == y)
+>>> lcss.computeNormalized(['a','b','c'], ['a','b','c', 'd']) #doctest: +ELLIPSIS
+1.0
+>>> lcss.computeNormalized(['a','b','c','x'], ['a','b','c', 'd']) #doctest: +ELLIPSIS
+0.75
+>>> lcss.compute(['a','b','c'], ['a','b','c', 'd'])
 3
->>> LCSS(['a','b','c','x','d'], ['a','b','c','d','x'], lambda x,y: x == y)
+>>> lcss.compute(['a','x','b','c'], ['a','b','c','d','x'])
+3
+>>> lcss.compute(['a','b','c','x','d'], ['a','b','c','d','x'])
 4
->>> LCSS(['a','b','c'], ['a','b','x','x','c'], lambda x,y: x == y, 1)
+>>> lcss.delta = 1
+>>> lcss.compute(['a','b','c'], ['a','b','x','x','c'])
 2
 
->>> LCSS(['a','b','c'], ['a','b','c', 'd'], lambda x,y: x == y, returnSubSequence = True)
-(3, [(0, 0), (1, 1), (2, 2)])
->>> LCSS(['a','b','c'], ['x','a','b','c'], lambda x,y: x == y, returnSubSequence = True)
-(3, [(0, 1), (1, 2), (2, 3)])
->>> LCSS(['a','g','b','c'], ['a','b','c', 'd'], lambda x,y: x == y, returnSubSequence = True)
-(3, [(0, 0), (2, 1), (3, 2)])
+>>> lcss.delta = float('inf')
+>>> lcss.compute(['a','b','c'], ['a','b','c', 'd'], computeSubSequence = True)
+3
+>>> lcss.subSequenceIndices
+[(0, 0), (1, 1), (2, 2)]
+>>> lcss.compute(['a','b','c'], ['x','a','b','c'], computeSubSequence = True)
+3
+>>> lcss.subSequenceIndices
+[(0, 1), (1, 2), (2, 3)]
+>>> lcss.compute(['a','g','b','c'], ['a','b','c', 'd'], computeSubSequence = True)
+3
+>>> lcss.subSequenceIndices
+[(0, 0), (2, 1), (3, 2)]
 
->>> alignedLCSS(range(5), range(5), lambda x,y:(abs(x-y) <= 0.1), 2)
+>>> alignedLcss = LCSS(lambda x,y:(abs(x-y) <= 0.1), delta = 2, aligned = True)
+>>> alignedLcss.compute(range(5), range(5))
 5
->>> alignedLCSS(range(1,5), range(5), lambda x,y:(abs(x-y) <= 0.1), 2)
+>>> alignedLcss.compute(range(1,5), range(5))
 4
 
->>> alignedLCSS(range(5,10), range(10), lambda x,y:(abs(x-y) <= 0.1), 2)
+>>> alignedLcss.compute(range(5,10), range(10))
 5
->>> LCSS(range(5,10), range(10), lambda x,y:(abs(x-y) <= 0.1), 2)
+
+>>> lcss.delta = 2
+>>> lcss.compute(range(5,10), range(10))
 0
->>> alignedLCSS(range(5), range(5), lambda x,y:(abs(x-y) <= 0.1), 6)
+>>> alignedLcss.delta = 6
+>>> alignedLcss.compute(range(5), range(5))
 5
->>> alignedLCSS(range(5), range(6), lambda x,y:(abs(x-y) <= 0.1), 6)
+>>> alignedLcss.compute(range(5), range(6))
 5
->>> alignedLCSS(range(1,7), range(6), lambda x,y:(abs(x-y) <= 0.1), 10)
+>>> lcss.delta = 10
+>>> alignedLcss.compute(range(1,7), range(6))
 5
--- a/python/utils.py	Mon Jul 15 15:08:53 2013 -0400
+++ b/python/utils.py	Mon Jul 15 16:47:09 2013 -0400
@@ -141,86 +141,105 @@
 # sequence section
 #########################
 
-def sequenceSimilarities(l1, l2, similarityFunc, delta = float('inf')):
-    from numpy import zeros, int as npint
-    n1 = len(l1)
-    n2 = len(l2)
-    similarities = zeros((n1+1,n2+1), dtype = npint)
-    for i in xrange(1,n1+1):
-        for j in xrange(max(1,i-delta),min(n2+1,i+delta+1)):
-            if similarityFunc(l1[i-1], l2[j-1]):
-                similarities[i,j] = similarities[i-1,j-1]+1
-            else:
-                similarities[i,j] = max(similarities[i-1,j], similarities[i,j-1])
-    return similarities
+class LCSS:
+    '''Class that keeps the LCSS parameters
+    and puts together the various computations'''
+    def __init__(self, similarityFunc, delta = float('inf'), aligned = False, lengthFunc = min):
+        self.similarityFunc = similarityFunc
+        self.aligned = aligned
+        self.delta = delta
+        self.lengthFunc = lengthFunc
 
-def subSequence(similarities, i, j):
-    '''Returns the subsequence of two sequences
-    http://en.wikipedia.org/wiki/Longest_common_subsequence_problem'''
-    if i == 0 or j == 0:
-        return []
-    elif similarities[i][j] == similarities[i][j-1]:
-        return subSequence(similarities, i, j-1)
-    elif similarities[i][j] == similarities[i-1][j]:
-        return subSequence(similarities, i-1, j)
-    else:
-        return subSequence(similarities, i-1, j-1) + [(i-1,j-1)]
+    def similarities(self, l1, l2):
+        from numpy import zeros, int as npint
+        n1 = len(l1)
+        n2 = len(l2)
+        self.similarityTable = zeros((n1+1,n2+1), dtype = npint)
+        for i in xrange(1,n1+1):
+            for j in xrange(max(1,i-self.delta),min(n2+1,i+self.delta+1)):
+                if self.similarityFunc(l1[i-1], l2[j-1]):
+                    self.similarityTable[i,j] = self.similarityTable[i-1,j-1]+1
+                else:
+                    self.similarityTable[i,j] = max(self.similarityTable[i-1,j], self.similarityTable[i,j-1])
 
-def LCSS(l1, l2, similarityFunc, delta = float('inf'), returnSubSequence = False):
-    '''returns the longest common subsequence similarity
-    based on the threshold on distance between two elements of lists l1, l2
-    similarityFunc returns True or False whether the two points are considered similar
+    def subSequence(self, i, j):
+        '''Returns the subsequence of two sequences
+        http://en.wikipedia.org/wiki/Longest_common_subsequence_problem'''
+        if i == 0 or j == 0:
+            return []
+        elif self.similarityTable[i][j] == self.similarityTable[i][j-1]:
+            return self.subSequence(i, j-1)
+        elif self.similarityTable[i][j] == self.similarityTable[i-1][j]:
+            return self.subSequence(i-1, j)
+        else:
+            return self.subSequence(i-1, j-1) + [(i-1,j-1)]
+
+    def computeLCSS(self, l1, l2, computeSubSequence = False):
+        '''returns the longest common subsequence similarity
+        based on the threshold on distance between two elements of lists l1, l2
+        similarityFunc returns True or False whether the two points are considered similar
 
-    eg distance(p1, p2) < epsilon
-    '''
-    from numpy import argmax
-    similarities = sequenceSimilarities(l1, l2, similarityFunc, delta)
-    imax = argmax(similarities[:,-1])
-    jmax = argmax(similarities[-1,:])
-    if returnSubSequence:
-        if similarities[imax, -1] > similarities[-1, jmax]:
-            lcss = similarities[imax, -1]
-            subseq = subSequence(similarities, imax, len(l2))
+        eg distance(p1, p2) < epsilon
+        '''
+        from numpy import argmax
+        self.similarities(l1, l2)
+        imax = argmax(self.similarityTable[:,-1])
+        jmax = argmax(self.similarityTable[-1,:])
+        if computeSubSequence:
+            if self.similarityTable[imax, -1] > self.similarityTable[-1, jmax]:
+                lcss = self.similarityTable[imax, -1]
+                self.subSequenceIndices = self.subSequence(imax, len(l2))
+            else:
+                lcss = self.similarityTable[imax, -1]
+                self.subSequenceIndices = self.subSequence(len(l1), jmax)
+            return lcss
         else:
-            lcss = similarities[-1, jmax]
-            subseq = subSequence(similarities, len(l1), jmax)
-        return lcss, subseq
-    else:
-        return max(similarities[imax, -1], similarities[-1, jmax])
-
-def normalizedLCSS(l1, l2, similarityFunc, delta = float('inf'), lengthFunc = min):
-    ''' compute the normalized LCSS
-    ie, the LCSS divided by the min or mean of the indicator lengths (using lengthFunc)
-    lengthFunc = lambda x,y:float(x,y)/2'''
-    return float(LCSS(l1, l2, similarityFunc, delta))/lengthFunc(len(l1), len(l2))
+            return max(self.similarityTable[imax, -1], self.similarityTable[-1, jmax])
 
-def DLCSS(l1, l2, similarityFunc, delta = float('inf'), lengthFunc = min):
-    ''' compute the LCSS distance'''
-    return 1-normalizedLCSS(l1, l2, similarityFunc, delta, lengthFunc)
+    def compute(self, _l1, _l2, computeSubSequence = False):
+        '''returns the best matching if using a finite delta by shiftinig the series alignments'''
+        if self.aligned:
+            if len(_l2) < len(_l1): # l1 is the shortest
+                l1 = _l2
+                l2 = _l1
+            else:
+                l1 = _l1
+                l2 = _l2
+            n1 = len(l1)
+            n2 = len(l2)
+            # for i in xrange(min(delta,n1), max(n1+n2-delta, n2+1)): # i is the alignment of the end of l1 in l2
+            #     print l1[min(-i-1,n1):] # min(n1+n2-i,n1)
+            #     print l2[max(0,i-n1):]
+            #     print LCSS(l1[min(-i-1,n1):], l2[max(0,i-n1):], similarityFunc, delta)
+            lcss = [self.computeLCSS(l1[min(-i-1,n1):], l2[max(0,i-n1):]) for i in xrange(min(self.delta,n1), max(n1+n2-self.delta, n2+1))]
+            return max(lcss)
+        else:
+            return self.computeLCSS(_l1, _l2, computeSubSequence)
 
-def alignedLCSS(_l1, _l2, similarityFunc, delta):
-    '''returns the best matching if using a finite delta by shiftinig the series alignments'''
-    if len(_l2) < len(_l1): # l1 is the shortest
-        l1 = _l2
-        l2 = _l1
-    else:
-        l1 = _l1
-        l2 = _l2
-    n1 = len(l1)
-    n2 = len(l2)
-    # for i in xrange(min(delta,n1), max(n1+n2-delta, n2+1)): # i is the alignment of the end of l1 in l2
-    #     print l1[min(-i-1,n1):] # min(n1+n2-i,n1)
-    #     print l2[max(0,i-n1):]
-    #     print LCSS(l1[min(-i-1,n1):], l2[max(0,i-n1):], similarityFunc, delta)
-    lcss = [LCSS(l1[min(-i-1,n1):], l2[max(0,i-n1):], similarityFunc, delta) for i in xrange(min(delta,n1), max(n1+n2-delta, n2+1))]
-    return max(lcss)
+    def get(self, l1, l2, computeSubSequence = False):
+        '''get methods are to be shadowed in child classes '''
+        return self.compute(l1, l2, computeSubSequence)
+
+    def computeAlignement(self):
+        from numpy import mean
+        return mean([j-i for i,j in self.subSequenceIndices])
 
-def normalizedAlignedLCSS(l1, l2, similarityFunc, delta, lengthFunc = min):
-    return float(alignedLCSS(l1, l2, similarityFunc, delta))/lengthFunc(len(l1), len(l2))
+    def computeNormalized(self, l1, l2):
+        ''' compute the normalized LCSS
+        ie, the LCSS divided by the min or mean of the indicator lengths (using lengthFunc)
+        lengthFunc = lambda x,y:float(x,y)/2'''
+        return float(self.compute(l1, l2))/self.lengthFunc(len(l1), len(l2))
 
-def alignedDLCSS(l1, l2, similarityFunc, delta, lengthFunc = min):
-    return 1-normalizedAlignedLCSS(l1, l2, similarityFunc, delta, lengthFunc)
+    def getNormalized(self, l1, l2):
+        return self.computeNormalized(l1, l2)
 
+    def computeDistance(self, l1, l2):
+        ''' compute the LCSS distance'''
+        return 1-self.computeNormalized(l1, l2)
+
+    def getDistance(self, l1, l2):
+        return self.computeDistance(l1, l2)
+    
 #########################
 # maths section
 #########################