Baby Language Lab Scripts
A collection of data processing tools.
 All Classes Namespaces Files Functions Variables Pages
main_window.py
Go to the documentation of this file.
1 from gi.repository import Gtk as gtk
2 from gi.repository import Gdk as gdk
3 from gi.repository import GdkPixbuf
4 import csv
5 import logging
6 import traceback
7 import re
8 import os
9 import subprocess
10 from datetime import datetime
11 
12 from db.bll_database import DBConstants
13 from db.csv_database import CSVDatabase
14 from utils.ui_utils import UIUtils
15 from utils.progress_dialog import ProgressDialog
16 from utils.backend_utils import BackendUtils
17 from utils.praat_interop import PraatInterop
18 from parsers.wav_parser import WavParser
19 from ui.data_viewer_app.filter_window import FilterWindow
20 
21 class MainWindow():
22  #if none of these match, then string is used
23  DATA_TYPE_REGEXS = {
24  float: r'^-?\d+(\.\d+)?$',
25  bool: r'^True|False$',
26  }
27 
28  START_COL_NAMES = ['Start Time', 'Wav.Begin']
29  END_COL_NAMES = ['End Time', 'Wav.End']
30  DUR_COL_NAMES = ['Duration', 'Segment_Duration']
31  EL_TIME_COL_NAMES = ['Elapsed_Time']
32 
33  def __init__(self):
34  self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
35  self.window.set_title('Data Viewer')
36  self.window.connect('destroy', lambda w: self._clean_up())
37  self.window.set_border_width(10)
38  self.window.set_default_size(800, 600)
39  self.logger = logging.getLogger(__name__)
40 
41  self.csv_filename = UIUtils.open_file('Select csv file', filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])
42  self.window.set_title('%s - %s' % (self.window.get_title(), os.path.basename(self.csv_filename)))
43  if not self.csv_filename:
44  exit(0)
45 
47  self.wav_parser = None
48  if self.wav_filename:
49  self.wav_parser = WavParser(self.wav_filename)
50  # if not self.wav_filename:
51  # exit(0)
52 
53  self.sound_col_fcns = None
54 
55  col_datatypes, col_headers = self._get_col_info()
56 
57  db = self._build_db(col_datatypes, col_headers)
58 
59  model = self._get_model(col_datatypes)
60  treeview = self._get_treeview(model, col_headers, db)
61 
62  self.filters = []
63  self.where_cond = None
64  self.where_params = []
65  self.sort_order = None
66  self.sort_col = None
67  self._populate_model(db, model)
68 
69  toolbar = self._build_toolbar(db, treeview, col_headers)
70  scrolled_win = gtk.ScrolledWindow()
71  scrolled_win.set_policy(gtk.PolicyType.AUTOMATIC, gtk.PolicyType.AUTOMATIC)
72  scrolled_win.add(treeview)
73 
74  vbox = gtk.VBox()
75  vbox.pack_start(toolbar, False, False, 0)
76  vbox.pack_start(scrolled_win, True, True, 0)
77  self.window.add(vbox)
78 
79  self.window.show_all()
80 
81  def _get_wav_filename(self):
82  wav_filename = None
83 
84  #try to find a wav file with the same name as the csv file
85  if self.csv_filename.lower().endswith('.csv'):
86  default_filename = self.csv_filename[:-3] + 'wav'
87  if os.path.exists(default_filename):
88  wav_filename = default_filename
89 
90  #if the above didn't succeed, prompt the user for a filename
91  if not wav_filename:
92  wav_filename = UIUtils.open_file(title='Select wav file', filters=[UIUtils.WAV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])
93 
94  return wav_filename
95 
96  def _get_csv_delim(self, csv_file):
97  pos = csv_file.tell()
98  delim = ',' if csv_file.next().find(',') > -1 else '\t'
99  csv_file.seek(pos)
100 
101  return delim
102 
103  def _get_col_info(self):
104  csv_file = open(self.csv_filename, 'rb')
105  delim = self._get_csv_delim(csv_file)
106  reader = csv.reader(csv_file, delimiter=delim)
107  headers = reader.next()
108 
109  line = reader.next()
110  datatypes = [int] #for hidden id column
111  headers = ['id'] + headers
112  for col in line:
113  datatypes.append(self._get_datatype(col))
114 
115  csv_file.close()
116 
117  #append marked column, if not already present
118  if datatypes and not datatypes[-1] == bool:
119  datatypes.append(bool)
120  headers.append('Marked')
121 
122  return datatypes, headers
123 
124  def _get_datatype(self, col_val):
125  col_type = None
126  i = 0
127  regex_keys = MainWindow.DATA_TYPE_REGEXS.keys()
128  while not col_type and i < len(regex_keys):
129  if re.match(MainWindow.DATA_TYPE_REGEXS[regex_keys[i]], col_val):
130  col_type = regex_keys[i]
131  i += 1
132 
133  if not col_type:
134  col_type = str
135 
136  return col_type
137 
138  def _get_model(self, col_datatypes):
139  #tell the treeview that all columns are strings (even those that are really floats). This way, floats and numbers will appear exactly as they do in the input spreadsheet, and not using gtk's default precision (i.e. we don't have to do any number formatting / rounding). Since the db has the actual types for its columns, and it is in change of filtering and sorting (NOT the UI), this works out fine.
140  #The exception is the last column ('marked'); it requires type bool for the checkboxes to function properly in the ui
141 
142  col_types = [int] + [str] * (len(col_datatypes) - 2) + [bool]
143 
144  return gtk.ListStore(*col_types)
145 
146  def _get_treeview(self, model, headers, db):
147  treeview = gtk.TreeView(model)
148 
149  #force the row selection colours to stay the same even when the treeview is deselected (otherwise the selection is hard to see - and seeing this at all times is important in this app's use cases)
150  treeview.modify_base(gtk.StateFlags.ACTIVE, gdk.Color.parse('#3399FF')[1])
151  treeview.modify_text(gtk.StateFlags.ACTIVE, gdk.Color.parse('#FFFFFF')[1])
152 
153  col_index = 0
154  col = gtk.TreeViewColumn(headers[0], gtk.CellRendererText(), text=col_index)
155  col.set_visible(False)
156  treeview.append_column(col)
157 
158  for col_index in range(1, len(headers) - 1):
159  col = gtk.TreeViewColumn(headers[col_index], gtk.CellRendererText(), text=col_index)
160  col.set_resizable(True)
161  col.set_reorderable(True)
162  col.set_visible(col.get_title() != '' and col.get_title() != '# segments')
163  if col.get_visible():
164  col.set_clickable(True)
165  col.connect('clicked', self._toggle_sort_column, treeview, db, col_index)
166  treeview.append_column(col)
167 
168  toggleRenderer = gtk.CellRendererToggle()
169  toggleRenderer.connect('toggled', self._toggle_renderer_callback, model, col_index + 1, db)
170  mark_col = gtk.TreeViewColumn(headers[col_index + 1], toggleRenderer, active=col_index + 1)
171  mark_col.set_resizable(True)
172  mark_col.set_reorderable(True)
173  mark_col.set_clickable(True)
174  mark_col.connect('clicked', self._toggle_sort_column, treeview, db, col_index + 1)
175  mark_col.set_alignment(0.5) #position the title in the middle, since this column's wider than the others (since it's the last visible column)
176  treeview.append_column(mark_col)
177 
178  return treeview
179 
180  def _toggle_sort_column(self, treeview_col, treeview, db, treeview_col_index):
181  #clear any previous sort indicators on all columns except the one we care about
182  for col in treeview.get_columns():
183  if col != treeview_col:
184  col.set_sort_indicator(False)
185 
186  if treeview_col.get_sort_indicator():
187  sort_order = treeview_col.get_sort_order()
188 
189  if sort_order == gtk.SortType.ASCENDING:
190  self.sort_order = CSVDatabase.ORDER_DIRS.DESC
191  self.sort_col = treeview_col_index
192 
193  treeview_col.set_sort_order(gtk.SortType.DESCENDING)
194 
195  elif sort_order == gtk.SortType.DESCENDING:
196  self.sort_order = None
197  self.sort_col = None
198 
199  treeview_col.set_sort_indicator(False)
200  else:
201  self.sort_order = CSVDatabase.ORDER_DIRS.ASC
202  self.sort_col = treeview_col_index
203 
204  treeview_col.set_sort_indicator(True)
205  treeview_col.set_sort_order(gtk.SortType.ASCENDING)
206 
207  self._populate_model(db, treeview.get_model())
208 
209  #for debugging
210  def _print_model(self, model):
211  for row in model:
212  s = ''
213  for item in row:
214  s += str(item) + ' '
215  print s
216 
217  def _toggle_renderer_callback(self, renderer, path, model, col_index, db):
218  if path is not None:
219  model[path][col_index] = not model[path][col_index]
220  db.csv_update_by_index([model.get_n_columns() - 1], where_cond='id=?', params=[int(model[path][col_index]), model[path][0]])
221 
222  def _build_db(self, col_datatypes, col_headers):
223  csv_file = open(self.csv_filename, 'rb')
224  delim = self._get_csv_delim(csv_file)
225  lines = csv_file.readlines()
226  reader = csv.reader(lines, delimiter=delim)
227  header_row = reader.next() #skip headers row
228 
229  db = CSVDatabase(col_headers, col_datatypes)
230 
231  progress_dialog = ProgressDialog(title='Loading file', phases=['Loading file...'])
232  progress_dialog.show()
233 
234  num_rows = len(lines) - 1 #subtract one for header
235 
236  done = False
237  i = 0
238  while i < num_rows and not done:
239  row = reader.next()
240 
241  #if this file already contains counts at the end (has already been exported), skip those rows
242  done = row and (row[0].startswith('File Stats') or row[0].startswith('Count of'))
243  if not done:
244  #if we have a marked column, use it - otherwise, append one
245  if re.match(MainWindow.DATA_TYPE_REGEXS[bool], row[-1]):
246  row = row[:-1] + [int(bool(row[-1] == 'True'))]
247  else:
248  row = row + [0]
249 
250  db.csv_insert([i + 1] + row)
251 
252  if (i % 10):
253  progress_dialog.set_fraction(float(i + 1) / float(num_rows))
254 
255  i += 1
256 
257  progress_dialog.ensure_finish()
258  csv_file.close()
259 
260  return db
261 
262  def _populate_model(self, db, model):
263  model.clear()
264 
265  #order_by_indices = None
266  #order_by_dirs = None
267  order_by = None
268  if self.sort_col != None and self.sort_order != None:
269  order_by = '%s%s %s' % (CSVDatabase.COL_PREFIX, self.sort_col, self.sort_order)
270  # order_by_indices = [self.sort_col]
271  # order_by_dirs = [self.sort_order]
272 
273  #print self.where_cond, self.where_params, model.get_n_columns()
274  rows = db.select(
275  CSVDatabase.TABLE_NAME,
276  [('%s%d' % (CSVDatabase.COL_PREFIX, i)) for i in range(model.get_n_columns())],
277  where_cond=self.where_cond,
278  params=self.where_params,
279  order_by=order_by
280  )
281 
282  #rows = db.csv_select_by_index(where_cond=self.where_cond, params=self.where_params, order_by_indices=order_by_indices, order_by_dirs=order_by_dirs)
283  for cur_row in rows:
284  cur_row = list(cur_row)
285 
286  for i in range(1, len(cur_row) - 1):
287  cur_row[i] = str(cur_row[i])
288  cur_row[-1] = bool(cur_row[-1])
289 
290  model.append(cur_row)
291 
292  def _filter_callback(self, filters, db, model, search_entry, col_headers):
293  #Filters will be None if the user clicked cancel in the FilterWindow
294  if filters != None:
295  self.filters = filters
296  where_cond, where_params = FilterWindow.get_sql_where_cond(self.filters)
297  self.where_cond = where_cond
298  self.where_params = where_params
299  self._populate_model(db, model)
300 
301  desc = FilterWindow.get_filters_desc(filters, col_headers)
302  search_entry.set_text(desc)
303 
304  def _update_filters(self, db, model, col_headers, search_entry):
305  FilterWindow(self.filters, col_headers, lambda filters: self._filter_callback(filters, db, model, search_entry, col_headers))
306 
307  def _clean_up(self):
308  if self.wav_parser:
309  self.wav_parser.close()
310 
311  gtk.main_quit()
312 
313  def _build_toolbar(self, db, treeview, col_headers):
314  toolbar = gtk.Toolbar()
315 
316  clear_img_path = UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.CLEAR, UIUtils.BUTTON_ICON_SIZES.PX16)
317  #clear_pixbuf = gtk.gdk.pixbuf_new_from_file(clear_img_path)
318  clear_pixbuf = GdkPixbuf.Pixbuf.new_from_file(clear_img_path)
319 
320  search_entry = gtk.Entry()
321  search_entry.set_sensitive(False)
322  search_entry.set_text(FilterWindow.get_filters_desc(self.filters, col_headers))
323 
324  filter_button = UIUtils.create_button('Filters', UIUtils.BUTTON_ICONS.FILTER, UIUtils.BUTTON_ICON_SIZES.PX16)
325  filter_button.connect('clicked', lambda w: self._update_filters(db, treeview.get_model(), col_headers, search_entry))
326 
327  play_button = UIUtils.create_button('Play', UIUtils.BUTTON_ICONS.PLAY, UIUtils.BUTTON_ICON_SIZES.PX16)
328  play_button.connect('clicked', lambda w: self._play_selected_row(col_headers, treeview))
329  praat_button = UIUtils.create_button('Praat', UIUtils.BUTTON_ICONS.PRAAT, UIUtils.BUTTON_ICON_SIZES.PX16)
330  praat_button.connect('clicked', lambda w: self._open_in_praat(col_headers, treeview))
331 
332  export_button = UIUtils.create_button('Export', UIUtils.BUTTON_ICONS.EXPORT, UIUtils.BUTTON_ICON_SIZES.PX16)
333  export_button.connect('clicked', lambda w: self._export(treeview, col_headers, db))
334 
335  context_label = gtk.Label('Context')
336  context_adj = gtk.Adjustment(value=0, lower=0, upper=99, step_increment=1)
337  self.context_spinner = gtk.SpinButton()
338  self.context_spinner.set_adjustment(context_adj)
339  self.context_spinner.set_numeric(True)
340 
341  spacer = gtk.SeparatorToolItem()
342  spacer.set_draw(False) #don't draw a vertical line
343  spacer.set_expand(True)
344 
345  filter_label = gtk.Label('Filter state:')
346 
347  for widget in [filter_label, search_entry, filter_button, praat_button, play_button, self.context_spinner, context_label]:
348  tool_item = gtk.ToolItem()
349  tool_item.add(widget)
350  if widget == search_entry:
351  tool_item.set_expand(True)
352  toolbar.insert(tool_item, -1)
353 
354  toolbar.insert(spacer, -1)
355 
356  tool_item = gtk.ToolItem()
357  tool_item.add(export_button)
358  toolbar.insert(tool_item, -1)
359 
360  return toolbar
361 
362  #Takes a time string in one of two formats, and returns a float set to the absolute number of seconds it represents.
363  #time_str must be either a string in any of the formats accepted by BackendUtils.time_str_to_float(), or a stringified float.
364  # (e.g. ''00:00:3.45 or '3.45')
365  def _get_abs_time(self, time_str):
366  sec = None
367 
368  if ':' in time_str:
369  sec = BackendUtils.time_str_to_float(time_str)
370  else:
371  sec = float(time_str)
372 
373  return sec
374 
375  def _get_sel_row_clip_info(self, col_headers, treeview):
376  start = None
377  end = None
378 
379  model, it = treeview.get_selection().get_selected()
380  if it:
381  #find start and end column indices (if not already found)
382  if self.sound_col_fcns == None:
383  start_index, end_index, dur_index, el_time_index = self._get_sound_col_indices(col_headers)
384 
385  if start_index > -1 and end_index > -1:
386  self.sound_col_fcns = (
387  lambda model, it: self._get_abs_time(model.get_value(it, start_index)),
388  lambda model, it: self._get_abs_time(model.get_value(it, end_index)),
389  )
390 
391  elif dur_index > -1 and el_time_index > -1:
392  self.sound_col_fcns = (
393  lambda model, it: self._get_abs_time(float(model.get_value(it, el_time_index))),
394  lambda model, it: self._get_abs_time(float(model.get_value(it, el_time_index)) + float(model.get_value(it, dur_index))),
395  )
396 
397  else:
398  error_msg = 'The program was unable to derive the sound clip start and end times from the columns.\n'
399  error_msg += '\nColumn headers must include:\n'
400  error_msg += '-One start name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.START_COL_NAMES]))
401  error_msg += '-One end name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.END_COL_NAMES]))
402  error_msg += '\nOr alternatively:\n'
403  error_msg += '-One duration name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.DUR_COL_NAMES]))
404  error_msg += '-One elapsed time name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.EL_TIME_COL_NAMES]))
405 
406  error_msg += '\nPlease make sure your input spreadsheet contains one of these pairs.'
407 
408  UIUtils.show_message_dialog(error_msg)
409 
410  if self.sound_col_fcns != None and self.wav_filename:
411  try:
412  start = float( self.sound_col_fcns[0](treeview.get_model(), it) )
413  end = float( self.sound_col_fcns[1](treeview.get_model(), it) )
414  except ValueError as err:
415  UIUtils.show_message_dialog('The program was unable to determine start and end times for this row.')
416  start = None
417  end = None
418  print err
419 
420  else:
421  UIUtils.show_no_sel_dialog()
422 
423  return start, end
424 
425  def _open_in_praat(self, headers, treeview):
426  start, end = self._get_sel_row_clip_info(headers, treeview)
427 
428  if self.wav_filename and start != None and end != None:
429  PraatInterop.open_praat()
430  PraatInterop.send_commands(PraatInterop.get_open_clip_script(start, end, self.wav_filename))
431 
432  def _play_selected_row(self, col_headers, treeview):
433  #this populates self.wav_parser and self.wav_filename, if the file exists
434  start, end = self._get_sel_row_clip_info(col_headers, treeview)
435 
436  if self.wav_parser and start != None and end != None:
437  start = max(0, start - self.context_spinner.get_value())
438  end = min(self.wav_parser.get_sound_len(), end + self.context_spinner.get_value())
439  self.wav_parser.play_clip(start, end)
440 
441  #return the focus to the currenly selected row in the treeview, so the user can immediately press the down arrow to move on to the next row
442  treeview.grab_focus()
443 
444  def _find_col(self, header_dict, key_list):
445  index = -1
446  i = 0
447 
448  while index < 0 and i < len(key_list):
449  if key_list[i] in header_dict:
450  index = header_dict[ key_list[i] ]
451  i += 1
452 
453  return index
454 
455  def _get_sound_col_indices(self, headers):
456  header_dict = dict( zip(headers, range(len(headers))) )
457 
458  start = self._find_col(header_dict, MainWindow.START_COL_NAMES)
459  end = self._find_col(header_dict, MainWindow.END_COL_NAMES)
460  dur = self._find_col(header_dict, MainWindow.DUR_COL_NAMES)
461  el_time = self._find_col(header_dict, MainWindow.EL_TIME_COL_NAMES)
462 
463  return (start, end, dur, el_time)
464 
465  def _export(self, treeview, col_headers, db):
466  write_filename, open_now = UIUtils.save_file(filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER], open_now_opt=True)
467  if write_filename: #if they didn't click cancel
468  #lag_time_cutoff = float( UIUtils.show_entry_dialog(None, 'Lag time cutoff for counts: ', default_text='2', validate_regex=r'^-?\d+(\.\d+)?$', invalid_msg='Please enter a number.') )
469  lag_time_cutoff = 2.0
470 
471  if write_filename and lag_time_cutoff != None: #if user did not click cancel (note: lag time cutoff could be 0)
472  if not write_filename.lower().endswith('.csv'):
473  write_filename += '.csv'
474 
475  try:
476  csv_file = open(write_filename, 'wb')
477  writer = csv.writer(csv_file, quoting=csv.QUOTE_ALL)
478 
479  cols = treeview.get_columns()
480  visible_col_indices = filter(lambda i: cols[i].get_visible(), range(len(cols)))
481 
482  filtered_headers = [col_headers[i] for i in visible_col_indices]
483  writer.writerow(filtered_headers)
484 
485  progress_dialog = ProgressDialog(title='Exporting to file', phases=['Exporting...'])
486  progress_dialog.show()
487 
488  num_rows = len(treeview.get_model())
489  row_index = 1 #this is awkward, but there is no way to pull all the rows out of the model in one shot, so we have to use a non-indexed for loop and manually track the index
490  for row in treeview.get_model():
491  filtered_row = [row[i] for i in visible_col_indices]
492  writer.writerow(filtered_row)
493 
494  progress_dialog.set_fraction(float(row_index) / float(num_rows))
495  row_index += 1
496 
497  # export_stats = self._get_export_stats(lag_time_cutoff, db, col_headers)
498  # if export_stats:
499  # for row in export_stats:
500  # writer.writerow(row)
501 
502  progress_dialog.ensure_finish()
503  csv_file.close()
504 
505  if open_now:
506  subprocess.Popen(['%s' % DBConstants.SETTINGS.SPREADSHEET_PATH, write_filename])
507  else:
508  UIUtils.show_message_dialog('Data exported successfully.')
509 
510 
511  #UIUtils.show_message_dialog('Data export completed.')
512  except Exception as err:
513  UIUtils.show_message_dialog('Unable to export data - please make sure the destination file is not already open in another program.')
514  raise err
515 
516  def _get_export_stats(self, lag_time_cutoff, db, col_headers):
517  stats = []
518 
519  cols = {
520  'Speaker': None,
521  'Sentence Type': None,
522  'Lag Time': None,
523  'Marked': None,
524  }
525 
526  for i in range(1, len(col_headers)):
527  if col_headers[i] in cols:
528  cols[col_headers[i]] = '%s%d' % (CSVDatabase.COL_PREFIX, i - 1)
529 
530  have_all_cols = reduce(lambda accum, key: cols[key] != None and accum, cols, True)
531 
532  if have_all_cols:
533  #Count of Marked MQ -> B
534  #"SELECT count(d1.id) FROM data d1, data d2 WHERE d1.id = d2.id - 1 AND d1.sentence_type='Q' AND d2.speaker='B' AND d1.marked=1 AND d1.lag_time < cutoff"
535  where_cond = "d1.id = d2.id - 1 AND d1.%s = 'M' AND d1.%s = 'Q' AND d2.%s = 'B' AND d1.%s = 1 AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Marked'], cols['Lag Time'])
536  rows = db.select(
537  '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
538  ['count(d1.id)'],
539  where_cond=where_cond,
540  params=[lag_time_cutoff],
541  )
542  if rows:
543  stats.append( ['Count of "Marked MQ -> B": %d' % (int(rows[0][0]))] )
544 
545  #Avg lag time on mother utterance for (MQ or MD) -> B
546  where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND (d1.%s = 'Q' OR d1.%s = 'D') AND d2.%s = 'B' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Sentence Type'], cols['Speaker'], cols['Lag Time'])
547  rows = db.select(
548  '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
549  ['avg(d1.%s)' % (cols['Lag Time'])],
550  where_cond=where_cond,
551  params=[lag_time_cutoff],
552  )
553  if rows:
554  val = str(None)
555  if len(rows[0]) and rows[0][0] != None:
556  val = '%0.3f' % (float(rows[0][0]))
557  stats.append( ['Avg Lag Time for "(MQ or MD) -> B": %s' % (val)] )
558 
559  #Avg of lag time (first in pair) on mother utterance for MQ -> MQ
560  where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND d1.%s = 'Q' AND d2.%s = 'M' AND d2.%s = 'Q' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Sentence Type'], cols['Lag Time'])
561  rows = db.select(
562  '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
563  ['avg(d1.%s)' % (cols['Lag Time'])],
564  where_cond=where_cond,
565  params=[lag_time_cutoff],
566  )
567  if rows:
568  val = str(None)
569  if len(rows[0]) and rows[0][0] != None:
570  val = '%0.3f' % (float(rows[0][0]))
571  stats.append( ['Avg Lag Time for "MQ -> MQ": %s' % (val)] )
572 
573  #Avg of lag time (first in pair) on mother utterance for MD -> MD
574  where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND d1.%s = 'D' AND d2.%s = 'M' AND d2.%s = 'D' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Sentence Type'], cols['Lag Time'])
575  rows = db.select(
576  '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
577  ['avg(d1.%s)' % (cols['Lag Time'])],
578  where_cond=where_cond,
579  params=[lag_time_cutoff],
580  )
581  if rows:
582  val = str(None)
583  if len(rows[0]) and rows[0][0] != None:
584  val = '%0.3f' % (float(rows[0][0]))
585  stats.append( ['Avg Lag Time for "MD -> MD": %s' % (val)] )
586 
587  #Avg of lag time (first in pair) on baby utterance for B -> (MQ or MD)
588  where_cond = "d1.id = d2.id - 1 AND d1.%s = 'B' AND d2.%s = 'M' AND d1.%s <= ?" % (cols['Speaker'], cols['Speaker'], cols['Lag Time'])
589  rows = db.select(
590  '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
591  ['avg(d1.%s)' % (cols['Lag Time'])],
592  where_cond=where_cond,
593  params=[lag_time_cutoff],
594  )
595  if rows:
596  val = str(None)
597  if len(rows[0]) and rows[0][0] != None:
598  val = '%0.3f' % (float(rows[0][0]))
599  stats.append( ['Avg Lag Time for "B -> (MQ or MD)": %s' % (val)] )
600 
601  if stats:
602  stats.insert(0, ['File Stats (lag time <= %0.3f)' % (lag_time_cutoff)])
603 
604  return stats
605