1 from gi.repository
import Gtk
as gtk
2 from gi.repository
import Gdk
as gdk
3 from gi.repository
import GdkPixbuf
10 from datetime
import datetime
24 float:
r'^-?\d+(\.\d+)?$',
25 bool:
r'^True|False$',
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']
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__)
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)))
57 db = self.
_build_db(col_datatypes, col_headers)
70 scrolled_win = gtk.ScrolledWindow()
71 scrolled_win.set_policy(gtk.PolicyType.AUTOMATIC, gtk.PolicyType.AUTOMATIC)
72 scrolled_win.add(treeview)
75 vbox.pack_start(toolbar,
False,
False, 0)
76 vbox.pack_start(scrolled_win,
True,
True, 0)
79 self.window.show_all()
85 if self.csv_filename.lower().endswith(
'.csv'):
87 if os.path.exists(default_filename):
88 wav_filename = default_filename
92 wav_filename = UIUtils.open_file(title=
'Select wav file', filters=[UIUtils.WAV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])
98 delim =
',' if csv_file.next().find(
',') > -1
else '\t'
106 reader = csv.reader(csv_file, delimiter=delim)
107 headers = reader.next()
111 headers = [
'id'] + headers
118 if datatypes
and not datatypes[-1] == bool:
119 datatypes.append(bool)
120 headers.append(
'Marked')
122 return datatypes, headers
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]
142 col_types = [int] + [str] * (len(col_datatypes) - 2) + [bool]
144 return gtk.ListStore(*col_types)
147 treeview = gtk.TreeView(model)
150 treeview.modify_base(gtk.StateFlags.ACTIVE, gdk.Color.parse(
'#3399FF')[1])
151 treeview.modify_text(gtk.StateFlags.ACTIVE, gdk.Color.parse(
'#FFFFFF')[1])
154 col = gtk.TreeViewColumn(headers[0], gtk.CellRendererText(), text=col_index)
155 col.set_visible(
False)
156 treeview.append_column(col)
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)
166 treeview.append_column(col)
168 toggleRenderer = gtk.CellRendererToggle()
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)
175 mark_col.set_alignment(0.5)
176 treeview.append_column(mark_col)
182 for col
in treeview.get_columns():
183 if col != treeview_col:
184 col.set_sort_indicator(
False)
186 if treeview_col.get_sort_indicator():
187 sort_order = treeview_col.get_sort_order()
189 if sort_order == gtk.SortType.ASCENDING:
193 treeview_col.set_sort_order(gtk.SortType.DESCENDING)
195 elif sort_order == gtk.SortType.DESCENDING:
199 treeview_col.set_sort_indicator(
False)
204 treeview_col.set_sort_indicator(
True)
205 treeview_col.set_sort_order(gtk.SortType.ASCENDING)
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]])
225 lines = csv_file.readlines()
226 reader = csv.reader(lines, delimiter=delim)
227 header_row = reader.next()
231 progress_dialog =
ProgressDialog(title=
'Loading file', phases=[
'Loading file...'])
232 progress_dialog.show()
234 num_rows = len(lines) - 1
238 while i < num_rows
and not done:
242 done = row
and (row[0].startswith(
'File Stats')
or row[0].startswith(
'Count of'))
245 if re.match(MainWindow.DATA_TYPE_REGEXS[bool], row[-1]):
246 row = row[:-1] + [int(bool(row[-1] ==
'True'))]
250 db.csv_insert([i + 1] + row)
253 progress_dialog.set_fraction(float(i + 1) / float(num_rows))
257 progress_dialog.ensure_finish()
275 CSVDatabase.TABLE_NAME,
276 [(
'%s%d' % (CSVDatabase.COL_PREFIX, i))
for i
in range(model.get_n_columns())],
284 cur_row = list(cur_row)
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])
290 model.append(cur_row)
296 where_cond, where_params = FilterWindow.get_sql_where_cond(self.
filters)
301 desc = FilterWindow.get_filters_desc(filters, col_headers)
302 search_entry.set_text(desc)
309 self.wav_parser.close()
314 toolbar = gtk.Toolbar()
316 clear_img_path = UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.CLEAR, UIUtils.BUTTON_ICON_SIZES.PX16)
318 clear_pixbuf = GdkPixbuf.Pixbuf.new_from_file(clear_img_path)
320 search_entry = gtk.Entry()
321 search_entry.set_sensitive(
False)
322 search_entry.set_text(FilterWindow.get_filters_desc(self.
filters, col_headers))
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))
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))
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))
335 context_label = gtk.Label(
'Context')
336 context_adj = gtk.Adjustment(value=0, lower=0, upper=99, step_increment=1)
338 self.context_spinner.set_adjustment(context_adj)
339 self.context_spinner.set_numeric(
True)
341 spacer = gtk.SeparatorToolItem()
342 spacer.set_draw(
False)
343 spacer.set_expand(
True)
345 filter_label = gtk.Label(
'Filter state:')
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)
354 toolbar.insert(spacer, -1)
356 tool_item = gtk.ToolItem()
357 tool_item.add(export_button)
358 toolbar.insert(tool_item, -1)
369 sec = BackendUtils.time_str_to_float(time_str)
371 sec = float(time_str)
379 model, it = treeview.get_selection().get_selected()
385 if start_index > -1
and end_index > -1:
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)),
391 elif dur_index > -1
and el_time_index > -1:
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))),
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]))
406 error_msg +=
'\nPlease make sure your input spreadsheet contains one of these pairs.'
408 UIUtils.show_message_dialog(error_msg)
414 except ValueError
as err:
415 UIUtils.show_message_dialog(
'The program was unable to determine start and end times for this row.')
421 UIUtils.show_no_sel_dialog()
429 PraatInterop.open_praat()
430 PraatInterop.send_commands(PraatInterop.get_open_clip_script(start, end, self.
wav_filename))
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)
442 treeview.grab_focus()
448 while index < 0
and i < len(key_list):
449 if key_list[i]
in header_dict:
450 index = header_dict[ key_list[i] ]
456 header_dict = dict( zip(headers, range(len(headers))) )
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)
463 return (start, end, dur, el_time)
466 write_filename, open_now = UIUtils.save_file(filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER], open_now_opt=
True)
469 lag_time_cutoff = 2.0
471 if write_filename
and lag_time_cutoff !=
None:
472 if not write_filename.lower().endswith(
'.csv'):
473 write_filename +=
'.csv'
476 csv_file = open(write_filename,
'wb')
477 writer = csv.writer(csv_file, quoting=csv.QUOTE_ALL)
479 cols = treeview.get_columns()
480 visible_col_indices = filter(
lambda i: cols[i].get_visible(), range(len(cols)))
482 filtered_headers = [col_headers[i]
for i
in visible_col_indices]
483 writer.writerow(filtered_headers)
485 progress_dialog =
ProgressDialog(title=
'Exporting to file', phases=[
'Exporting...'])
486 progress_dialog.show()
488 num_rows = len(treeview.get_model())
490 for row
in treeview.get_model():
491 filtered_row = [row[i]
for i
in visible_col_indices]
492 writer.writerow(filtered_row)
494 progress_dialog.set_fraction(float(row_index) / float(num_rows))
502 progress_dialog.ensure_finish()
506 subprocess.Popen([
'%s' % DBConstants.SETTINGS.SPREADSHEET_PATH, write_filename])
508 UIUtils.show_message_dialog(
'Data exported successfully.')
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.')
521 'Sentence Type':
None,
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)
530 have_all_cols = reduce(
lambda accum, key: cols[key] !=
None and accum, cols,
True)
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'])
537 '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
539 where_cond=where_cond,
540 params=[lag_time_cutoff],
543 stats.append( [
'Count of "Marked MQ -> B": %d' % (int(rows[0][0]))] )
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'])
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],
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)] )
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'])
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],
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)] )
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'])
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],
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)] )
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'])
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],
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)] )
602 stats.insert(0, [
'File Stats (lag time <= %0.3f)' % (lag_time_cutoff)])