-
Notifications
You must be signed in to change notification settings - Fork 2
/
plotmish.py
1273 lines (1165 loc) · 67.5 KB
/
plotmish.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
'''
Python alternative to plotnik
-Takes as input a .plt file (output of the FAAV-extract program) and a corresponding .wav file
-Displays all vowels according to F1 and F2
-Subsets of vowels can be displayed individually
-When play button is off; scroll over the vowel to display the relevant info about that vowel
-When play button is on: scrolling over will also play the vowel sound
-Hit enter while play is on to display other possible measurements of the vowel whose info is
currently being displayed (measurements are based on F1 and F2 values at 20%,35%,50%,65%,80%,
of the vowel's duration)
-choose a new measurement from the options displayed (black buttons) or keep the original
measurement (white button)
-all remeasurements are written to the log.txt file (or other file, specified with the -o flag)
requires SOX, Praat and pygame to run
If using the txtRead option then add the line:
candidates = T
in the config.txt file when running FAVE-extract
This will allow for the option to remeasure a vowel based on the number of formants
sox.sourceforge.net/
www.pygame.org/
by: Misha Schwartz
'''
import pygame, sys, argparse, os, re, csv, math, copy, time
from os.path import isdir, isfile, join, basename
from glob import glob
from subprocess import call, Popen
if isdir('plotmish'):
os.chdir('plotmish')
sys.path.append('support_scripts')
import pygbutton, inputbox, mapToCelex, plotmishClasses
from pygame.locals import *
try: import numpy as np
except: print >> sys.stderr, 'can\'t find numpy, will not be able to draw ellipses'
# parse in arguments from the command line
parser = argparse.ArgumentParser(description = 'Make blank textgrids for transcription of DR uncut clips')
parser.add_argument('vowels', metavar = 'vowel info', help = 'formant.txt file or folder containing many')
parser.add_argument('wav', metavar = 'wav file', help = '.wav file or folder containing many')
parser.add_argument('annotator', metavar = 'annotator', help = 'what\'s your name?')
parser.add_argument('-k', metavar = 'keyword', default = '*', help = 'keyword for selecting files in a directory, default is all files in the directory')
parser.add_argument('-o', metavar = 'output file', default = 'log', help = 'change folder to write log files to, default is plotmish/logs')
parser.add_argument('-a', action = 'store_true', help = 'append to older log file instead of writing over it')
parser.add_argument('-p', metavar = 'Praat', default = '/Applications/Praat.app', help = 'change path to Praat application, default is /Applications/Praat.app')
parser.add_argument('-f0', metavar = 'pitch tracks', default = '', help = 'folder containing pre-generated pitch tracks for each sound file')
parser.add_argument('-c',metavar = 'celex dict', default = '' , help = 'path to epw.cd celex dictionary file, will then run in celex mode, default is ARPABET mode')
args = parser.parse_args()
# check celex mode has access to celex dict
if args.c:
#set path to epw.cd celex dict
try:
mapToCelex.changeCelexPath(args.c)
except:
print >> sys.stderr , 'Cannot read %r \nPlease change file path' % args.c
#make new save file for celex prons if it doesn't already
#exist otherwise read from the save File:
celStore = join('support_scripts','celexStore.txt')
if isfile(celStore):
saveDict = mapToCelex.readSaved(celStore)
else:
mapToCelex.newSaveFile(celStore)
#set window sizes and frames per second
startWidth, startHeight = 820.0,850.0 #useful for defining location of objects when resizing
WINDOWWIDTH = int(startWidth)
WINDOWHEIGHT = int(startHeight)
#set fonts
myfont = pygame.font.SysFont('helvetica',20)
numFont = pygame.font.SysFont('helvetica',15)
miniFont = pygame.font.SysFont('helvetica',12)
textListFont = pygame.font.SysFont('courier',18)
smallButtonFont = pygame.font.SysFont('courier', 16)
boldButtonFont = pygame.font.SysFont('courier', 16, bold = True)
#pressed button lists
ctrl = [K_RCTRL, K_LCTRL]
shft = [K_RSHIFT, K_LSHIFT]
#set default black and white colours
WHITE = (255, 255, 255, 0)
BLACK = (0, 0, 0)
# dictionary of all vowel codes and corresponding colours
colours = {'AA': (127, 255, 212, 255),
'AE': (216, 191, 216, 255),
'AH': (124, 252, 0, 255),
'AO': (255, 0, 0, 255),
'AW': (107, 142, 35, 255),
'AY': (60, 179, 113, 255),
'EH': (255, 99, 71, 255),
'ER': (124, 252, 0, 255),
'EY': (139, 0, 139, 255),
'IH': (139, 69, 19, 255),
'IW': (139, 26, 26, 255),
'IY': (238, 154, 0, 255),
'OW': (0, 191, 255, 255),
'OY': (65, 105, 225, 255),
'UH': (255, 105, 180, 255),
'UW': (120, 120, 120, 255)}
# cmu/arpabet vowels
arpVowels = ('AA', 'IY', 'AE', 'EH', 'AH', 'UW', 'OY', 'AO',
'UH', 'IH', 'OW', 'EY', 'IW', 'AW', 'AY', 'ER')
def writeLogs(plot):
# write to new log file or append to old one
#only overwrite files if this is the first time writing this session
if plot.firstSave:
append = True
else:
append = False
plot.firstSave = True
# write to -corrLog.csv files
for f,writeThis in plot.allLogs.items():
if not writeThis: continue
# write header to log file
header = True if not isfile(join(args.o,basename(f).replace('.wav','-corrLog.csv'))) else False
if args.a or append:
log = csv.writer(open(join(args.o,basename(f).replace('.wav','-corrLog.csv')),'a'))
if header: log.writerow(['annotator','id','vowel','word','oldTime','time','duration (ms)','stress','maxForms','oldF1','F1','oldF2','F2'])
else:
log = csv.writer(open(join(args.o,basename(f).replace('.wav','-corrLog.csv')),'wb'))
log.writerow(['annotator','id','vowel','word','oldTime','time','duration (ms)','stress','maxForms','oldF1','F1','oldF2','F2'])
# write info to log file
for w in writeThis:
log.writerow([args.annotator]+[w[0].split('-')[-1]]+w[1:])
# get pitch files if -f0 flag is used
if args.f0: pitchFiles = glob(join(args.f0,'*.Pitch'))
# lists of headings from the config file
mandatoryHeadings = ['ARPABET','STRESS', 'WORD', 'F1', 'TIME', 'WORD PRONUNCIATION', 'MAX FORMANTS', 'BEGINNING', 'END', 'INDEX']
durHeadings = ['F1@20%', 'F2@20%' ,'F1@35%' , 'F2@35%' , 'F1@50%' , 'F2@50%' , 'F1@65%' , 'F2@65%' , 'F1@80%' , 'F2@80%']
optionalHeadings = ['DURATION', 'PRECEDING PHONE', 'FOLLOWING PHONE', 'CELEX', 'ALT MEASUREMENTS']
def readConfig():
# read from the config file to get the formant.txt file headings
configF = open('config.txt','rU')
configList = [c.split('#')[0].strip().split(':') if '#' in c else c.strip() for c in configF.readlines() if c.split('#')[0]]
configF.close()
configDict = {c[0].strip(): c[1].strip() for c in configList}
return configDict
def getFiles(sett):
# get all relevant files and pair them together
# as (wav file, formant.txt file)
sett.files = []
# if looking in directories
if isdir(args.wav) and isdir(args.vowels):
wFiles = glob(join(args.wav,'*'+args.k+'*.wav'))
vFiles = glob(join(args.vowels,'*'+args.k+'*'))
vFiles = [v for v in vFiles if '.plt' in v or 'formant.txt' in v]
for w in wFiles:
for v in vFiles:
if basename(w.replace('.wav','')) in basename(v):
sett.files += [(w,v)]
# if only one formant file given
elif isdir(args.wav):
wFiles = glob(join(args.wav,'*'+args.k+'*.wav'))
for w in wFiles:
if basename(w.replace('.wav','')) in basename(args.vowels):
sett.files += [(w,args.vowels)]
# if only one wav file given
elif isdir(args.vowels):
vFiles = glob(join(args.vowels,'*'+args.k+'*'))
for v in vFiles:
if basename(args.wav.replace('.wav','')) in basename(v):
sett.files += [(args.wav,v)]
# if only one formant file and only one wav file is given
else:
sett.files += [(args.wav,args.vowels)]
sett.files = [f for f in sett.files if 'txt' == f[1][-3:]]
assert sett.files, 'ERROR: no files found'
def calculateVowelLocation(f, plot):
# calculates the location to display the vowel based on tuple of (F1,F2)
wOffsetBig, wOffsetSmall = relativeSizing([20,15])
hOffsetBig, hOffsetSmall = relativeSizing([20,15], 'h')
x = (((plot.maxMin[3]-float(f[1])))/(plot.maxMin[3]-plot.maxMin[1]))*(plot.width - wOffsetBig)+wOffsetSmall
y = ((float(f[0]) - plot.maxMin[0])/(plot.maxMin[2]-plot.maxMin[0]))*(plot.height - hOffsetBig)+hOffsetSmall
return (x,y)
def makeVowelButton(v, plot, settings = {'w':8, 'h':8, 'bgcol':'new', 'fgcol':'new'}):
# makes a new vowel button for each new vowel
x,y = calculateVowelLocation((v.F1,v.F2), plot)
buttonRect = pygame.Rect(x,y, settings['w'], settings['h'])
buttonRect.center = (x,y)
button = pygbutton.PygButton(buttonRect,'►'.decode('utf8'),border = False)
button.bgcolor = colours[v.name] if settings['bgcol'] == 'new' else settings['bgcol']
button.fgcolor = colours[v.name] if settings['fgcol'] == 'new' else settings['fgcol']
return button
def getCelexVowel(word,cmu,vIndex):
# gets celex vowel for each vowel token according to the
# word it occurs in (not a one to one mapping)
allIndexes = [i for i,c in enumerate(cmu) if c[:-1] in arpVowels]
vIndex = allIndexes.index(vIndex)
try:
return saveDict[(word,''.join(cmu))][vIndex][1][0]
except:
try:
return mapToCelex.mapToCelex(word,cmu, makeSave = True)[vIndex][1][0]
except:
return ''
# place to write last file and frame checked
# when iterating over the pitch files
oldPitchFile = None
oldPitchFrame = 0
def getPitch(pitchList, timestamp, thisPitch):
# find pitch at time for a vowel token
global oldPitchFile, oldPitchFrame
dx = None
x1 = None
ceiling = None
for p in pitchList:
pvar = p.split('=')
if 'dx' in pvar[0]: dx = float(pvar[1].strip())
if 'x1' in pvar[0]: x1 = float(pvar[1].strip())
if 'ceiling' in pvar[0]:
ceiling = float(pvar[1].strip())
break
frame = '['+str(int(((float(timestamp)-x1)/dx)))+']'
foundFrame = False
pitch = 'Not Found'
if oldPitchFile != thisPitch:
oldPitchFrame = 0
startHere = oldPitchFrame
for i,p in enumerate(pitchList[startHere:]):
if 'frame' in p and frame in p: foundFrame = True
if foundFrame and 'candidate [1]' in p:
pitchNum = float(pitchList[i+startHere+1].split('=')[1].strip())
if pitchNum != 0 and pitchNum < ceiling:
pitch = str(int(pitchNum))
oldPitchFile = thisPitch
oldPitchFrame = i+oldPitchFrame
return pitch
def assignMaxMin(plot, allvowels):
# get maxMin value for plot
plot.maxF1 = max([float(a.F1) for a in allvowels])
plot.minF1 = min([float(a.F1) for a in allvowels])
plot.maxF2 = max([float(a.F2) for a in allvowels])
plot.minF2 = min([float(a.F2) for a in allvowels])
plot.maxMin = (plot.minF1, plot.minF2, plot.maxF1, plot.maxF2)
plot.defaultMaxMin = (plot.minF1, plot.minF2, plot.maxF1, plot.maxF2)
def primaryCmuPronDict():
# make a dictionary with the primary pronunciation for all
# words in cmu.txt. Primary pronunciation is the pron with the
# most segments and the least schwa (AH0) vowels
cmuF = open(os.path.join('support_scripts','cmu.txt'))
lines = cmuF.readlines()
cmuF.close()
cmuDict = {}
for l in lines:
word = l.split(' ',1)[0].strip()
pron = l.split(' ',1)[1].strip().split()
if word not in cmuDict:
cmuDict[word] = pron
elif len(pron) > len(cmuDict[word]):
cmuDict[word] = pron
elif len(pron) == len(cmuDict[word]) and len([p for p in pron if p in ['AH0', 'IH0']]) < len([p for p in cmuDict[word] if p in ['AH0', 'IH0']]):
cmuDict[word] = pron
return cmuDict
def getCmuPron(word, ind, pron, cmuDict):
# returns unreduced vowel (when possible)
try:
unreducedPron = ['']+cmuDict[word]+['']
except:
return 'NA'
pron = ['']+pron+['']
ind += 1
before, after = pron[ind-1], pron[ind+1]
for i in range(len(unreducedPron)):
if i == 0 or i == len(unreducedPron)-1:
continue
if unreducedPron[i-1] == pron[ind-1] and unreducedPron[i+1] == pron[ind+1] and len(unreducedPron[i]) == 3:
return unreducedPron[i][:2]
return 'NA'
def getVowels(plot, sett):
# reads all the vowels from the formant.txt file
allvowels = []
headings = readConfig()
revHeadings = {v:k for k,v in headings.items()}
if not args.c:
altDict = primaryCmuPronDict()
for f in sett.files:
# display which file is being processed TODO: speed this up
loadingMessage(plot.display, myfont, ['Loading Vowels', basename(f[0]).replace('.wav','')])
# get pitch track if f0 is specified as an argument (from the command line)
if args.f0:
thisPitch = [p for p in pitchFiles if basename(p).replace('.Pitch','') in basename(f[0])][0]
pitchList = [p.replace('\n','').strip() for p in open(thisPitch,'rU').readlines()]
vowelF = open(f[1],'r')
vowels = vowelF.readlines()
# find header line and column indexes
indexes = {i: None for i in mandatoryHeadings+durHeadings+optionalHeadings}
badFile = True
for i,line in enumerate(vowels):
headCount = 0
for head in mandatoryHeadings:
if headings[head] in line: headCount += 1
if headCount == len(mandatoryHeadings):
for j,l in enumerate(line.split('\t')):
try: indexes[revHeadings[l]] = j
except: pass
badFile = False
vowels = vowels[i+1:]
break
if badFile:
loadingMessage(plot.display, myfont, ['Mandatory Headings not found','for file: ', basename(f[0]).replace('.wav',''), 'check config.txt file'])
print >> sys.stderr, 'Mandatory Headings not found','for file: '+ basename(f[0]).replace('.wav','')
pygame.time.wait(2000)
continue
#get associated log file if available
try:
logF = open(join(args.o , basename(f[0]).replace('.wav','')+'-corrLog.csv'),'rU')
logR = list(csv.reader(logF))[1:]
logF.close()
except:
logR = None
for i,v in enumerate(vowels):
v = v.split('\t')
nV = plotmishClasses.vowel(float(v[indexes['F1']]),float(v[indexes['F2']]),f[0]) # initialize new vowel
# get other formant measurements from various points in the vowel duration
# (F1@20%, F2@20%, F1@35%, F2@35%, F1@50%, F2@50%, F1@65%, F2@65%, F1@80%, F2@80%)
nV.id = basename(f[0]).replace('.wav','')+'-'+str(i+1)
extraForms = [v[indexes[h]] for h in durHeadings]
if len(extraForms) == len(durHeadings):
for j in range(0,len(extraForms),2):
try: nV.durForms += [(round(float(extraForms[j]),1),round(float(extraForms[j+1]),1))]
except: continue
# allow this vowel to be remeasured using % duration if all formant measurements are accounted for
if len(nV.durForms) == 5: nV.remeasureOpts += ['dur']
# get other formant measurements from the same point with various max Formant settings (3,4,5,6)
if indexes['ALT MEASUREMENTS'] != None:
moreForms = [re.sub('[\[\]]','',m).split(',') for m in v[indexes['ALT MEASUREMENTS']].split('],[')]
for m in moreForms:
try:
temp = [tuple([round(float(n.strip()),1) for n in m[:2]])]
except:
assert False, 'ERROR:\tthe formant.txt files do not contain extra formant measurement info\n\t\t\tmake sure the config.txt file in FAVE-extract has the line:\n\t\t\tcandidates=T\n\t\t\tand then re-extract the formant values'
if len(temp[0]) != 2:
continue
nV.numForms += temp
# allow this vowel to be remeasured using max Formants if all formant measurements are accounted for
if len(nV.numForms) == 4: nV.remeasureOpts += ['num']
# get other values
cmuPron = [p.strip() for p in re.sub('[\[\]\']','',v[indexes['WORD PRONUNCIATION']]).split(',')]
nV.pPhone = v[indexes['PRECEDING PHONE']] if indexes['PRECEDING PHONE'] != None else '??'
nV.fPhone = v[indexes['FOLLOWING PHONE']] if indexes['FOLLOWING PHONE'] != None else '??'
nV.word = v[indexes['WORD']]
nV.name = v[indexes['ARPABET']]
vIndex = int(v[indexes['INDEX']])
if args.c:
nV.altVow = getCelexVowel(nV.word,cmuPron,vIndex) if indexes['CELEX'] == None else v[indexes['CELEX']]
if nV.altVow.strip() == '': nV.altVow = 'NA'
else:
nV.altVow = getCmuPron(nV.word, vIndex, cmuPron, altDict)
nV.time = v[indexes['TIME']]
if args.f0: nV.pitch = getPitch(pitchList, nV.time, thisPitch)
nV.maxForm = v[indexes['MAX FORMANTS']]
nV.stress = v[indexes['STRESS']]
nV.timeRange = (v[indexes['BEGINNING']],v[indexes['END']])
nV.duration = str(int(float(v[indexes['DURATION']])*1000)) if indexes['DURATION'] != None else '??'
nV.wFile = f[0]
if logR:
for line in logR:
if line[1].strip() == str(i+1):
try:
if not line[13].strip():
nV.alreadyCorrected = (line[5], line[8], line[10], line[12])
else:
nV.alreadyCorrected = 'removed'
except:
nV.alreadyCorrected = (line[5], line[8], line[10], line[12])
allvowels += [nV]
# save all translated words for future easy access
mapToCelex.writeSaved(celStore)
assignMaxMin(plot, allvowels)
for av in allvowels:
av.button = makeVowelButton(av, plot)
return allvowels
def confidenceEllipse(xyPoints,sdev,plot):
# make a confidence ellipse of the points currently plotted on the scree
# adapted from Jaime at:
#stackoverflow.com/questions/20126061/creating-a-confidence-ellipses-in-a-sccatterplot-using-matplotlib
x = [calculateVowelLocation(xy, plot)[0] for xy in xyPoints]
y = [calculateVowelLocation(xy, plot)[1] for xy in xyPoints]
angleAdjust = False if np.mean([p[1] for p in sorted(xyPoints)[:int(len(xyPoints)/2.0)]]) < np.mean([p[1] for p in sorted(xyPoints)[int(len(xyPoints)/2.0):]]) else True
mean = (np.mean(x),np.mean(y))
cov = np.cov(x, y)
lambda_, v = np.linalg.eig(cov)
lambda_ = np.sqrt(lambda_)
angle = np.rad2deg(np.arccos(v[0, 0]))
if angleAdjust: angle = 180-angle
width=lambda_[0]*sdev*2
height=lambda_[1]*sdev*2
plot.display.fill(WHITE)
ellipDim = pygame.Rect(0,0,width,height)
ellipDim.center = (WINDOWWIDTH/2.0,WINDOWHEIGHT/2.0)
pygame.draw.ellipse(plot.display,BLACK,ellipDim,2)
rot_img = pygame.transform.rotate(plot.display, angle)
img_rect = rot_img.get_rect()
img_rect.center = mean
plot.ellip = (rot_img, img_rect)
def relativeSizing(valList, orientation = 'w'):
#returns relative size for button location and sizes
#when resizing screen (orientation is either 'height' or 'width')
if isinstance(valList, int): #so that you can pass a single int instead of as a list
v = valList
return (v/startWidth)*WINDOWWIDTH if orientation == 'w' else (v/startHeight)*WINDOWHEIGHT
return [(v/startWidth)*WINDOWWIDTH if orientation == 'w' else (v/startHeight)*WINDOWHEIGHT for v in valList]
def makeButtons(oldButtons = {}):
# make permanent buttons (on the bottom of the screen)
vowButtons = []
onOffButtons = []
altVowButtons = []
#arpabet vowels
# front
frontArp = ['IY', 'IH', 'EY', 'EH', 'AE']
x, w = relativeSizing([20,30]) #get relative placement of x position and width of button
y, h, sp = relativeSizing([640,30,35], 'h') #get relative placement of y position, height and spacing between buttons
for i,c in enumerate(frontArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
vowButtons.append(button)
# central
x = relativeSizing(60)
y = relativeSizing(710, 'h')
button = pygbutton.PygButton((x, y, w, h), 'AH')
button.bgcolor = colours['AH'] if c not in oldButtons else oldButtons['AH'].bgcolor
button.font = smallButtonFont if 'AH' not in oldButtons else oldButtons['AH'].font
vowButtons.append(button)
# back
backArp = ['UW', 'UH', 'OW', 'AO', 'AA']
x = relativeSizing(100)
y = relativeSizing(640, 'h')
for i,c in enumerate(backArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
vowButtons.append(button)
# diphthongs and rhoticized
diphArp = ['AY','OY', 'AW', 'IW', 'ER']
x = relativeSizing(140)
for i,c in enumerate(diphArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
vowButtons.append(button)
# celex vowels
# front
x = relativeSizing(255)
y, sp = relativeSizing([640,40], 'h')
for i,c in enumerate(['i','I','1','E','{'] if args.c else frontArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
altVowButtons.append(button)
# central
x = relativeSizing(295)
if args.c:
for i,c in enumerate(['@','3','V','Q']):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
altVowButtons.append(button)
else:
y = relativeSizing(710, 'h')
button = pygbutton.PygButton((x, y, w, h), 'AH')
button.font = smallButtonFont if 'AH' not in oldButtons else oldButtons['AH'].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours['AH'] if 'AH' not in oldButtons else oldButtons['AH'].bgcolor
altVowButtons.append(button)
# back
x = relativeSizing(335)
y = relativeSizing(640, 'h')
for i,c in enumerate(['u','U','5','$','#'] if args.c else backArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
altVowButtons.append(button)
#diphthongs
x = relativeSizing(375)
for i,c in enumerate(['2','4','6'] if args.c else diphArp):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
altVowButtons.append(button)
if args.c:
x = relativeSizing(415)
for i,c in enumerate(['7','8','9','H','P']):
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
button.font = smallButtonFont if c not in oldButtons else oldButtons[c].font
if args.c: button.bgcolor = Color("lightskyblue")
else: button.bgcolor = colours[c] if c not in oldButtons else oldButtons[c].bgcolor
altVowButtons.append(button)
# union/intersect button
x = relativeSizing(190)
y = relativeSizing(720, 'h')
button = pygbutton.PygButton((x,y,w,h),'∩'.decode('utf8') if '∩'.decode('utf8') in oldButtons else 'U'.decode('utf8'))
button.bgcolor = Color('darkolivegreen2')
button.font = myfont
onOffButtons.append(button)
# side buttons
sideButtons = ['Show All', 'Clear', 'Play', 'Std Dev', ('Dur.Filter','Rmv.Dur.Filt') ,('Wrd.Filter', 'Rmv.Dur.Filt'),
('Zoom', 'Reset Zoom'),('RemeasureP', 'RemeasureF', 'Remeasure%'), 'Cancel', ('Saved', 'Save'), 'Undo', 'Check Last', ('Rmv. Bad', 'Rmv. OK'), 'Resume']
lowest = 0
x, w = relativeSizing([705,110])
y = relativeSizing(10, 'h')
for i,c in enumerate(sideButtons):
if isinstance(c, tuple):
c = [capt for capt in c if capt in oldButtons][0] if oldButtons else c[0]
print c
button = pygbutton.PygButton((x, y+(i*sp), w, h), c)
lowest = y+(i*sp)+sp
if c in oldButtons:
button.bgcolor = oldButtons[c].bgcolor
button.font = oldButtons[c].font
elif button.caption == 'Rmv. Bad':
button.bgcolor = Color('red')
else:
button.bgcolor = Color('darkolivegreen2')
onOffButtons.append(button)
#stress buttons
x, w, hzsp = relativeSizing([705,35,37]) #now also calculation for horizontal spacing (hzsp)
for i,c in enumerate(['1','2','0']):
button = pygbutton.PygButton((x+(i*hzsp),lowest, w, h), c)
button.bgcolor = Color('darkolivegreen4') if (c, 'stress') not in oldButtons else oldButtons[(c, 'stress')].bgcolor
onOffButtons.append(button)
return (vowButtons,onOffButtons,altVowButtons)
def loadingMessage(surface, font, message):
# display loading message
surface.fill(WHITE)
for i,m in enumerate(message):
mess = font.render(m,1,BLACK)
surface.blit(mess,(WINDOWWIDTH/2.0-(font.size(m)[0]/2.0),WINDOWHEIGHT/2.0-(font.size(m)[1]/2.0)+(i*(font.size(m)[1])+5)))
pygame.display.update()
def drawGrid(numFont, plot):
# draw grid and max/min over plot area
# horizontal lines are every 100 Hz
# vertical lines are every 50 Hz
# initialize start and end points and the distance between lines
vtSpace, vtOffset = relativeSizing([10, 50], 'h')
hzSpace, hzOffset = relativeSizing([10,100])
intervalH = int(((plot.height-vtSpace)/(plot.maxF1-plot.minF1))*vtOffset)
startH = int(((plot.height-vtSpace)/(plot.maxF1-plot.minF1))*(math.ceil(plot.minF1/vtOffset)*vtOffset - plot.minF1)+vtSpace)
startV = WINDOWWIDTH - int(((plot.width-hzSpace)/(plot.maxF2-plot.minF2))*(math.ceil(plot.minF2/hzOffset)*hzOffset - plot.minF2) + (WINDOWWIDTH-plot.width))
intervalV = int(((plot.width-hzSpace)/(plot.maxF2-plot.minF2))*hzOffset)
h,v = (0,0)
# draw horizontal lines
while True:
hlimit = startH + h*intervalH
if hlimit > plot.height: break
pygame.draw.line(plot.display,Color('grey87') ,(hzSpace,hlimit),(plot.width,hlimit))
h += 1
# draw vertical lines
while True:
vlimit = startV - v*intervalV
if vlimit < vtSpace: break
pygame.draw.line(plot.display,Color('grey87'),(vlimit,plot.height),(vlimit, vtSpace))
v += 1
# write max and min values for F1 and F2
fontMaxMin = [numFont.render(str(int(i)),1,BLACK) for i in plot.maxMin]
plot.display.blit(fontMaxMin[0],(plot.width-numFont.size(str(int(plot.minF1)))[0],numFont.size(str(int(plot.minF1)))[1]+vtSpace))
plot.display.blit(fontMaxMin[1],(plot.width-numFont.size(str(int(plot.minF2)))[0]-hzSpace,vtSpace))
plot.display.blit(fontMaxMin[2],(plot.width-numFont.size(str(int(plot.maxF1)))[0], plot.height-numFont.size(str(int(plot.minF1)))[1]))
hzSpace = relativeSizing(12)
plot.display.blit(fontMaxMin[3],(hzSpace, vtSpace))
def resize(tempMaxMin, plot):
# recalculate the location of all vowels when zooming
if tempMaxMin != plot.defaultMaxMin:
temp = [None,None,None,None]
temp[0] = (plot.maxF1-plot.minF1)*(tempMaxMin[0]/float(plot.height-10))+plot.minF1
temp[1] = plot.maxF2-((plot.maxF2-plot.minF2)*(tempMaxMin[1]/float(plot.width-10)))
temp[2] = (plot.maxF1-plot.minF1)*(tempMaxMin[2]/float(plot.height-10))+plot.minF1
temp[3] = plot.maxF2-((plot.maxF2-plot.minF2)*(tempMaxMin[3]/float(plot.width-10)))
plot.maxMin = tuple(temp)
plot.minF1, plot.minF2, plot.maxF1, plot.maxF2 = temp
else:
plot.maxMin = plot.defaultMaxMin
plot.minF1, plot.minF2, plot.maxF1, plot.maxF2 = plot.defaultMaxMin
# set the vowel buttons to their new location
for v in plot.vowButtons:
x,y = calculateVowelLocation((v.F1,v.F2),plot)
v.button.rect.center = (x,y)
# set the current vowel to it's new location
if plot.currentVowel:
x,y = calculateVowelLocation((plot.currentVowel.F1, plot.currentVowel.F2), plot)
plot.currentVowel.button.rect.center = (x,y)
def clearRange(tempMaxMin, reason, plot, within = None):
# clear a range of vowels that fall in tempMaxMin
# with a reason for their removal
# only remove vowels in the within list (this is used to only remove vowels currently
# displayed on the screen, not all vowels in the range)
if not within: within = plot.vowButtons # remove all vowels in range if within not set
# clear the vowel if it's in the range
tempVBL = []
for v in plot.vowButtons:
x,y = v.button.rect.center
if not (y > tempMaxMin[0] and x < tempMaxMin[1] and y < tempMaxMin[2] and x > tempMaxMin[3]) or v not in within:
tempVBL += [v]
else:
clear(v, reason, plot)
# remove current vowel if it falls in the range
if plot.currentVowel:
x,y = plot.currentVowel.button.rect.center
plot.currentVowel = plot.currentVowel if not (y > tempMaxMin[0] and x < tempMaxMin[1] and y < tempMaxMin[2] and x > tempMaxMin[3]) else None
if not plot.currentVowel: plot.textList = [] # if the current vowel is removed, stop displaying it's info
plot.vowButtons = tempVBL
def clear(vowel, reason, plot):
# write to log that a vowel has been removed with a given reason
because = 'unallowed variant' if not plot.remReason and not reason else plot.remReason+' '+reason
because = 'removed: '+because if not plot.remReason else because
newInfo = [str(wr) for wr in [vowel.id, vowel.name ,vowel.word,'NA',vowel.time,vowel.duration,vowel.stress,vowel.maxForm,'NA','NA','NA','NA',because]]
plot.allLogs[vowel.wFile] += [newInfo]
def updateDisplayed(displayed, button, plot):
# return the list of vowels to display
# according to the arp or celex vowel
# they represent
if button.caption in displayed:
button.font = smallButtonFont
button._update()
displayed.remove(button.caption)
else:
button.font = boldButtonFont
button._update()
displayed += [button.caption]
plot.textList = []
plot.filtered = []
plot.minDur = None
plot.filtWrd = None
return displayed
def writeInfo(v, plot):
# update textlist to write to the info square (lower right)
plot.textList = [('vowel: ' if args.c else 'arpabet: ')+v.name,('celex: ' if args.c else 'unreduced: ')+v.altVow,'F1: '+str(v.F1),
'F2: '+str(v.F2),'stress: '+v.stress,
'duration: '+v.duration+' ms','word: '+v.word,
'time: '+v.time,'environ.: '+v.pPhone+' v '+v.fPhone,
'max formants: '+v.maxForm]
if args.f0: plot.textList += ['pitch: '+ v.pitch]
def initializeSettings(sett, plot):
# set memory lists
sett.displayMemory = [plot.vowButtons] # list of all vowel plots up to the last save
sett.logMemory = [plot.allLogs] # list of all log dicts up to the last save
sett.praatLog = join(os.getcwd(),'praatLog') # set path of praatlog file (location of output of praat)
#initialize all buttons (permanent and vowel tokens)
sett.permButtons = makeButtons() # make permanent buttons (vowel buttons, display all/none buttons)
sett.permDisplay = sett.permButtons[0]+sett.permButtons[1]+sett.permButtons[2] #put all permanent buttons in a list so they can be displayed
#get info from all formant.txt files
getFiles(sett)
# make rendered text objects to draw on screen
sett.F1,sett.F2 = (myfont.render('F1',1,Color('grey87')),myfont.render('F2',1,Color('grey87')))
sett.arpLabel = myfont.render('ARPABET',1,BLACK)
sett.celLabel = myfont.render('CELEX' if args.c else 'UNREDUCED',1,BLACK)
# make dictionary to log changes to
plot.allLogs = {f[0]:[] for f in sett.files}
plot.vowButtons = getVowels(plot, sett)
def quit(sett):
call(['rm', sett.praatLog]) # sanity check to remove praat log (if it still exists)
pygame.quit()
sys.exit()
def selectRange(plot, sett, pressCTRLA, event):
pressed = pygame.key.get_pressed()
T,L,B,R = None,None,None,None # Top, Left, Bottom, Right of the section of the plot you are selecting
if event.type == MOUSEBUTTONDOWN: # click down to start selecting a section of the plot
sett.start = pygame.mouse.get_pos()
sett.FPS = 50 # increase frames per second for smooth drawing of selection box (temporary)
elif sett.start:
sett.stop = pygame.mouse.get_pos() # get current position of mouse
L,R = (sett.start[0],sett.stop[0]) if sett.start[0] < sett.stop[0] else (sett.stop[0],sett.start[0]) # get dimensions of rectangle currently selected
T,B = (sett.start[1],sett.stop[1]) if sett.start[1] < sett.stop[1] else (sett.stop[1],sett.start[1])
sett.zoomLines = [(L,T),(R,T),(R,B),(L,B)] # define dimensions of lines to draw (showing selection)
sett.vowelChange = True
if event.type == MOUSEBUTTONUP: # when you're finally done with the clicking and the dragging
if not (L < 10 or R > plot.width or T < 10 or B > plot.height or T == B or L == R): # make sure you haven't selected outside the plot
sett.FPS = 10 # resume normal frame rate
if pressCTRLA: # if removing a selection
reason = ''
if (pressed[shft[0]] or pressed[shft[1]]): # get a reason for the removal if shift is pressed
reason = inputbox.ask(plot.display,'Reason ')
if not reason:
sett.vowelChange = True
sett.zoomLines = None #stop drawing lines
sett.start, sett.stop = (),() # reset start and stop tuples to default
return 'break' # if no reason is given then don't remove the tokens
sett.displayMemory += [[v for v in plot.vowButtons]] # make backup of display
sett.logMemory += [copy.deepcopy(plot.allLogs)] # make backup of log
clearRange((T,R,B,L), reason, plot, within = sett.vowList) # clear all vowels currently displayed to screen in selected area
textList = []
else: # if zooming in
resize((T,R,B,L), plot) # zoom in to to selected area
for b in sett.permButtons[1]: # set zoom button caption
if b.caption == 'Zoom':
b.caption = 'Reset Zoom'
sett.zoomLines = None
sett.start, sett.stop = (),()
def allVowels(plot, sett, showAll = True):
# displays all vowels to screen if showAll else clears all vowels from screen
for b in sett.permButtons[0]+sett.permButtons[2]:
b.font = boldButtonFont if showAll else smallButtonFont
b._update()
sett.vowList = plot.vowButtons if showAll else []
plot.arpDisplayed = [b.caption for b in sett.permButtons[0]] if showAll else []
plot.altDisplayed = [b.caption for b in sett.permButtons[2]]+['NA'] if showAll else []
plot.filtered = []
plot.textList = []
plot.minDur = None
plot.filtWrd = None
sett.vowelChange = True
def togglePlay(sett, button):
if not sett.play:
sett.play = True
sett.FPS = 30 # increase frames per second so that the vowel you're currently on is played
button.bgcolor = Color("darkolivegreen4")
else:
sett.play = False
sett.FPS = 10 # resume regular frame rate
button.bgcolor = Color("darkolivegreen2")
def drawEllipse(sett, plot, button, resize = False):
if resize and sett.stdDevCounter != 0:
confidenceEllipse([(form.F1,form.F2) for form in sett.vowList],sett.stdDevCounter, plot)
elif not resize:
try:
if sett.stdDevCounter == 0:
confidenceEllipse([(form.F1,form.F2) for form in sett.vowList],1, plot)
sett.stdDevCounter = 1
button.caption = 'Std Dev 1'
button.bgcolor = Color("darkolivegreen4")
elif sett.stdDevCounter == 1:
confidenceEllipse([(form.F1,form.F2) for form in sett.vowList],2, plot)
sett.stdDevCounter = 2
button.caption = 'Std Dev 2'
elif sett.stdDevCounter == 2:
confidenceEllipse([(form.F1,form.F2) for form in sett.vowList],3, plot)
sett.stdDevCounter = 3
button.caption = 'Std Dev 3'
else:
plot.ellip = []
button.caption = 'Std Dev'
button.bgcolor = Color("darkolivegreen2")
sett.stdDevCounter = 0
except:
plot.ellip = []
button.caption = 'Std Dev'
button.bgcolor = Color("darkolivegreen2")
sett.stdDevCounter = 0
sett.vowelChange = True
def filterVowels(sett, plot, button):
# filters vowels by duration or by word
# (not displayed but not removed yet)
plot.minDur = None
sett.vowelChange = True
if 'Dur' in button.caption:
while not plot.minDur: # ask user to input a minimum duration in a textbox on screen
plot.minDur = inputbox.ask(plot.display,'Minimum Duration (ms)')
try:
plot.minDur = int(plot.minDur)
break
except:
if plot.minDur != '':
plot.minDur = None
else: break
plot.filtered = [v for v in sett.vowList if int(v.duration) < plot.minDur]
# don't filter anything if no minimum duration is given
if plot.filtered and not plot.minDur == '': # change button caption
button.caption = 'Rmv.Dur.Filt'
button.font = smallButtonFont
else: # filter according to the word
plot.filtWrd = inputbox.ask(plot.display,'Remove word').strip().upper() # ask user to input word to remove
plot.filtered = [v for v in sett.vowList if v.word == plot.filtWrd]
if plot.filtered and plot.filtWrd:
button.caption = 'Rmv.Wrd.Filt'
button.font = smallButtonFont
def removeFiltered(sett, plot, button):
# removes filtered vowel tokens (by duration or word, not stress)
yesno = None
while not yesno: # double check they actually want to remove the vowels
yesno = inputbox.ask(plot.display,'Remove %r filtered vowels? y/n' % len(plot.filtered)).strip().lower()
if yesno not in ['y','n']:
if yesno == 'yes': plot.minDur = 'y'
elif yesno == 'no': plot.minDur = 'n'
else: yesno = None
sett.vowelChange = True
if yesno == 'y': # if yes, remove all the vowels in filtered
sett.displayMemory += [[v for v in plot.vowButtons]]
sett.logMemory += [copy.deepcopy(plot.allLogs)]
for f in plot.filtered:
reason = 'filtered: below minimum duration of %d ms' % plot.minDur if 'Dur' in button.caption else 'filtered: word %r' % plot.filtWrd
clear(f,reason, plot)
plot.vowButtons.remove(f)
# reset buttons and appropriate lists
plot.filtered = []
plot.minDur = None
sett.vowelChange = True
button.caption = 'Dur.Filter' if 'Dur' in button.caption else 'Wrd.Filter'
button.font = smallButtonFont
button._update()
def resumeFromLog(sett, plot):
sett.displayMemory += [[v for v in plot.vowButtons]]
sett.logMemory += [copy.deepcopy(plot.allLogs)]
for v in plot.vowButtons:
if isinstance(v.alreadyCorrected,tuple): # change vowel if it's been changed and logged in the log file
x,y = calculateVowelLocation((v.alreadyCorrected[2],v.alreadyCorrected[3]), plot)
buttonRect = pygame.Rect(x,y, 8, 8)
buttonRect.center = (x,y)
button = pygbutton.PygButton(buttonRect, '►'.decode('utf8'),border = False)
button.bgcolor = WHITE
button.fgcolor = v.button.bgcolor
newV = v.makeAlternate(v.alreadyCorrected[2],v.alreadyCorrected[3],button) # make new vowel
newV.time, newV.maxForm = v.alreadyCorrected[:2] # update maxforms and time
newV.alreadyCorrected = None
plot.vowButtons.remove(v)
plot.vowButtons.append(newV)
plot.vowButtons = [v for v in plot.vowButtons if v.alreadyCorrected != 'removed'] # remove vowel if it's been removed in the log file
def drawToScreen(sett, plot, NOTPLOTRECTS):
# draw the vowel plot if it has been updated
if sett.vowelChange:
plot.display.fill(WHITE)
if plot.ellip: # draw confidence ellipse
plot.display.blit(plot.ellip[0],plot.ellip[1])
plot.display.blit(sett.F1,(plot.width-myfont.size('F1')[0],plot.height/2)) # draw F1 and F2 as axis labels for the plot
plot.display.blit(sett.F2,(plot.width/2,10))
drawGrid(numFont, plot) # draw the grid on the plot
if sett.zoomLines: pygame.draw.lines(plot.display,BLACK,True,sett.zoomLines,1) # draw the box to zoom to/remove vowels from
for b in [v.button for v in sett.vowList]: # draw all vowel tokens to the screen
b.draw(plot.display)
sett.vowelChange = False
else: # if it hasn't been updated, update the rest of the screen only
for r in NOTPLOTRECTS:
pygame.draw.rect(plot.display,WHITE,r)
lSide, bSide = [ relativeSizing(490), relativeSizing(840, 'h') ] # relative left and bottom edge of vowel info rectangle
pygame.draw.lines(plot.display,BLACK,True, [(lSide,plot.height),(plot.width,plot.height),(plot.width,bSide),(lSide,bSide)],2) # draw rectangle to display vowel info
lSide, tSide = [ relativeSizing(10), relativeSizing(10, 'h') ] # relative left and top edge of vowel button rectangle
pygame.draw.lines(plot.display,BLACK,True, [(lSide,tSide),(plot.width,tSide),(plot.width,plot.height),(lSide,plot.height)],2) # draw rectangle to display vowel buttons
tokenNum = myfont.render(str(len(sett.vowList)),1,BLACK) # render and draw the number of tokens currently on the screen
plot.display.blit(tokenNum,(relativeSizing(710),relativeSizing(820, 'h')))
rSide, tSide = [relativeSizing(185), relativeSizing(630, 'h')]
pygame.draw.lines(plot.display,BLACK,True,[(lSide,tSide),(rSide,tSide),(rSide,bSide),(lSide,bSide)],2) # draw the box for the arpabet vowels
lSide, rSide = [relativeSizing(225), relativeSizing(480)]
pygame.draw.lines(plot.display,BLACK,True,[(lSide,tSide),(rSide,tSide),(rSide,bSide),(lSide,bSide)],2) #draw the box for the celex vowels
plot.display.blit(sett.celLabel,(relativeSizing(320),relativeSizing(605,'h'))) # draw CELEX
plot.display.blit(sett.arpLabel,(relativeSizing(50),relativeSizing(605,'h'))) # draw ARPABET
# draw the minimum duration or the removed word if set
if (plot.minDur or plot.filtWrd) and plot.filtered:
if plot.minDur:
filteredCode = ( miniFont.render('Minimum Duration: ',1,BLACK), miniFont.render(str(plot.minDur),1,BLACK) )
filtsize = miniFont.size(str(plot.minDur))
else:
filteredCode = ( miniFont.render('Filtered Word: ',1,BLACK) , miniFont.render(str(plot.filtWrd),1,BLACK) )
filtsize = miniFont.size(str(plot.filtWrd))
for i,f in enumerate(filteredCode):
plot.display.blit(f,(WINDOWWIDTH-(filtsize[0]+10 if i else 110),WINDOWHEIGHT-50 + i*(filtsize[1]+3)))
for b in sett.permDisplay: # draw all buttons
b.draw(plot.display)
if plot.textList: # draw info for last vowel scrolled over to screen
x = relativeSizing(500)
y,sp = relativeSizing([605,21], 'h')
for i,t in enumerate(plot.textList):
label = textListFont.render(t, 1, BLACK)
plot.display.blit(label, (x, y+(sp*i)))
if sett.chooseFormants: # draw alternate formant buttons (when in choose formant mode)
for xf in plot.xFormButtons:
xf.button.draw(plot.display)
pygame.display.update() # update screen
def resizeScreen(screenSize, plot, sett):
global WINDOWWIDTH, WINDOWHEIGHT, myfont, numFont, miniFont, textListFont, smallButtonFont, boldButtonFont
# reset widow size
WINDOWWIDTH, WINDOWHEIGHT = screenSize
# reset plot sizes and the display window
plot.height = relativeSizing(600, 'h')
plot.width = relativeSizing(700)
plot.display = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), RESIZABLE)
# redefine font sizes
myfont = pygame.font.SysFont('helvetica',int(relativeSizing(20, 'h')))
numFont = pygame.font.SysFont('helvetica',int(relativeSizing(15)))
miniFont = pygame.font.SysFont('helvetica',int(relativeSizing(12)))
textListFont = pygame.font.SysFont('courier',int(relativeSizing(18)))
smallButtonFont = pygame.font.SysFont('courier',int(relativeSizing(16)))
boldButtonFont = pygame.font.SysFont('courier',int(relativeSizing(16)), bold = True)
# remake permanent buttons in the correct spots
oldButtons = {b.caption:b for b in sett.permButtons[0]+sett.permButtons[2]} #dict of old button settings when resizing
sideButtons = {(b.caption if b.caption not in ['1','2','0'] else (b.caption,'stress')):b for b in sett.permButtons[1]}
oldButtons.update(sideButtons)
sett.permButtons = makeButtons(oldButtons)
sett.permDisplay = sett.permButtons[0]+sett.permButtons[1]+sett.permButtons[2]
# redraw vowel buttons in the correct spots and change size
vowWidth, vowHeight = [ relativeSizing(8), relativeSizing(8,'h') ]
for v in plot.vowButtons:
v.button = makeVowelButton(v, plot, settings = {'w':vowWidth, 'h':vowHeight, 'bgcol':v.button.bgcolor, 'fgcol':v.button.fgcolor})
# redefine text objects with new font sizes
sett.F1,sett.F2 = (myfont.render('F1',1,Color('grey87')),myfont.render('F2',1,Color('grey87')))
sett.arpLabel = myfont.render('ARPABET',1,BLACK)
sett.celLabel = myfont.render('CELEX' if args.c else 'UNREDUCED',1,BLACK)
# draw ellipse if required
drawEllipse(sett, plot, None, resize = True)
# make sure screen updates
sett.vowelChange = True
def main():
# this is where the magic happens
#initialize pygame surfaces and clocks
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURFACE = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), RESIZABLE) # create window according to dimensions
plot = plotmishClasses.vowelPlot(DISPLAYSURFACE)
NOTPLOTRECTS = (pygame.Rect(plot.width,0,WINDOWWIDTH - plot.width, plot.height),
pygame.Rect(0,plot.height,WINDOWWIDTH,WINDOWHEIGHT - plot.height))
caption = 'Plotmish - '+args.k if args.k else 'Plotmish'
pygame.display.set_caption(caption) # set window caption
#initialize plotmish settings
sett = plotmishClasses.Settings()
initializeSettings(sett, plot)
# remove praatlog if it exists (it shouldn't but just in case)
call(['rm', sett.praatLog])
while True: # main loop
## this is the exciting bit
for event in pygame.event.get(): # event handling loop
## process when quitting the program (hit escape to quit)
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
quit(sett)
if event.type == VIDEORESIZE:
resizeScreen(event.size, plot, sett)
NOTPLOTRECTS = (pygame.Rect(plot.width,0,WINDOWWIDTH - plot.width, plot.height),
pygame.Rect(0,plot.height,WINDOWWIDTH,WINDOWHEIGHT - plot.height))
## deal with zooming and selecting a range of vowels
pressed = pygame.key.get_pressed() # get all buttons currently pressed
pressCTRLA = (pressed[ctrl[0]] or pressed[ctrl[1]]) and pressed[K_a] # boolean indicating if ctrl+a is pressed (for selecting a range)
if sett.zooming or pressCTRLA:
selectRange(plot, sett, pressCTRLA, event)
elif sett.zoomLines: