changeset 1161:b968c33f8c2f

work on producing figures and collision maps for large datasets with multiple sites
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Thu, 04 Mar 2021 23:12:34 -0500
parents 3a6b00f13e95
children efd52c55a72b
files scripts/process.py trafficintelligence/events.py
diffstat 2 files changed, 74 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/process.py	Mon Feb 22 22:12:19 2021 -0500
+++ b/scripts/process.py	Thu Mar 04 23:12:34 2021 -0500
@@ -24,7 +24,7 @@
 parser.add_argument('--process', dest = 'process', help = 'data to process', choices = ['feature', 'object', 'classification', 'prototype', 'interaction'])
 parser.add_argument('--display', dest = 'display', help = 'data to display (replay over video)', choices = ['feature', 'object', 'classification', 'interaction'])
 parser.add_argument('--progress', dest = 'progress', help = 'information about the progress of processing', action = 'store_true')
-parser.add_argument('--analyze', dest = 'analyze', help = 'data to analyze (results)', choices = ['feature', 'object', 'classification', 'interaction', 'event-speed', 'event-interaction'])
+parser.add_argument('--analyze', dest = 'analyze', help = 'data to analyze (results)', choices = ['feature', 'object', 'classification', 'interaction', 'collision-map', 'event-speed', 'event-interaction'])
 
 # common options
 parser.add_argument('--cfg', dest = 'configFilename', help = 'name of the configuration file')
@@ -61,6 +61,8 @@
 # analysis options
 parser.add_argument('--output', dest = 'output', help = 'kind of output to produce (interval means)', choices = ['figure', 'interval', 'event'])
 parser.add_argument('--min-duration', dest = 'minDuration', help = 'mininum duration we have to see the user or interaction to take into account in the analysis (s)', type = float)
+parser.add_argument('--max-time-indicator-value', dest = 'maxTimeIndicatorValue', help = 'maximum indicator value for time indicators like PET and TTC (s)', type = float)
+parser.add_argument('--max-speed-indicator-value', dest = 'maxSpeedIndicatorValue', help = 'maximum indicator value for speed indicators like individual speed statistics and speed differential (km/h)', type = float)
 parser.add_argument('--interval-duration', dest = 'intervalDuration', help = 'length of time interval to aggregate data (min)', type = int, default = 15)
 parser.add_argument('--aggregation', dest = 'aggMethods', help = 'aggregation method per user/interaction and per interval', choices = ['mean', 'median', 'centile'], nargs = '*', default = ['median'])
 parser.add_argument('--aggregation-centiles', dest = 'aggCentiles', help = 'centile(s) to compute from the observations', nargs = '*', type = int)
@@ -138,7 +140,8 @@
         print('{} video sequences without a camera view:'.format(len(videoSequences)))
         print([vs.idx for vs in videoSequences])
         print('-'*80)
-    print(data)
+    with pd.option_context('display.max_rows', None, 'display.max_columns', None):
+        print(data)
 
 #################################
 # Delete
@@ -284,26 +287,27 @@
 
 if args.analyze == 'interaction': # redo as for object, export in dataframe all interaction data
     indicatorIds = [2,5,7,10]
-    conversionFactors = {2: 1., 5: 30.*3.6, 7:1./30, 10:1./30}
     #maxIndicatorValue = {2: float('inf'), 5: float('inf'), 7:10., 10:10.}
     data = [] # list of observation per site-user with time
     headers = ['site', 'date', 'time', events.Interaction.indicatorNames[10].replace(' ','-')] # user types?
     aggFunctions, tmpheaders = utils.aggregationMethods(args.aggMethods, args.aggCentiles)
+    nAggFunctions = len(tmpheaders)
+    indicatorUnits = [events.Interaction.indicatorUnits[10]] # for PET above
     for i in indicatorIds[:3]:
         for h in tmpheaders:
             headers.append(events.Interaction.indicatorNames[i].replace(' ','-')+'-'+h)
-    indicators = {}
-    interactions = {}
+            indicatorUnits.append(events.Interaction.indicatorUnits[i])
     for vs in videoSequences:
         print('Extracting SMoS from '+vs.getDatabaseFilename())
         interactions = storage.loadInteractionsFromSqlite(str(parentPath/vs.getDatabaseFilename()))
         minDuration = vs.cameraView.cameraType.frameRate*args.minDuration
+        conversionFactors = {2: 1., 5: 3.6*vs.cameraView.cameraType.frameRate, 7:1./vs.cameraView.cameraType.frameRate, 10:1./vs.cameraView.cameraType.frameRate}
         for inter in interactions:
             if inter.length() > minDuration:
                 d = vs.startTime.date()
                 t = vs.startTime.time()
                 row = [vs.cameraView.site.name, d, utils.framesToTime(inter.getFirstInstant(), vs.cameraView.cameraType.frameRate, t)]
-                pet = inter.getIndicator('Post Encroachment Time')
+                pet = inter.getIndicator(events.Interaction.indicatorNames[10])
                 if pet is None:
                     row.append(None)
                 else:
@@ -320,21 +324,74 @@
                             else:
                                 row.append(agg)
                     else:
-                        row.extend([None]*len(aggFunctions))
+                        row.extend([None]*nAggFunctions)
                 data.append(row)
     data = pd.DataFrame(data, columns = headers)
     if args.output == 'figure':
-        for i in indicatorIds:
-            pass # tmp = [indicators[siteId][i] for siteId in indicators]
-            # plt.ioff()
-            # plt.figure()
+        plt.ioff()
+        for i, indic in enumerate(headers[3:]):
+            if 'Time' in indic and args.maxTimeIndicatorValue is not None:
+                tmp = data.loc[data[indic] < args.maxTimeIndicatorValue, ['site', indic]]
+            elif 'Speed' in indic and args.maxSpeedIndicatorValue is not None:
+                tmp = data.loc[data[indic] < args.maxSpeedIndicatorValue, ['site', indic]]
+            else:
+                tmp = data[['site', indic]]
+            plt.figure()
+            tmp.boxplot(indic, 'site')
             # plt.boxplot(tmp, labels = [session.query(Site).get(siteId).name for siteId in indicators])
-            # plt.ylabel(events.Interaction.indicatorNames[i]+' ('+events.Interaction.indicatorUnits[i]+')')
-            # plt.savefig(events.Interaction.indicatorNames[i]+'.png', dpi=150)
-            # plt.close()
+            plt.ylabel(indic+' ('+indicatorUnits[i]+')')
+            plt.savefig('boxplot-sites-'+indic+'.pdf')#, dpi=150)
+            plt.close()
+            plt.figure()
+            for site in sorted(tmp.site.unique()):
+                x = sorted(tmp.loc[tmp.site == site, indic])
+                plt.plot(x, np.arange(1,len(x)+1)/len(x), label=site)
+            plt.legend()
+            plt.title('Cumulative Distribution Function by Site')
+            plt.xlabel(indic+' ('+indicatorUnits[i]+')')
+            plt.savefig('cdf-sites-'+indic+'.pdf')
+            plt.close()
     elif args.output == 'event':
         data.to_csv(args.eventFilename, index = False)
 
+if args.analyze == 'collision-map':
+    predictionParameters = prediction.CVExactPredictionParameters()
+    data = []
+    for vs in videoSequences:
+        print('Extracting potential collision points from '+vs.getDatabaseFilename())
+        interactions = storage.loadInteractionsFromSqlite(str(parentPath/vs.getDatabaseFilename()))
+        objects = storage.loadTrajectoriesFromSqlite(str(parentPath/vs.getDatabaseFilename()), 'object')
+        params = storage.ProcessParameters(str(parentPath/vs.cameraView.getTrackingConfigurationFilename()))
+        minDuration = vs.cameraView.cameraType.frameRate*args.minDuration
+        maxTimeIndicatorValue = vs.cameraView.cameraType.frameRate*args.maxTimeIndicatorValue
+        for inter in interactions:
+            if inter.length() > minDuration:
+                ttc = inter.getIndicator(events.Interaction.indicatorNames[7])
+                if ttc is not None:
+                    t = min(ttc.values, key = ttc.values.get)
+                    if args.maxTimeIndicatorValue is None or ttc.values[t] < maxTimeIndicatorValue:
+                        inter.setRoadUsers(objects)
+                        cps, _ = predictionParameters.computeCrossingsCollisionsAtInstant(t, inter.roadUser1, inter.roadUser2, params.collisionDistance, params.predictionTimeHorizon)
+                        data.append([vs.cameraView.site.name, cps[0].x, cps[0].y, cps[0].indicator])
+    data = pd.DataFrame(data, columns = ['site', 'x', 'y', 'ttc'])
+    margin = 0.1
+    for site in data.site.unique():
+        s = session.query(Site).filter(Site.name.like('%'+site+'%')).first()
+        img = plt.imread(str(parentPath/s.getMapImageFilename()))
+        tmp = data[data.site == site].copy()
+        tmp.x = tmp.x/s.nUnitsPerPixel
+        tmp.y = tmp.y/s.nUnitsPerPixel
+        h, w, _ = img.shape
+        tmp = tmp[(tmp.x>-margin*w) & (tmp.x < (1+margin)*w) & (tmp.y > -margin*h) & (tmp.y < (1+margin)*h)]
+        plt.figure()
+        plt.imshow(img)
+        plt.hexbin(tmp.x, tmp.y, alpha = 0.5, edgecolors = 'face', mincnt=1, gridsize=50)
+        plt.title('Density of Potential Collision Points at Site '+site)
+        plt.colorbar()
+        plt.axis('equal')
+        plt.savefig('collision-map-'+site+'.pdf')
+        #plt.close()
+        
 if args.analyze == 'event-speed': # aggregate event data by 15 min interval (args.intervalDuration), count events with thresholds
     data = pd.read_csv(args.eventFilename, parse_dates = [2])
     #data = pd.read_csv('./speeds.csv', converters = {'time': lambda s: datetime.datetime.strptime(s, "%H:%M:%S").time()}, nrows = 5000)
@@ -377,6 +434,7 @@
     data = pd.read_csv(args.eventFilename, parse_dates = [2])
     headers = ['site', 'date', 'intervalend15', 'duration', 'count']
     aggFunctions, tmpheaders = utils.aggregationMethods(args.aggMethods, args.aggCentiles)
+    nAggFunctions = len(tmpheaders)
     dataColumns = list(data.columns[3:])
     for h in dataColumns:
         if not 'speed' in h.lower(): # proximity indicators are reversed, taking 85th centile of this column will yield the 15th centile (which we have to take the opposite again)
@@ -405,7 +463,7 @@
                     else:
                         row.append(np.abs(aggregated))
                 else:
-                    row.extend([None]*len(aggFunctions))
+                    row.extend([None]*nAggFunctions)
         for h,t in zip(dataColumns, args.eventThresholds): # each threshold in this case applies to one indicator
             if 'speed' in h.lower():
                 row.append((group[h] > t).sum())
--- a/trafficintelligence/events.py	Mon Feb 22 22:12:19 2021 -0500
+++ b/trafficintelligence/events.py	Thu Mar 04 23:12:34 2021 -0500
@@ -85,7 +85,7 @@
                       'm',
                       'm',
                       'rad',
-                      'm/s',
+                      'km/h',
                       '',
                       's',
                       '',