tvaLib
draw.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 # tvaLib Copyright (c) 2012-2016 Paul G. St-Aubin
3 # Ecole Polytechnique de Montreal, McGill University
4 # Python 2.7; (dt) Spyder Windows 10 64-bit; ipython Ubuntu 15.04 64-bit
5 
9 import os, sys, time, math
10 from inspect import getfile as inspect_getfile
11 from inspect import currentframe as inspect_currentframe
12 from copy import deepcopy
13 
14 try: import numpy as np
15 except ImportError: raise Exception, [0101, 'Numpy is not installed.']
16 try:
17  import matplotlib.pyplot as plt
18  import matplotlib.pylab as plb
19  #import matplotlib.image as mpimg
20 except ImportError: raise Exception, [106, 'Matplotlib could not be found/imported.']
21 
22 if __name__ == '__main__':
23  print('Draw library loaded directly.')
24  sys.path.insert(0,os.path.dirname(os.path.dirname(os.path.abspath(inspect_getfile(inspect_currentframe())))))
25 import lib.tools as tvaLib
26 import lib.filt as tvaFilter
27 import lib.scene as tvaScene
28 import lib.vis as tvaVis
29 
30 
33 def tellMe(s, console='', target=False):
34  if(not target):
35  target = plt
36 
37  if(console): print(console)
38  #print('Drawing console: '+s)
39  plt.title(s,fontsize=14)
40  plt.draw()
41 
42 
43 def drawMask(objects, sites, siteIx=0, camIx=0, fileIx=0, draw_max_traj=200, local=None, alternateView=False, autoUpdate=True, figsize=[15.0,12.0]):
44  ''' User interface for mask drawing.
45 
46  Input:
47  ======
48  alternateView: display drawing in image space
49  '''
50 
51  if(alternateView and sites[siteIx][camIx].getHomography()):
52  if(sites[siteIx][camIx].camera.camera_matrix.data): forceDistorted = True
53  else: forceDistorted = False
54  target = tvaVis.visTrajImageSpace(objects, sites[siteIx][camIx][fileIx].getImageSpaceFrameFilename(forceDistorted=forceDistorted), tvaLib.Obj.invHomography(sites[siteIx][camIx].getHomography().asNpArray()), draw_max_traj=draw_max_traj, mask=sites[siteIx][camIx].mask, figsize=figsize, local=local)
55  else:
56  target = tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, mask=sites[siteIx][camIx].mask, sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, figsize=figsize, local=local)
57  if(target is None): raise Exception, [311, 'There was an unknown problem preparing the mask.']
58  plt.setp(target.gca(), autoscale_on=False)
59 
60  target.tight_layout()
61 
62  exit_cond = False
63  mask = []
64  pj = []
65  j = 0
66  while not exit_cond:
67  pts = []
68  while len(pts) < 3:
69  tellMe('Draw mask. Left click: select points, right click: undo, middle click: exit.', target=target)
70  pts = [list(x) for x in plb.ginput(0,timeout=-1)]
71  if(len(pts) == 0):
72  plt.close()
73  return None
74  if(len(pts) < 3):
75  tellMe('Too few points, starting over.', target=target)
76  time.sleep(1) # Wait a second
77 
78  ph = plb.fill([x[0] for x in pts], [x[1] for x in pts], 'r', lw=2)
79  tellMe('Save mask? Mouse click for yes, key click for no.', target=target)
80  save_cond = plb.waitforbuttonpress()
81  for p in ph: p.remove()
82  if(not save_cond):
83  mask.append(pts)
84  pj.append(plb.fill([x[0] for x in pts], [x[1] for x in pts], '0.66', lw=2))
85  j += 1
86  tellMe('Add mask? Mouse for yes, key for no.', target=target)
87  exit_cond = plb.waitforbuttonpress()
88  plt.close()
89 
90 
91  if(alternateView and sites[siteIx][camIx].getHomography()):
92  for mIx in range(len(mask)):
93  mask[mIx] = tvaLib.Obj.homographyProject(mask[mIx], sites[siteIx][camIx].getHomography())
94 
95  if(mask and autoUpdate):
96  sites[siteIx][camIx].mask = tvaLib.Constructors.SuperListParse(mask, dimension=3, datatype='point')
97  sites.update()
98  return sites
99 
100 def drawZone(objects, sites, site_analyses, siteIx=0, camIx=0, saIx=0, draw_max_traj=200, local=None, autoUpdate=True, figsize=[15.0,12.0]):
101  ''' User interface for analysis zone drawing. '''
102  target = tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=sites[siteIx].alignments, mask=sites[siteIx][camIx].mask, zone=site_analyses[saIx].zone, xy_bounds=site_analyses[saIx].xy_bounds, sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, figsize=figsize, local=local)
103  plt.setp(target.gca(), autoscale_on=False)
104 
105  target.tight_layout()
106 
107  exit_cond = False
108  zone = []
109  pj = []
110  j = 0
111  while not exit_cond:
112  pts = []
113  while len(pts) < 3:
114  tellMe('Draw zone. Left click: select points, right click: undo, middle click: exit.', target=target)
115  pts = [list(x) for x in plb.ginput(0,timeout=-1)]
116  if(len(pts) == 0):
117  plt.close()
118  return None
119  if(len(pts) < 3):
120  tellMe('Too few points, starting over.', target=target)
121  time.sleep(1) # Wait a second
122 
123  ph = plb.fill([x[0] for x in pts], [x[1] for x in pts], 'r', lw=2)
124 
125  tellMe('Save zone? Mouse click for yes, key click for no.', target=target)
126  save_cond = plb.waitforbuttonpress()
127  for p in ph: p.remove()
128  if(not save_cond):
129  zone.append(pts)
130  pj.append(plb.fill([x[0] for x in pts], [x[1] for x in pts], '0.66', lw=2))
131  j += 1
132  tellMe('Add zone? Mouse for yes, key for no.', target=target)
133  exit_cond = plb.waitforbuttonpress()
134  plt.close()
135 
136  if(zone and autoUpdate):
137  site_analyses[saIx].zone = tvaLib.Constructors.SuperListParse(zone, dimension=3, datatype='point')
138  site_analyses.update()
139  return site_analyses
140 
141 def drawAlign(objects, sites, siteIx=0, draw_max_traj=200, local=None, autoUpdate=True, minPointDist=0.5, connectorSearchDistance=2.5, intersectionDistanceFactor=1.5, alpha=0.5, zorder=21, figsize=[15.0,12.0]):
142  ''' User interface for alignment drawing.
143 
144  Input:
145  ======
146  minPointDist: this ensures that a minimum distance in correponding
147  distance units, i.e. meters, exists between points, to
148  avoid rare duplicate points, or exessive point drawing.
149  '''
150  if(type(objects) is not list): objects = [objects]
151  target = tvaVis.traj2D(objects[0], draw_max_traj=draw_max_traj, alignments=sites[siteIx].alignments, mask=sites[siteIx].getCombinedMasks(), sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, fig_name=sites[siteIx].name+': Draw alignments...', figsize=figsize, local=local, **dict(('companion_objects_'+str(olIx), objects[olIx]) for olIx in range(1,len(objects))))
152  plt.setp(target.gca(), autoscale_on=False)
153 
154 
155  '''exit_condition = True
156  while exit_condition:
157  pj = []
158  alignments = []
159  ## Segments
160  while True:
161  pts = []
162  while len(pts) < 2:
163  tellMe('Draw alignments. Left click: select points, right click: undo, middle click: exit.', target=target)
164  pts = [list(x) for x in plb.ginput(0,timeout=-1)]
165  if(len(pts) == 0): break
166  if(len(pts) == 1):
167  tellMe('Too few points, starting over.', target=target)
168  time.sleep(1) # Wait a second
169  if(len(pts) == 0): break
170 
171  ph = plb.plot([x[0] for x in pts], [x[1] for x in pts], 'r', lw=2, zorder=zorder)
172 
173  tellMe('Save alignments? Mouse click for yes, key click for no.', target=target)
174  save_cond = plb.waitforbuttonpress()
175  for p in ph: p.remove()
176  if(not save_cond):
177  alignments.append(pts)
178  pj.append(plb.plot([x[0] for x in pts], [x[1] for x in pts], '#FFA500', lw=2, zorder=zorder))
179 
180  if(not alignments):
181  exit_condition = False
182  break
183 
184 
185  newAlign = tvaScene.Alignments(alignments)
186  for link in newAlign.getLinks():
187  pj.append(plb.plot([link[2], link[6]], [link[3], link[7]], 'c', lw=4 ))
188  for corridorSet in newAlign.getCorridors():
189  if(newAlign[corridorSet[0]].hasAlignBB() and newAlign[corridorSet[1]].hasAlignBB()):
190  X2 = newAlign[corridorSet[1]].getXCoordinatesBB()
191  X2.reverse()
192  Y2 = newAlign[corridorSet[1]].getYCoordinatesBB()
193  Y2.reverse()
194  pj.append(plt.fill(newAlign[corridorSet[0]].getXCoordinatesBB()+X2, newAlign[corridorSet[0]].getYCoordinatesBB()+Y2, 'c', alpha=alpha, zorder=zorder-1))
195  else:
196  X2 = newAlign[corridorSet[1]].getXCoordinates()
197  X2.reverse()
198  Y2 = newAlign[corridorSet[1]].getYCoordinates()
199  Y2.reverse()
200  pj.append(plt.fill(newAlign[corridorSet[0]].getXCoordinates()+X2, newAlign[corridorSet[0]].getYCoordinates()+Y2, 'c', alpha=alpha, zorder=zorder-1))
201 
202  tellMe(. Mouse to continue, key to discard.', target=target) exit_condition = plb.waitforbuttonpress()
203  if(exit_condition): break
204 
205  for ph in pj:
206  for p in ph: p.remove() '''
207 
208  class LocalState(object):
209  def __init__(self, original_alignments, minPointDist=0.5, connectorSearchDistance=2.5, operation=0, zorder=21):
210  self.original_alignments = original_alignments
211  self.alignments = []
212  self.sidewalks = []
213  self.bikepaths = []
214  self.operation = operation
215  self.minPointDist = minPointDist
216  self.origin = None
217  self.zorder = 21
218  self.pts = []
219  self.pt_plot = []
220  self.pt_arrows = []
221  self.pt_plots = []
222  self.connectors = []
223  self.instruction = {1: 'Draw alignments.',
224  2: 'Verify alignment connectors (max search distance = '+str(connectorSearchDistance)+'m)',
225  3: 'Select alignments to identify as sidewalks (step is optional).',
226  4: 'Select alignments to identify as bikepaths (step is optional).',
227  5: 'Close'}
228  self.console = {1: 'Left click: select points; right click: undo; middle click: next alignment; keypress: skip drawing.',
229  2: 'Left click: accept; right click: return to previous step.',
230  3: 'Left click: select alignment; right click: undo; middle click or keypress: finish.',
231  4: 'Left click: select alignment; right click: undo; middle click or keypress: finish.',
232  5: 'Close'}
233 
234  def __call__(self, event):
235  #print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (event.button, event.x, event.y, event.xdata, event.ydata))
236  #sys.stdout.flush()
237 
238  minPointDistSquared = self.minPointDist**2
239 
240  ## Draw alignments
241  if(self.operation==1):
242  # Left click (add point/alignment)
243  if(hasattr(event, 'button') and event.button == 1):
244  if(len(self.pts) > 0):
245  if((event.xdata-self.pts[-1][0])**2+(event.ydata-self.pts[-1][1])**2 > minPointDistSquared):
246  self.pts.append([event.xdata, event.ydata])
247  plot, = plb.plot([x[0] for x in self.pts[-2:]], [x[1] for x in self.pts[-2:]], color='r', lw=3, zorder=self.zorder)
248  self.pt_plot.append(plot)
249  elif(len(self.pts) == 0):
250  self.pts.append([event.xdata, event.ydata])
251  self.origin, = plb.plot([event.xdata], [event.ydata], color='r', marker='o')
252  plt.draw()
253  # Right click (remove point)
254  elif(hasattr(event, 'button') and event.button == 3 and self.pts):
255  self.pts = self.pts[:-1]
256  if(len(self.pts) > 0):
257  try:
258  self.pt_plot[-1].remove()
259  self.pt_plot = self.pt_plot[:-1]
260  plt.draw()
261  except: pass
262  if(len(self.pts) == 0):
263  self.origin.remove()
264  plt.draw()
265  # Middle click (finish alignment/set)
266  elif(hasattr(event, 'button') and event.button == 2 and self.pts):
267  self.origin.remove()
268  for pp in self.pt_plot: pp.remove()
269  self.pt_plot = []
270  plot,arrows = tvaVis.plotWithArrows([x[0] for x in self.pts], [x[1] for x in self.pts], color='#FFA500', lw=3, zorder=self.zorder)
271  self.pt_plots.append(plot[0])
272  self.pt_arrows.append(arrows)
273  self.alignments.append(self.pts)
274  self.pts = []
275  plt.draw()
276  elif(event.key or event.button == 2 and not self.pts):
277  self.nextOperation()
278 
279 
280  elif(self.operation==2):
281  if(hasattr(event, 'button') and event.button == 1):
282  self.nextOperation()
283  if(hasattr(event, 'button') and event.button == 3):
284  self.alignments = []
285  self.previousOperation()
286 
287 
288  elif(self.operation==3 and self.alignments):
289  if(hasattr(event, 'button') and event.button == 1):
290  [spline,_,_,_] = tvaLib.Geo.matchSplineNearest(event.xdata, event.ydata, self.alignments)
291  if(spline not in self.sidewalks):
292  self.pt_plots[spline].set_color('#377220')
293  self.sidewalks.append(spline)
294  plt.draw()
295  elif(hasattr(event, 'button') and event.button == 3 and self.sidewalks):
296  [spline,_,_,_] = tvaLib.Geo.matchSplineNearest(event.xdata, event.ydata, self.alignments)
297  if(spline in self.sidewalks):
298  self.pt_plots[spline].set_color('#FFA500')
299  self.sidewalks.remove(spline)
300  plt.draw()
301  elif(hasattr(event, 'button') and event.button == 2): self.nextOperation()
302  elif(event.key): self.nextOperation()
303 
304 
305  elif(self.operation==4 and self.alignments):
306  if(hasattr(event, 'button') and event.button == 1):
307  [spline,_,_,_] = tvaLib.Geo.matchSplineNearest(event.xdata, event.ydata, self.alignments)
308  if(spline not in self.sidewalks and spline not in self.bikepaths):
309  self.pt_plots[spline].set_color('#700f92')
310  self.bikepaths.append(spline)
311  plt.draw()
312  elif(hasattr(event, 'button') and event.button == 3 and self.bikepaths):
313  [spline,_,_,_] = tvaLib.Geo.matchSplineNearest(event.xdata, event.ydata, self.alignments)
314  if(spline not in self.sidewalks and spline in self.bikepaths):
315  self.pt_plots[spline].set_color('#FFA500')
316  self.bikepaths.remove(spline)
317  plt.draw()
318  elif(hasattr(event, 'button') and event.button == 2): self.nextOperation()
319  elif(event.key): self.nextOperation()
320 
321  def cleanforward(self):
322  ''' Cleanup drawing (moving forward).
323  Last value of *operation* is origin '''
324 
325  if(self.operation==1):
326  if(not self.alignments): self.alignments = self.original_alignments.data
327  for pp in self.pt_plot: pp.remove()
328  for pp in self.pt_plots:
329  try: pp.remove()
330  except: pass
331  try: self.origin.remove()
332  except: pass
333  self.pt_plots = []
334  for align in self.alignments:
335  plot,arrows = tvaVis.plotWithArrows([x[0] for x in align], [x[1] for x in align], color='#FFA500', lw=3, zorder=self.zorder+1)
336  self.pt_plots.append(plot[0])
337  self.pt_arrows.append(arrows)
338  self.pts = []
339  plt.draw()
340 
341 
342 
343  elif(self.operation==2):
344  for pp in self.connectors:
345  try: pp.remove()
346  except: pass
347 
348 
349  def cleanback(self):
350  ''' Cleanup drawing (moving forward).
351  Last value of *operation* is origin '''
352  if(self.operation==2):
353  for pp in self.pt_plots:
354  try: pp.remove()
355  except: pass
356  for pp in self.pt_arrows:
357  for arrow in pp:
358  try: arrow.remove()
359  except: pass
360 
361  def prepare(self):
362 
363  if(self.operation==1):
364  plt.draw()
365  elif(self.operation==2):
366  newAlign = tvaScene.Alignments(self.alignments)
367  for link in newAlign.getLinks():
368  plot, = plb.plot([link[2], link[6]], [link[3], link[7]], 'c', lw=4)
369  self.connectors.append(plot)
370  for corridorSet in newAlign.getCorridors():
371  if(newAlign[corridorSet[0]].hasAlignBB() and newAlign[corridorSet[1]].hasAlignBB()):
372  X2 = newAlign[corridorSet[1]].getXCoordinatesBB()
373  X2.reverse()
374  Y2 = newAlign[corridorSet[1]].getYCoordinatesBB()
375  Y2.reverse()
376  plot, = plt.fill(newAlign[corridorSet[0]].getXCoordinatesBB()+X2, newAlign[corridorSet[0]].getYCoordinatesBB()+Y2, 'c', alpha=alpha, zorder=zorder-1)
377  self.connectors.append(plot)
378  else:
379  X2 = newAlign[corridorSet[1]].getXCoordinates()
380  X2.reverse()
381  Y2 = newAlign[corridorSet[1]].getYCoordinates()
382  Y2.reverse()
383  plot, = plt.fill(newAlign[corridorSet[0]].getXCoordinates()+X2, newAlign[corridorSet[0]].getYCoordinates()+Y2, 'c', alpha=alpha, zorder=zorder-1)
384  self.connectors.append(plot)
385  plt.draw()
386  elif(self.operation==3):
387  plt.draw()
388 
389  elif(self.operation==5):
390  plt.close()
391 
392 
393  def nextOperation(self):
394  self.cleanforward()
395  self.operation += 1
396  tellMe(self.instruction[self.operation], console=self.console[self.operation], target=target)
397  self.prepare()
398 
399  def previousOperation(self):
400  self.cleanback()
401  self.operation -= 1
402  tellMe(self.instruction[self.operation], console=self.console[self.operation], target=target)
403  self.prepare()
404 
405 
406  localState = LocalState(sites[siteIx].alignments, minPointDist=minPointDist, connectorSearchDistance=connectorSearchDistance, zorder=zorder)
407  localState.nextOperation()
408  target.canvas.mpl_connect('button_release_event', localState)
409  target.canvas.mpl_connect('key_release_event', localState)
410  plt.show(block=True)
411 
412  if(localState.operation==5 and autoUpdate):
413  sites[siteIx].alignments = tvaScene.Alignments(localState.alignments, sidewalks=sorted(localState.sidewalks), bikepaths=sorted(localState.bikepaths))
414  sites.update()
415 
416  return localState.alignments
417 
418 
419 def drawBounds(objects, sites, site_analyses, siteIx=0, camIx=0, saIx=0, draw_max_traj=200, local=None, autoUpdate=True, figsize=[15.0,12.0]):
420  ''' User interface for bound drawing. '''
421  target = tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=sites[siteIx].alignments, mask=sites[siteIx][camIx].mask, sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, labels=False, figsize=figsize, local=local)
422  plt.setp(target.gca(),autoscale_on=False)
423 
424  fig = plt.gcf()
425  exit_cond = False
426  while not exit_cond:
427  tellMe('Draw plot bounds. Left click: draw points, right click: undo, middle click: finish.', target=target)
428  pts = [list(x) for x in plb.ginput(2,timeout=-1)]
429  if(len(pts) < 2):
430  plt.close()
431  return None
432  rect_artist = fig.gca().add_artist(plt.Rectangle(pts[0], pts[1][0]-pts[0][0], pts[1][1]-pts[0][1], alpha=1, facecolor='none', edgecolor='#000000', linestyle='-', linewidth=2))
433 
434  tellMe('Save plot bounds? Mouse click for yes, key click for no.', target=target)
435  exit_cond = not plb.waitforbuttonpress()
436 
437  rect_artist.remove()
438  xy_bounds = [[min(pts[0][0],pts[1][0]),max(pts[0][0],pts[1][0])],[min(pts[0][1],pts[1][1]),max(pts[0][1],pts[1][1])]]
439  plt.close()
440 
441  if(xy_bounds and autoUpdate):
442  site_analyses[saIx].xy_bounds = tvaLib.Constructors.SuperListParse(xy_bounds, datatype='bound')
443  site_analyses.update()
444  return
445 
446 def autoBounds(objects, site_analyses, saIx=0, centile=2.0, max_outside_distance=10.0, inside_buffer=5.0, autoUpdate=True, verbose=0):
447  ''' Automatically create bounds from trajectory location and orthophoto. '''
448 
449 
450  x_mins = [min(obj.getXCoordinates()) for obj in objects]
451  x_maxs = [max(obj.getXCoordinates()) for obj in objects]
452  y_mins = [min(obj.getYCoordinates()) for obj in objects]
453  y_maxs = [max(obj.getYCoordinates()) for obj in objects]
454 
455  x_min = tvaLib.Math.getPercintileBinFromList(x_mins, percentile=centile)
456  x_max = tvaLib.Math.getPercintileBinFromList(x_maxs, percentile=100.0-centile)
457  y_min = tvaLib.Math.getPercintileBinFromList(y_mins, percentile=centile)
458  y_max = tvaLib.Math.getPercintileBinFromList(y_maxs, percentile=100.0-centile)
459 
460 
461  xy_bounds = [[x_min-inside_buffer,x_max+inside_buffer],[y_min-inside_buffer,y_max+inside_buffer]]
462 
463 
464  try:
465  img = mpimg.imread(site_analyses[saIx].site.getFullSatFilename())
466  x = int(img.shape[1]*site_analyses[saIx].site.satres)
467  y = int(img.shape[0]*site_analyses[saIx].site.satres)
468  if(xy_bounds[0][0] < -max_outside_distance): xy_bounds[0][0] = -max_outside_distance
469  if(xy_bounds[0][1] > x+max_outside_distance): xy_bounds[0][1] = x+max_outside_distance
470  if(xy_bounds[1][0] < -max_outside_distance): xy_bounds[1][0] = -max_outside_distance
471  if(xy_bounds[1][1] > y+max_outside_distance): xy_bounds[1][1] = y+max_outside_distance
472  except: pass
473 
474  if(verbose>=5): print(' Bounds set automatically to: '+str(xy_bounds))
475 
476  if(xy_bounds and autoUpdate):
477  site_analyses[saIx].xy_bounds = tvaLib.Constructors.SuperListParse(xy_bounds, datatype='bound')
478  site_analyses.update()
479  return
480 
481 
482 def drawLoops(objects, site_analyses, saIx=0, draw_max_traj=200, radius=3, colour='#D63E33', local=None, autoUpdate=True, figsize=[15.0,12.0]):
483  ''' User interface for virtual loop drawing. '''
484  target = tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=tvaScene.Alignments(site_analyses[saIx].cameras[0].site.alignments), mask=site_analyses[saIx].getCombinedMasks(), virtual_loops=False, labels=False, figsize=figsize, local=local)
485  plt.setp(target.gca(),autoscale_on=False)
486 
487  virtual_loops_return = []
488  fig = plt.gcf()
489  exit_cond = False
490  while not exit_cond:
491  tellMe('Draw virtual loops. Left click: draw points, right click: undo, middle click: finish.', target=target)
492  pts = [list(x) for x in plb.ginput(0,timeout=-1)]
493  if(len(pts) == 0):
494  plt.close()
495  return None
496 
497  circles=[]
498  for pt in pts:
499  circle = plt.Circle((pt[0], pt[1]), radius=radius, color=colour, alpha=0.7, zorder=1000)
500  circles.append(fig.gca().add_artist(circle))
501 
502  tellMe('Save virtual loops? Mouse click for yes, key click for no.', target=target)
503  save_cond = plb.waitforbuttonpress()
504  if(not save_cond):
505  virtual_loops_return += pts
506  else:
507  for circle in circles:
508  circle.remove()
509  tellMe('Add more virtual loops? Mouse for yes, key for no.', target=target)
510  exit_cond = plb.waitforbuttonpress()
511  plt.close()
512 
513  if(virtual_loops_return and autoUpdate):
514  site_analyses[saIx].virtual_loops = tvaScene.VirtualLoops(virtual_loops_return)
515  site_analyses.update()
516  return site_analyses
517 
518 
519 def drawTransformation(objects, sequence, draw_max_traj=200, figsize=[15.0,12.0]):
520  ''' Setup drawing of transformation vectors. '''
522  translationX = sequence.translationX
523  translationY = sequence.translationY
524  rotation = sequence.rotation
525 
526  alignments = tvaScene.Alignments(sequence.cameraView.site.alignments)
527  mask = tvaLib.Constructors.SuperListParse(sequence.cameraView.mask, dimension=3, datatype='point')
528 
529  objects = tvaFilter.transformTrajectories(objects, 0.0, 0.0, rotation, objectsOnly=True)
530  tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=alignments, mask=mask, virtual_loops=False, labels=False, figsize=figsize)
531 
532  exit_cond = False
533  while not exit_cond:
534  tellMe('Change rotation? Mouse click for yes, key click for no.')
535  exit_cond = plt.waitforbuttonpress()
536  if(exit_cond == True):
537  break
538 
539  tellMe('Draw origin (1) and desination (2) reference points to rotate about (0,0).')
540 
541  pts = []
542  for point in range(2):
543  pts.append(list(plb.ginput(1,timeout=-1)[0]))
544  plt.plot([0,pts[point][0]], [0,pts[point][1]], 'm', lw=2 )
545 
546  incrRotation = tvaLib.Geo.vectorsToAngleDegCC(pts[0][0],pts[0][1],pts[1][0],pts[1][1])
547  rotation += incrRotation
548  print('Rotation angle: '+str(rotation))
549  print('Incremental rotation angle: '+str(incrRotation))
550  objects = tvaFilter.transformTrajectories(objects, 0.0, 0.0, incrRotation, objectsOnly=True)
551  plt.clf()
552  tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=alignments, mask=mask, virtual_loops=False, labels=False)
553 
554  tellMe('Change rotation? Mouse click for yes, key click for no.')
555  exit_cond = plt.waitforbuttonpress()
556 
557  plt.clf()
558  objects = tvaFilter.transformTrajectories(objects, translationX, translationY, 0.0, objectsOnly=True)
559  tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=alignments, mask=mask, virtual_loops=False, labels=False)
560 
561  exit_cond = False
562  while not exit_cond:
563  tellMe('Apply new translation? Mouse click for yes, key click for no.')
564  exit_cond = plt.waitforbuttonpress()
565  if(exit_cond == True):
566  break
567 
568  tellMe('Draw origin (1) and desination (2) reference points for translation.')
569 
570  pts = []
571  for point in range(2):
572  pts.append(list(plb.ginput(1,timeout=-1)[0]))
573 
574  incrTranslation = [pts[1][0]-pts[0][0],pts[1][1]-pts[0][1]]
575  translationX += incrTranslation[0]
576  translationY += incrTranslation[1]
577  print('Translation : '+str(translationX)+','+str(translationY))
578  print('Incremental translation: '+str(incrTranslation[0])+','+str(incrTranslation[1]))
579  objects = tvaFilter.transformTrajectories(objects, incrTranslation[0], incrTranslation[1], 0.0, objectsOnly=True)
580  plt.clf()
581  tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=alignments, mask=mask, virtual_loops=False, labels=False)
582 
583  plt.draw()
584  plt.close()
585  return rotation, translationX, translationY
586 
587 
588 def drawAlignSnap(objects, site, draw_max_traj=200, local=None, figsize=[15.0,12.0]):
589  ''' Snap point to alignment.
590  This mode is verbose only. '''
591  target = tvaVis.traj2D(objects, draw_max_traj=draw_max_traj, alignments=site.alignments, mask=site.getCombinedMasks(), labels=False, figsize=figsize, local=local)
592  plt.setp(target.gca(), autoscale_on=False)
593 
594  exit_cond = False
595  while not exit_cond:
596 
597  try: tellMe('(X,Y)=({0},{1}) snaps to (S,Y,L)=({2},{3},{4}); Left click: next point; Middle click: exit'.format(round(qx,2),round(qy,2),round(seg_d,2),round(OFFS_mag,2),round(spline,0)))
598  except: tellMe('Align Snap. Left click: select point near alignment.')
599  pts = np.asarray(plb.ginput(1,timeout=-1))
600  try:
601  qx = pts[0,0]
602  qy = pts[0,1]
603  except: break
604 
605  [spline,_,snapped_x,snapped_y] = tvaLib.Geo.matchSplineNearest(qx, qy, site.alignments)
606 
607  if(spline is False):
608  tellMe('There was a problem snapping this point. Please try again.')
609  continue
610 
611  plb.plot([snapped_x], [snapped_y], 'o', zorder=90000)
612 
613 
614  plt.close()
615  return True
616 
617 
618 def drawObjHighlight(objects, camera, draw_max_traj=200, local=None, arrow_every_dist=8.0, highlight_thickness=3.0, align_highlight_steps=5, figsize=[15.0,12.0]):
619  ''' Highlight selected object. This mode currently must plot all
620  trajectories available. '''
621 
622  alignments = tvaScene.Alignments(camera.site.alignments)
623  target = tvaVis.traj2D(objects, draw_max_traj=None, alignments=alignments, mask=camera.mask, labels=False, figsize=figsize, local=local)
624  plt.setp(target.gca(), autoscale_on=False)
625 
626  exit_cond = False
627  while not exit_cond:
628 
629  try: tellMe('Object ID: '+str(objects[nearest_object_ix].num)+'; Left click: continue; Middle click: exit')
630  except: tellMe('Object highlighting. Left click: select point near object.')
631  pts = np.asarray(plb.ginput(1,timeout=-1))
632  try:
633  qx = pts[0,0]
634  qy = pts[0,1]
635  except: break
636 
637 
638  nearest_squared_distance = float('inf')
639  nearest_object_ix = None
640  for oIx in range(len(objects)):
641  for pIx in range(len(objects[oIx].positions.getXCoordinates())):
642  squared_distance = (qx-objects[oIx].positions.getXCoordinates()[pIx])**2+(qy-objects[oIx].positions.getYCoordinates()[pIx])**2
643  if(squared_distance < nearest_squared_distance):
644  nearest_squared_distance = squared_distance
645  nearest_object_ix = oIx
646 
647 
648  if(oIx is None):
649  tellMe('Could not locate a nearest object. Please try again.')
650  continue
651 
652 
653  try:
654  line.remove()
655  for arrow in arrows: arrow.remove()
656  except: pass
657  try:
658  for align_correspondance in align_correspondances: align_correspondance[0].remove()
659  except: pass
660 
661  line, = plt.plot(objects[nearest_object_ix].getXCoordinates(), objects[nearest_object_ix].getYCoordinates(), color='red', linewidth=highlight_thickness)
662  arrows=[]
663  cum_dist_since = 0
664  for pIx in range(len(objects[nearest_object_ix].getXCoordinates())):
665  cum_dist_since += math.sqrt(objects[nearest_object_ix].velocities.getXCoordinates()[pIx]**2+objects[nearest_object_ix].velocities.getYCoordinates()[pIx]**2)
666  if(cum_dist_since>arrow_every_dist):
667  cum_dist_since = 0
668  arrows.append(plt.arrow(objects[nearest_object_ix].getXCoordinates()[pIx], objects[nearest_object_ix].getYCoordinates()[pIx], objects[nearest_object_ix].velocities.getXCoordinates()[pIx], objects[nearest_object_ix].velocities.getYCoordinates()[pIx], head_width=1, head_length=2, fc='red', ec='red', zorder=20))
669  if(hasattr(objects[nearest_object_ix], 'curvilinearPositions')):
670  align_correspondances = []
671  for pIx in range(0,len(objects[nearest_object_ix].getXCoordinates()),align_highlight_steps):
672  [x,y] = tvaLib.Geo.getXYfromSY(objects[nearest_object_ix].curvilinearPositions.getXCoordinates()[pIx], alignments[objects[nearest_object_ix].curvilinearPositions.getLanes()[pIx]])
673  align_correspondances.append(plt.plot([objects[nearest_object_ix].getXCoordinates()[pIx],x], [objects[nearest_object_ix].getYCoordinates()[pIx],y], color='orange', linewidth=2))
674  plt.close()
675  return True
676 
677 
678 def drawMastLocation(sites, siteIx, camIx, autoUpdate=True, local=None, figsize=[15.0,12.0]):
679  ''' Draw interactive plot to select mast location. '''
680 
681 
682  target = tvaVis.traj2D([], sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, labels=False, fig_name=sites[siteIx].name+'/'+sites[siteIx][camIx].name+': Select camera origin...', figsize=figsize, local=local)
683  plt.setp(target.gca(), autoscale_on=False)
684  pt = plb.ginput(1,timeout=-1)
685 
686 
687  plt.close()
688 
689 
690  if(autoUpdate):
691  sites[siteIx][camIx].camOrigin = tvaLib.Constructors.SuperListParse(list(pt[0]), dimension=1, datatype='float')
692  sites.update()
693  return sites
694 
695 
696 
697 
698 def drawMastCalibration(objects, sites, siteIx, camIx, autoUpdate=True, local=None, figsize=[15.0,12.0]):
699  ''' Draw plot with slider allowing for mast height to be interactively changed
700  trajectories available. '''
701 
702  from matplotlib.widgets import Slider
703  if(not sites[siteIx][camIx].camHeight): sites[siteIx][camIx].camHeight=10.0
704 
705 
706  target = tvaVis.traj2D([], alignments=sites[siteIx].alignments, sateliteImage=sites[siteIx].getFullSatFilename(), sateliteResolution=sites[siteIx].satres, labels=False, fig_name=sites[siteIx].name+'/'+sites[siteIx][camIx].name+': Calibrate camera height (0-30m)...', figsize=figsize, local=local)
707  ca = target.gca()
708  plt.setp(target.gca(), autoscale_on=False)
709 
710 
711  sliderHeight_ax = plt.axes([0.25, 0.1, 0.65, 0.03])
712  sliderHeight = Slider(sliderHeight_ax, 'camHeight', 0.1, 30.0, valinit=sites[siteIx][camIx].camHeight)
713 
714 
715  class LocalState(object):
716  def __init__(self):
717  self.obj_traces = []
718 
719  def update(self, val):
720 
721  for obj in self.obj_traces:
722  obj.remove()
723  self.obj_traces=[]
724 
725  transformed_obj = deepcopy(objects)
726  transformed_obj = tvaFilter.compensateParralax(transformed_obj, origin=sites[siteIx][camIx].camOrigin, mast_height=sliderHeight.val, verbose=0)
727  for obj in transformed_obj:
728  plot, = ca.plot(obj.getXCoordinates(), obj.getYCoordinates(), color='b')
729  self.obj_traces.append(plot)
730  plt.draw()
731 
732  localState = LocalState()
733  sliderHeight.on_changed(localState.update)
734  localState.update(None)
735 
736 
737 
738 
739  plt.show(block=True)
740  plt.close()
741 
742 
743  if(autoUpdate):
744  sites[siteIx][camIx].camHeight = sliderHeight.val
745  sites.update()
746  return sliderHeight.val
747 
748 
751 class DataEntry():
752  def __init__(self, instructions='', instructions2='', submit_label='Enter', cancel_label='Cancel', rows={}, sites=None, config=None, sites_selection_depth=1, dropdowns={}, multiSelectionListBoxes={}):
753  ''' Create data entry form
754 
755  Input
756  =====
757  instructions: printed at top of form
758  instructions2: printed at bottom of form
759  rows: ordinary text fields, as a dict, where keys are labels and values are defaults
760  dropdowns: drop-down selection fields, as a dict, where keys are labels, and values are dicts with keys as options and values as indeces.
761  multiSelectionListBoxes: multiple-selection list box fields, as a dict, where keys are labels, and values are dicts with keys as options and values as indeces.
762  sites: if this is passed, an interactive site selection dialogue will be printed
763  sites_selection_depth: 1=only select site; 2=select site and camera; 3=select site, camera, sequence
764  '''
765  from Tkinter import Tk, Label, Entry, Button, StringVar, OptionMenu, Listbox, W, E, N, S, Frame, Scrollbar, VERTICAL, END, LEFT
766  from interface import Style
767  from ttk import Combobox
768  from ttk import Style as ttk_Style
769  self.root = Tk()
770  self.root.title('Data entry')
771 
772  self.style = Style()
773  self.ttk_style = ttk_Style()
774  self.ttk_style.theme_create('combostyle', parent='alt',
775  settings = {'TCombobox':
776  {'configure':
777  {'fieldbackground': self.style.bgdark,
778  'background': self.style.bgdark
779  }}}
780  )
781  self.ttk_style.theme_use('combostyle')
782 
783  self.config = config
784  self.sites = sites
785  self.sites_selection_depth = sites_selection_depth
786  self.root.configure(bg=self.style.bglight)
787  self.submit = False
788  Label(self.root, text=instructions, font='-weight bold -size 8', foreground=self.style.fg, bg=self.style.bglight, wraplength=320, justify=LEFT).grid(columnspan=2, sticky=W)
789 
790  self.labels = []
791  self.entryVals = []
792  rowIx = 1
793  for row in rows:
794  Label(self.root, text=row, foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
795  self.labels.append(row)
796  self.entryVals.append(StringVar())
797  self.entryVals[-1].set(rows[row])
798  Entry(self.root, bd=5, width=30, textvariable=self.entryVals[-1], foreground=self.style.fg, bg=self.style.bgdark, highlightbackground=self.style.bglight).grid(row=rowIx, column=1, sticky=W)
799  rowIx += 1
800 
801  self.ddlabels = []
802  self.ddVals = []
803  for dropdown in dropdowns:
804  Label(self.root, text=dropdown, foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
805  self.ddlabels.append(dropdown)
806  self.ddVals.append(StringVar(self.root))
807  for ddval in dropdowns[dropdown]:
808  self.ddVals[-1].set(ddval)
809  break
810  #w = OptionMenu(self.root, self.ddVals[-1], *[x for x in dropdowns[dropdown]])
811  w = Combobox(self.root, textvariable=self.ddVals[-1], values=[x for x in dropdowns[dropdown]], state='readonly')
812  w.config(foreground=self.style.fg, background=self.style.bgdark)
813  w.grid(row=rowIx, column=1, sticky=W)
814  rowIx += 1
815 
816  self.mslblabels = []
817  self.mslbVals = []
818  self.mslbScrolls = []
819  self.mslbValsContents = multiSelectionListBoxes
820  for multiSelectionListBox in multiSelectionListBoxes:
821  Label(self.root, text=multiSelectionListBox, foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
822  self.mslblabels.append(multiSelectionListBox)
823  self.mslbScrolls.append(Scrollbar(self.root, orient=VERTICAL, troughcolor=self.style.fg, bg=self.style.bgdark, highlightbackground=self.style.bglight))
824  self.mslbVals.append(Listbox(self.root, width=31, height=10, selectmode='multiple', yscrollcommand=self.mslbScrolls[-1].set, foreground=self.style.fg, bg=self.style.bgdark, highlightbackground=self.style.bglight))
825  for mslbVal in multiSelectionListBoxes[multiSelectionListBox]:
826  self.mslbVals[-1].insert(END, mslbVal)
827  self.mslbVals[-1].grid(row=rowIx, column=1, sticky=W)
828  self.mslbScrolls[-1].config(command=self.mslbVals[-1].yview)
829  self.mslbScrolls[-1].grid(row=rowIx, column=2, sticky=N+S)
830  rowIx += 1
831 
832  if(sites):
833  self.site_option = StringVar(self.root)
834  self.site_option.trace('w', self.updateCameraOptions)
835  self.site_option_menu = Combobox(self.root, textvariable=self.site_option, values=[str(site.idx)+': '+site.name for site in sites])
836  self.camview_option = StringVar(self.root)
837  self.camview_option.trace('w', self.updateSequenceOptions)
838  self.camview_option_menu = Combobox(self.root, textvariable=self.camview_option, values=[str(cam.idx)+': '+cam.name for cam in sites[0]])
839  self.sequence_option = StringVar(self.root)
840  try: self.sequence_option_menu = Combobox(self.root, textvariable=self.sequence_option, values=[str(seq.idx)+': '+seq.name for seq in sites[0][0]])
841  except: self.sequence_option_menu = Combobox(self.root, textvariable=self.sequence_option, values=['**No sequences found**'])
842  if(self.sites_selection_depth > 0):
843  Label(self.root, text='site', foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
844  self.site_option.set(str(sites[0].idx)+': '+sites[0].name)
845  self.site_option_menu.grid(row=rowIx, column=1, sticky=W)
846  self.site_option_menu.config(foreground=self.style.fg, background=self.style.bgdark)
847  rowIx += 1
848  if(self.sites_selection_depth > 1):
849  Label(self.root, text='camera', foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
850  self.camview_option.set(str(sites[0][0].idx)+': '+sites[0][0].name)
851  self.camview_option_menu.grid(row=rowIx, column=1, sticky=W)
852  self.camview_option_menu.config(foreground=self.style.fg, background=self.style.bgdark)
853  rowIx += 1
854  if(self.sites_selection_depth > 2):
855  Label(self.root, text='sequence', foreground=self.style.fg, bg=self.style.bglight).grid(row=rowIx, sticky=E)
856  self.sequence_option.set(str(sites[0][0][0].idx)+': '+sites[0][0][0].name)
857  self.sequence_option_menu.grid(row=rowIx, column=1, sticky=W)
858  self.sequence_option_menu.config(foreground=self.style.fg, background=self.style.bgdark)
859  rowIx += 1
860  if(instructions2): Label(self.root, text=instructions2, font='-weight bold -size 8', foreground=self.style.fg, bg=self.style.bglight, wraplength=320, justify=LEFT).grid(columnspan=2, sticky=W)
861  self.drawButtonFrame(cancel_label, submit_label)
862  self.root.mainloop()
863 
864  def submitCallback(self):
865  self.submit = True
866  self.root.quit()
867  def cancelCallback(self): self.root.quit()
868 
869  def getEntries(self): return dict((label, x.get()) for label,x in zip(self.labels, self.entryVals))
870  def getDropDowns(self): return dict((label, x.get()) for label,x in zip(self.ddlabels, self.ddVals))
871  def getMSLBs(self): return dict((label, str([self.mslbValsContents[label].items()[int(y)][1] for y in x.curselection()])) for label,x in zip(self.mslblabels, self.mslbVals))
872  def getSiteSelection(self): return int(self.site_option.get().split(':')[0])
873  def getCamViewSelection(self): return int(self.camview_option.get().split(':')[0])
874  def getSequenceSelection(self): return int(self.sequence_option.get().split(':')[0])
875  def getSelectedSite(self): return [x for x in self.sites if x.idx==self.getSiteSelection()][0]
876  def getSelectedCamView(self): return [y for y in self.getSelectedSite() if y.idx==self.getCamViewSelection()][0]
877  def setEntry(self, label, value):
878  for l,x in zip(self.labels, self.entryVals):
879  if(l == label): x.set(value)
880 
881  def updateCameraOptions(self, index, value, op):
882  if(self.sites_selection_depth > 1):
883  self.camview_option_menu['values'] = [str(cam.idx)+': '+cam.name for cam in self.getSelectedSite()]
884  self.camview_option.set(str(self.getSelectedSite()[0].idx)+': '+self.getSelectedSite()[0].name)
885 
886  def updateSequenceOptions(self, index, value, op):
887  if(self.sites_selection_depth > 2):
888  self.sequence_option_menu['values'] = [str(seq.idx)+': '+seq.name for seq in self.getSelectedCamView()]
889  self.sequence_option.set(str(self.getSelectedCamView()[0].idx)+': '+self.getSelectedCamView()[0].name)
890 
891  def drawButtonFrame(self, cancel_label, submit_label):
892  from Tkinter import Button, Frame
893  self.buttonframe = Frame(self.root)
894  self.buttonframe.grid(columnspan=2)
895  Button(self.buttonframe, text=cancel_label, command=self.cancelCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=0)
896  Button(self.buttonframe, text=submit_label, command=self.submitCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=1)
897 
898 class CamDataEntry(DataEntry):
899  def __init__(self, *args, **kwargs):
900  DataEntry.__init__(self, *args, **kwargs)
901 
902  def drawButtonFrame(self, cancel_label, submit_label):
903  from Tkinter import Button, Frame
904  self.buttonframe = Frame(self.root)
905  self.buttonframe.grid(columnspan=3)
906  Button(self.buttonframe, text='Select origin', command=self.selectOrigin, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=0)
907  Button(self.buttonframe, text=cancel_label, command=self.cancelCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=1)
908  Button(self.buttonframe, text=submit_label, command=self.submitCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=2)
909 
910  def selectOrigin(self):
911  target = tvaVis.traj2D([], sateliteImage=self.getSelectedSite().getFullSatFilename(), sateliteResolution=self.getSelectedSite().satres, labels=False, fig_name='Select camera origin', figsize=self.config.figsize)
912  plt.setp(target.gca(), autoscale_on=False)
913  pt = plb.ginput(1,timeout=-1)
914  self.setEntry('camOrigin', str(list(pt[0])))
915  plt.close()
916  return
917 
918 class SeqsDataEntry(DataEntry):
919  def __init__(self, allowed_extensions=['.mp4', '.avi'], *args, **kwargs):
920  self.allowed_extensions = allowed_extensions
921  DataEntry.__init__(self, *args, **kwargs)
922 
923 
924  def drawButtonFrame(self, cancel_label, submit_label):
925  from Tkinter import Button, Frame
926  self.buttonframe = Frame(self.root)
927  self.buttonframe.grid(columnspan=3)
928  Button(self.buttonframe, text='Auto-fill time', command=self.autoSearchStartTime, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=0)
929  Button(self.buttonframe, text='T+h', command=self.incrementStartTime, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=1)
930  Button(self.buttonframe, text='T-h', command=self.decrementStartTime, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=2)
931  Button(self.buttonframe, text=cancel_label, command=self.cancelCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=3)
932  Button(self.buttonframe, text=submit_label, command=self.submitCallback, foreground=self.style.fg, bg=self.style.bgdark).grid(row=0, column=4)
933 
934  def incrementStartTime(self, duration_h=1.0):
935  from datetime import datetime, timedelta
936  try: t = datetime.strptime(self.getEntries()['startTime'], '%Y-%m-%d %H:%M:%S')
937  except:
938  tvaLib.printWarning('Time format in startTime field is invalid. Use the "2010-01-31 10:00:00" format.')
939  return False
940  self.setEntry('startTime', (t+timedelta(0,0,0,0,0,duration_h)).strftime('%Y-%m-%d %H:%M:%S'))
941  def decrementStartTime(self, duration_h=1.0):
942  from datetime import datetime, timedelta
943  try: t = datetime.strptime(self.getEntries()['startTime'], '%Y-%m-%d %H:%M:%S')
944  except:
945  tvaLib.printWarning('Time format in startTime field is invalid. Use the "2010-01-31 10:00:00" format.')
946  return False
947  self.setEntry('startTime', (t-timedelta(0,0,0,0,0,duration_h)).strftime('%Y-%m-%d %H:%M:%S'))
948  def autoSearchStartTime(self):
949  from datetime import datetime
950  try:
951  for first_file in sorted(os.listdir(os.path.join(self.getSelectedCamView().getBaseDirectory()))):
952  if(os.path.splitext(first_file)[-1].lower() in self.allowed_extensions): break
953  self.setEntry('startTime', datetime.fromtimestamp(os.path.getmtime(os.path.join(self.getSelectedCamView().getBaseDirectory(), first_file))).strftime('%Y-%m-%d %H:%M:%S'))
954  try:
955  import cv2
956  video_capture = cv2.VideoCapture(os.path.join(self.getSelectedCamView().getBaseDirectory(), first_file))
957  video_length = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
958  fps = video_capture.get(cv2.CAP_PROP_FPS)
959  '''count = 0
960  while(True):
961  # Capture frame-by-frame
962  ret, frame = video_capture.read()
963  if(not ret): break
964  count += 1'''
965  video_capture.release()
966  cv2.destroyAllWindows()
967  if(int(video_length/float(fps))):
968  self.setEntry('duration', 'auto ('+str(int(video_length/float(fps)))+')')
969  self.setEntry('durationUnit', 's')
970  except: tvaLib.printWarning('Failed to detect video duration')
971  except: tvaLib.printWarning('Could not find video sequence on disk to auto-fill time information')
972 
973 
974 def dataEntryCamType(cameraTypes, local):
975  from collections import OrderedDict
976  entries = DataEntry('Enter camera type details:', submit_label=local['UI_form_add'], rows=OrderedDict([('name',''), ('resX',''), ('resY',''), ('frameRate',''), ('camera_matrix',''), ('dist_coeffs',''), ('FOV','1'), ('freeScalingParameter',''), ('imageScalingFactor','')]))
977  if(not entries.submit): return False
978  cameraTypes.new(sqlFields=entries.getEntries())
979  return True
980 
981 def dataEntrySite(sites, config, local):
982 
983  missings = []
984  for site in sorted(os.listdir(sites.getBaseDirectory())):
985  if(os.path.isdir(os.path.join(sites.getBaseDirectory(), site)) and site != config.output_folder and site not in [x.name for x in sites]): missings.append(site)
986  if(missings):
987  print 'The following folders are potential sites but are not entered in the database and may need to be completed:'
988  for missing in missings: print ''.ljust(4,' ')+missing
989 
990  from collections import OrderedDict
991  entries = DataEntry('Enter site details:', submit_label=local['UI_form_add'], rows=OrderedDict([('name',''), ('description',''), ('xcoordinate',''), ('ycoordinate',''), ('satres',''), ('satFilename','ortho-cal.png')]))
992  if(not entries.submit): return False
993  sites.new(sqlFields=entries.getEntries())
994  return True
995 
996 def dataEntryCameraView(sites, config, cameraTypes, local):
997  from collections import OrderedDict
998 
999  missings = []
1000  for site in sites:
1001  if(not site.data): missings.append(site.name)
1002  if(missings):
1003  print 'The following site/camera views are missing sequence data and need to be completed:'
1004  for missing in missings: print ''.ljust(4,' ')+missing
1005 
1006  dropdowns = {'cameraId':OrderedDict()}
1007  for camtype in cameraTypes: dropdowns['cameraId'][camtype.name] = camtype.idx
1008  entries = CamDataEntry('Enter camera view details:', submit_label=local['UI_form_add'], rows=OrderedDict([('name',''), ('homographyFilename','homography-undistort.txt'), ('homographyDistanceUnit','m'), ('cameraCalibrationFilename',''), ('configurationFilename',''), ('maskFilename','mask.png'), ('camOrigin',''), ('camHeight','10.0')]), sites=sites, config=config, dropdowns=dropdowns)
1009  if(not entries.submit): return False
1010  ddVals = entries.getDropDowns()
1011  if(not entries.getEntries()['name']):
1012  site = entries.getSelectedSite()
1013  for path in sorted([x[0] for x in os.walk(os.path.join(sites.getBaseDirectory(), site.name))]):
1014  if(path != os.path.join(sites.getBaseDirectory(), site.name) and len([x[0] for x in os.walk(path)])==1):
1015  entries.setEntry('name', str(os.path.relpath(path, start=os.path.join(sites.getBaseDirectory(), site.name))).replace('\\\\','\\').replace('\\','/'))
1016  entries.getSelectedSite().new(entries.getSiteSelection(), dropdowns['cameraId'][ddVals['cameraId']], sites.session, sqlFields=entries.getEntries())
1017  else:
1018  entries.getSelectedSite().new(entries.getSiteSelection(), dropdowns['cameraId'][ddVals['cameraId']], sites.session, sqlFields=entries.getEntries())
1019  return True
1020 
1021 def dataEntrySequences(sites, local, allowed_extensions=['.mp4', '.avi']):
1022  from collections import OrderedDict
1023  from utils import datetimeFormat
1024  from datetime import datetime, timedelta
1025 
1026  missings = []
1027  for site in sites:
1028  for cam in site:
1029  if(not cam.data): missings.append(site.name+'/'+cam.name)
1030  if(missings):
1031  print 'The following site/camera views are missing sequence data and need to be completed:'
1032  for missing in missings: print ''.ljust(4,' ')+missing
1033 
1034  entries = SeqsDataEntry(instructions='Enter sequential sequence search details:', instructions2='"Auto-fill time" will attempt to fill startTime and duration field by analysing the first existing video file, but is NOT guaranteed to work if the video file time encoding is not preoperly set.', submit_label=local['UI_form_search'], rows=OrderedDict([('startTime','2010-01-31 10:00:00'), ('duration','3600'), ('durationUnit','s'), ('configurationFilename',''), ('translationX','0.0'), ('translationY','0.0'), ('rotation','0.0')]), sites=sites, allowed_extensions=allowed_extensions, sites_selection_depth=2)
1035  if(not entries.submit): return False
1036  entriesData = entries.getEntries()
1037  startTime = datetime.strptime(entriesData.pop('startTime'), datetimeFormat)
1038  site = entries.getSelectedSite()
1039  camView = entries.getSelectedCamView()
1040  i=0
1041  cumulativeTimeDelta = timedelta(0)
1042  for filename in sorted(os.listdir(os.path.join(sites.getBaseDirectory(), site.name, camView.name))):
1043  if(os.path.splitext(filename)[-1].lower() in allowed_extensions):
1044  if('auto' in entries.getEntries()['duration']):
1045  try:
1046  import cv2
1047  video_capture = cv2.VideoCapture(os.path.join(sites.getBaseDirectory(), entries.getSelectedSite().name, entries.getSelectedCamView().name, filename))
1048  video_length = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
1049  fps = video_capture.get(cv2.CAP_PROP_FPS)
1050  video_capture.release()
1051  cv2.destroyAllWindows()
1052  if(int(video_length/float(fps))): entriesData['duration'] = str(int(video_length/float(fps)))
1053  except:
1054  entriesData['duration'] = 3600
1055  tvaLib.printWarning('Failed to detect video duration')
1056  camView.new(camView.idx, sites.session, sqlFields=tvaLib.mergeDicts(entriesData, {'videoFilename':filename, 'startTime':startTime+cumulativeTimeDelta, 'dataFilename':os.path.splitext(filename)[0]+'.sqlite'}, overwrite=True))
1057  cumulativeTimeDelta = cumulativeTimeDelta + timedelta(0,int(entriesData['duration']))
1058  print('Sequence for video file "'+filename+'" added.')
1059  i += 1
1060  return True
1061 
1062 def dataEntryAnalysis(analyses, local):
1063  from collections import OrderedDict
1064  entries = DataEntry('Enter analysis details:', submit_label=local['UI_form_add'], rows=OrderedDict([('name',''), ('site_analyses','[]')]))
1065  if(not entries.submit): return False
1066  analyses.new(sqlFields=entries.getEntries())
1067  return True
1068 
1069 def dataEntrySA(sites, site_analyses, local):
1070  from collections import OrderedDict
1072  missings = []
1073  for site in sites:
1074  for cam in site:
1075  if(cam.idx not in tvaLib.flatten_list([sa.camIds for sa in site_analyses])): missings.append(site.name+'/'+cam.name)
1076  if(missings):
1077  print 'The following site/camera views are not associated with any site-analysis and should be added:'
1078  for missing in missings: print ''.ljust(4,' ')+missing
1079 
1080  camIds = OrderedDict()
1081  for site in sites:
1082  for cam in site:
1083  camIds.update({((site.name[:15] + '...') if len(site.name) > 15 else site.name)+'/'+cam.name:cam.idx})
1084  entries = DataEntry('Enter site analysis details:', submit_label=local['UI_form_add'], rows=OrderedDict([('name',''), ('startTimes','all'), ('endTimes','all'), ('configurationFilename',''), ('description',''), ('cm_bounds',''), ('max_speed','80'), ('hex_grid_x','60'), ('hex_grid_y','60')]), multiSelectionListBoxes=OrderedDict([('camIds',camIds)]))
1085  if(not entries.submit): return False
1086  if(not entries.getMSLBs()['camIds']): return False
1087  if(not entries.getEntries()['name']):
1088  cam_names = []
1089  for camIdx in tvaLib.Parse.list1D(entries.getMSLBs()['camIds']):
1090  for site in sites:
1091  for cam in site:
1092  if(cam.idx == camIdx): cam_names.append(site.name+'/'+cam.name)
1093  entries.setEntry('name', ','.join(cam_names))
1094  site_analyses.new(sqlFields=tvaLib.mergeDicts(entries.getEntries(),entries.getMSLBs()), sites=sites)
1095  return True
def drawButtonFrame(self, cancel_label, submit_label)
Definition: draw.py:926
site_option_menu
Definition: draw.py:837
def getCamViewSelection(self)
Definition: draw.py:875
camview_option
Definition: draw.py:838
mslbVals
Definition: draw.py:819
mslbScrolls
Definition: draw.py:820
def drawZone(objects, sites, site_analyses, siteIx=0, camIx=0, saIx=0, draw_max_traj=200, local=None, autoUpdate=True, figsize=[15.0)
Definition: draw.py:101
style
Style.
Definition: draw.py:774
labels
Handle regular entries.
Definition: draw.py:792
site_option
Definition: draw.py:835
def join(obj1, obj2, postSmoothing=True)
Definition: tools_obj.py:816
ddlabels
Handle dropdown menu items.
Definition: draw.py:803
def dataEntrySequences(sites, local, allowed_extensions=['.mp4', avi)
Definition: draw.py:1023
def getSelectedSite(self)
Definition: draw.py:877
sequence_option_menu
Definition: draw.py:842
entryVals
Definition: draw.py:793
config
Data.
Definition: draw.py:785
sites_selection_depth
Definition: draw.py:787
def getSequenceSelection(self)
Definition: draw.py:876
def __init__(self, args, kwargs)
Definition: draw.py:901
def autoSearchStartTime(self)
Definition: draw.py:950
def dataEntryAnalysis(analyses, local)
Definition: draw.py:1064
def updateCameraOptions(self, index, value, op)
Definition: draw.py:883
def setEntry(self, label, value)
Definition: draw.py:879
def dataEntrySite(sites, config, local)
Definition: draw.py:983
def drawMastCalibration(objects, sites, siteIx, camIx, autoUpdate=True, local=None, figsize=[15.0)
Definition: draw.py:700
sites
Definition: draw.py:786
mslblabels
Handle multi-selection lists.
Definition: draw.py:818
def decrementStartTime(self, duration_h=1.0)
Definition: draw.py:943
def drawButtonFrame(self, cancel_label, submit_label)
Definition: draw.py:893
Data entering tools.
Definition: draw.py:753
def selectOrigin(self)
Definition: draw.py:912
sequence_option
Definition: draw.py:841
def drawAlignSnap(objects, site, draw_max_traj=200, local=None, figsize=[15.0)
Definition: draw.py:590
def dataEntryCamType(cameraTypes, local)
Definition: draw.py:976
def drawObjHighlight(objects, camera, draw_max_traj=200, local=None, arrow_every_dist=8.0, highlight_thickness=3.0, align_highlight_steps=5, figsize=[15.0)
Definition: draw.py:620
def incrementStartTime(self, duration_h=1.0)
Definition: draw.py:936
def updateSequenceOptions(self, index, value, op)
Definition: draw.py:888
def autoBounds(objects, site_analyses, saIx=0, centile=2.0, max_outside_distance=10.0, inside_buffer=5.0, autoUpdate=True, verbose=0)
Definition: draw.py:448
def getDropDowns(self)
Definition: draw.py:872
def drawButtonFrame(self, cancel_label, submit_label)
Definition: draw.py:904
submit
Definition: draw.py:789
def cancelCallback(self)
Definition: draw.py:869
def dataEntryCameraView(sites, config, cameraTypes, local)
Definition: draw.py:998
def drawBounds(objects, sites, site_analyses, siteIx=0, camIx=0, saIx=0, draw_max_traj=200, local=None, autoUpdate=True, figsize=[15.0)
Definition: draw.py:421
def getEntries(self)
Definition: draw.py:871
def drawLoops(objects, site_analyses, saIx=0, draw_max_traj=200, radius=3, colour='#D63E33', local=None, autoUpdate=True, figsize=[15.0)
Definition: draw.py:484
Definition: draw.py:900
Definition: draw.py:920
ddVals
Definition: draw.py:804
def drawMask(objects, sites, siteIx=0, camIx=0, fileIx=0, draw_max_traj=200, local=None, alternateView=False, autoUpdate=True, figsize=[15.0)
Definition: draw.py:43
def dataEntrySA(sites, site_analyses, local)
Definition: draw.py:1071
allowed_extensions
Definition: draw.py:922
def submitCallback(self)
Definition: draw.py:866
def drawAlign(objects, sites, siteIx=0, draw_max_traj=200, local=None, autoUpdate=True, minPointDist=0.5, connectorSearchDistance=2.5, intersectionDistanceFactor=1.5, alpha=0.5, zorder=21, figsize=[15.0)
Definition: draw.py:142
def getSelectedCamView(self)
Definition: draw.py:878
root
Definition: draw.py:771
ttk_style
Definition: draw.py:775
def getSiteSelection(self)
Definition: draw.py:874
def getHomography(homoSrcFrame='', orthoSrcPath='', savePath='homography.txt', tsaiCameraSrcPath='', nPoints=4, unitsPerPixel=0.1, videoPts=None, worldPts=None, pointDrawSize=3, pointTargetDrawSize=7, pointTargetDrawThickness=2, maxDisplayResolutionX=900, fig_name='', verbose=0)
Definition: tools_obj.py:403
Definition: vis.py:1
def tellMe(s, console='', target=False)
Drawinbg tools.
Definition: draw.py:33
buttonframe
Definition: draw.py:895
def __init__(self, instructions='', instructions2='', submit_label='Enter', cancel_label='Cancel', rows={}, sites=None, config=None, sites_selection_depth=1, dropdowns={}, multiSelectionListBoxes={})
Definition: draw.py:754
camview_option_menu
Definition: draw.py:840
def drawMastLocation(sites, siteIx, camIx, autoUpdate=True, local=None, figsize=[15.0)
Definition: draw.py:680
Definition: filt.py:1
def getMSLBs(self)
Definition: draw.py:873
mslbValsContents
Definition: draw.py:821
def drawTransformation(objects, sequence, draw_max_traj=200, figsize=[15.0)
Definition: draw.py:521