-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrunGUI.py
468 lines (336 loc) · 25 KB
/
runGUI.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
'''
PROGRAM TO RUN GUI ON SELECTED TARGETS FROM ROBOAO DATABASE
-----------------------------------------------------------
Prompts user for path to directory containing directory for each target as well as the names of each of these directories
Targets must have been run through 'preGUI.py' before using this program
Returns 'guiOut.txt' containing target name, tags gui, and object coordinates when applicable
Each time this GUI is run in the same directory, it will overwrite info in previously created text files
USING THE GUI
-------------
Navigation:
Keys: left/right arrow keys or space bar to progress one at a time, Ctrl+left/right arrow keys progresses
through 50 targets at a time
Buttons: < or > to progress one at a time, << or >> to progress 10 at a time
Quality Check:
Each target is tagged 'good' as default. Selecting 'good', 'uncertain', or 'bad' will overwrite other tags.
Location:
Bottom left image is from DSS database, roboao field size is marked in faint brown box. Smaller green-outlined image
is data, full field (35.6 arcsec). Presumed target location is marked in green. If this matches DSS image do nothing.
If not, click on target and green circle will appear. Each time this position is re-selected it will overwrite
previous position. Can also tag image for not enough stars or bad pointing. Reseting location undoes all location
selections.
Companion:
If no companion do mothing. Choose location of companion by clicking on the companion in any of the four
bottom imags. For distant compaion, click in upper right image (8 arcsec across). Selecitons in close and far
field are both saved and updated with new clicks. Resetting removes tags and clicked locations.
Notes Entry:
Add any text note with no spaces or commas, press enter to save. Each time enter is pressed target's text file will
be appended with note.
KEY FOR guiOut.txt
------------------
G: good
U: uncertain
B: bad
NES: not enough stars
INP: incorecct pointing
DDB: needs different database
CB: possible Close Binary
NM: needs to be looked at manually
Location: pixel coordinates of corrected target location
Companion: pixel coordinates of companion within 1.75"
Far Companion: pixel corrdinates companion within 4"
For Questions: Claire Lamman, [email protected]
'''
import Tkinter as tk
from Tkinter import *
from PIL import Image, ImageTk
import glob, sys, os
import numpy as np
#FILE PATHS, TARGET NAMES & INFO
path = raw_input('Enter path to data directory (Ex- C:/Users/Claire/RoboAO/Data/):') #prompt user for directory path
programnum = raw_input('Enter Program Number (Ex- 3):') #prompt user for program number
dpath = sorted(glob.glob(path+programnum+'_*')) #target directories
nlist = [s for s in os.listdir(path) if s.startswith(programnum+'_')] #make list of directory path
names = [i.split(".")[0] for i in (nlist[0::1])] #Create list of object names
def convertCoords(xo, yo, pngDim, pngArcsec, xoff, yoff):
'''
Convert coordinates on a Tkinter canvas to cooresponding fits pixel coordinates for a specific target
----------------
INPUT PARAMETERS
xo, yo = original coordinates on canvas, relative to upper left corner, int
pngDim = dimmensions of square Tkinter canvas, int
pngArcsec = arcsecond across image displayed on square canvas, int
xoff, yoff = target's offset from center of original fits image, in pixels, int
'''
cx = (xo-(pngDim/2.))*(pngArcsec/pngDim) #Coordinates relative to center of png in arc seconds
cy = ((pngDim/2.)-yo)*(pngArcsec/pngDim)
dx = cx*(1000./17.4) #Pixel coordinates relative to target in 2048x2048 image
dy = cy*(1000./17.4) #...17.4 miliarcsec/pixel
fx = dx+xoff+1024 #pixel coordinates in original fits file relative to lower left corner
fy = dy+yoff+1024 #...and offset by target's offset from center
return fx, fy
class MyApp(object):
"""GUI to view, tag, and select various locations for data of multiple roboao targets"""
def __init__(self, root):
root = Toplevel() #Toplevel, not Tk to make python happy
root.wm_title("Target Examination") #Window Title
root.configure(background='black') #black background
root.geometry('{}x{}'.format(1300, 750)) #sixe of window
self.buttonframe = Frame(root, bd=1) #use grids to controll label positions
self.buttonframe.grid(row=8, column=20)
#FRAME FOR CANVASES
#position selector
frame1 = Frame(root, highlightbackground="DarkOliveGreen4", highlightthickness=2, width=200, height=200) #frame color, size
frame1.grid(row=6, column=4, rowspan=3, padx=20, pady=30, sticky=NW) #location on grid, space in y direction
frame1.pack_propagate(False)
#close companion selector
frame2 = Frame(root, highlightbackground="cadet blue", highlightthickness=2, width=900, height=225) #frame color, size
frame2.grid(row=9, column=4, rowspan=1, columnspan=50, sticky=NE) #location on grid, spans columns, stuck to East
frame2.pack_propagate(False)
#far companion selector
frame3 = Frame(root, highlightbackground="cadet blue", highlightthickness=2, width=400, height=400) #frame color, size
frame3.grid(row=1, column=7, rowspan=7, columnspan=1, padx=20, sticky=S) #location on grid, y space, stuck to West
frame3.pack_propagate(False)
root.bind('<Return>', self.enterKey)
root.bind('<Left>', self.leftKey) #Bind arrow and space bar keys for moving through targets
root.bind('<Right>', self.rightKey)
root.bind('<space>', self.spaceKey)
root.bind('<Control-Left>', self.ctrleft)
root.bind('<Control-Right>', self.ctright)
root.protocol("WM_DELETE_WINDOW", on_closing) #If window closes shut everything down
com0=[500]*(len(names)*2) #for saving circle coordinates
com1=[1000]*(len(names)*2)
com2=[500]*(len(names)*2)
global ct #declare global variable
ct=-1 #start with -1 so becomes 0 when function is called upon gui opening
global nxtTarget
def nxtTarget(f): #Function to bring up individual information for each target
global ct #redefine ct globally each time function is called
ct+=f
if (ct+1)>len(names): #Close window if there are no more targets
on_closing()
Label(root, text='no spaces, [Enter] to save', fg='dim gray', bg='black', font=("Helvetica", 8)).grid(row=7, column=6, sticky=SW, pady=20)
global e1
e1=Entry(root, width=25)
e1.grid(row=7, column=6, sticky=SW)
canvas1.coords(circle0, com0[ct*2]-8, com0[(ct*2)+1]-8, com0[ct*2]+8, com0[(ct*2)+1]+8,) #update circle positions
canvas2.coords(circle1, com1[ct*2]-8, com1[(ct*2)+1]-8, com1[ct*2]+8, com1[(ct*2)+1]+8)
canvas2.coords(circle2, com1[ct*2]+225-8, com1[(ct*2)+1]-8, com1[ct*2]+225+8, com1[(ct*2)+1]+8)
canvas2.coords(circle3, com1[ct*2]+450-8, com1[(ct*2)+1]-8, com1[ct*2]+450+8, com1[(ct*2)+1]+8)
canvas2.coords(circle4, com1[ct*2]+675-8, com1[(ct*2)+1]-8, com1[ct*2]+675+8, com1[(ct*2)+1]+8)
canvas3.coords(circle5, com2[ct*2]-8, com2[(ct*2)+1]-8, com2[ct*2]+8, com2[(ct*2)+1]+8)
#OPEN FILES
inf=[]
tinf = open(dpath[ct]+'/info4GUI.txt', 'r').readlines() #target info
for t in tinf:
inf.extend([float(t.strip('\n'))])
ftsim = sorted(glob.glob(dpath[ct]+'/*.png')) #all png image
xpix = inf[1] #get pixel coordinates of target
ypix = inf[0]
global xoffset
xoffset = xpix-1024 #target position relative to center of fits
global yoffset
yoffset = ypix-1024
#CREATE TEXT FILE TO STORE RESULTS
if os.path.isfile(dpath[ct]+'/guiOut.txt')==False: #only create file if one doesn't already exist
guiOut = open(dpath[ct]+'/guiOut.txt', 'w+') #open/create blank file
guiOut.write("%s \n%s \n%s \n%s \n%s \n%s \n%s \n%s \n%s \n%s \n%s \n%s \n" % ('Target:'+names[ct], #create blank template
'G:x', 'U:-', 'B:-', 'NES:-', 'IP:-', 'DDB:-', 'CB:-', 'NM:-', 'Location:-', 'Companion:-', 'Far Companion:-'))
guiOut.close() #close file
tinfr = [] #Blank array to store target info
for i in tinf:
if i != tinf[-1]: #Ignore last item in list: 'OK'
tinfr.append(str(round(float(i), 2))) #round to two sf, convert back to string
#SET UP WINDOW
#Target Info
Label(root, text=names[ct], fg='white', bg='black', font=("Helvetica", 16))\
.grid(row=0, column=0, columnspan=10, sticky=W) #target name (color, font, location)
Label(root, text='Core: '+str(inf[2])+' Halo: '+str(inf[3])+' Strehl: '+str(inf[4])+'% Mag: '+str(inf[5]),\
fg='white', bg='black', font=('Helvetica', 12))\
.grid(row=1, column=0, columnspan=4, sticky=W) #target information (color, font, location)
Label(root, text='Displaying Target '+str(ct+1)+'/'+str(len(names)), fg='white', bg='black', font=('Helvetica', 12))\
.grid(row=0, column=7, padx=20, sticky=E) #status on total targets, current/total (color, font, location)
#Set up Images
photo2 = ImageTk.PhotoImage((Image.open(ftsim[0])).resize((250, 150), Image.ANTIALIAS)) #contrast curve
photo4 = ImageTk.PhotoImage((Image.open(ftsim[3])).resize((200, 200), Image.ANTIALIAS)) #full field image
photo5 = ImageTk.PhotoImage((Image.open(ftsim[4])).resize((375, 375), Image.ANTIALIAS)) #database field
photo6 = ImageTk.PhotoImage((Image.open(ftsim[1])).resize((900, 225), Image.ANTIALIAS)) #fits collage
photo7 = ImageTk.PhotoImage((Image.open(ftsim[2])).resize((400, 400), Image.ANTIALIAS)) #larger field for companion
label2 = Label(root, image=photo2) #create labels from the 2 images not displayed on a canvas
label5 = Label(root, image=photo5, bg='DarkOliveGreen4') #This one with a green border
label2.image = photo2 #keep a reference!
label5.image = photo5
label2.grid(row=2, column=0, rowspan=4, padx=10, sticky=N) #contrast curve location
label5.grid(row=6, column=0, rowspan=4, columnspan=4, pady=30, sticky=N) #database image location
#Update previously created three canvases with images for new target
photo4.image=photo4 #full field image
canvas1.itemconfig(img1, image=photo4)
photo6.image=photo6 #fits collage
canvas2.itemconfig(img2, image=photo6)
photo7.image=photo7 #larger field for companion
canvas3.itemconfig(img3, image=photo7)
def selectloc1(event):
'''For Location Check: save cursor position and place circle on mouse release'''
x, y = event.x, event.y #save mouse coordinates on canvas
canvas1.coords(circle0, x-8, y-8, x+8, y+8) #cirlce around selected location
fx, fy = convertCoords(x, y, 200., 35.6352, 0, 0) #convert to pixel coordinates
editOut(9, 'Location: ('+str(fx)+' '+str(fy)+')')
com0[ct*2], com0[(ct*2)+1] = x, y #save circle location
def selectloc2(event):
'''For Close Companion Location: save cursor position and place circle on each of 4 images'''
x, y = event.x, event.y #save mouse coordinates on canvas
#To account for user not clicking in left most image
imcl = 225. #lendth of one image in canvas
if (x-imcl)<0: #if click is in first image
x-=0
elif (x-(imcl*2.))<0: #if click is in second image...
x-=imcl #subtract 1 image length
elif (x-(imcl*3.))<0: #if click is in third image...
x-=(imcl*2.) #subtract 2 image lengths
elif (x-(imcl*4.))<0: #if click is in fourth image...
x-=(imcl*3.) #subtract 3 image lengths
canvas2.coords(circle1, x-8, y-8, x+8, y+8) #circle around selected location for each image
canvas2.coords(circle2, x+225-8, y-8, x+225+8, y+8)
canvas2.coords(circle3, x+450-8, y-8, x+450+8, y+8)
canvas2.coords(circle4, x+675-8, y-8, x+675+8, y+8)
fx, fy = convertCoords(x, y, 225., 1.5, xoffset, yoffset) #convert to pixel coordinates
editOut(10, 'Companion: ('+str(fx)+' '+str(fy)+')') #update out text file
com1[ct*2], com1[(ct*2)+1] = x, y #save circle location
def selectloc3(event):
'''For Far Companion Location: save cursor position and place circle on mouse release'''
x, y = event.x, event.y #save mouse corrdinates on canvas
canvas3.coords(circle5, x-8, y-8, x+8, y+8) #circle around selected location
fx, fy = convertCoords(x, y, 400., 8., xoffset, yoffset) #convert to pixel coordinates
editOut(11, 'Far Companion: ('+str(fx)+' '+str(fy)+')') #update out text file
com2[ct*2], com2[(ct*2)+1] = x, y #save circle location
def editOut(line, mrk):
'''Function to replace specific line in results file with given string'''
with open(dpath[ct]+'/guiOut.txt', 'r') as file:
data = file.readlines() #read list of lines into data
data[line] = mrk+'\n' #replace line with new entry and end that line
with open(dpath[ct]+'/guiOut.txt', 'w') as file:
file.writelines(data) #rewrite out file
def rmCircle(circlenum):
'''Remove previously placed circle from view in canvas'''
if circlenum==0: #circle for location selection
com0[ct*2], com0[(ct*2)+1] = 500, 501
if circlenum==1: #circles for companion selection
com1[ct*2], com1[(ct*2)+1] = 5000, 5001
com2[ct*2], com2[(ct*2)+1] = 5000, 5001
canvas1.coords(circle0, com0[ct*2]-8, com0[(ct*2)+1]-8, com0[ct*2]+8, com0[(ct*2)+1]+8,) #update circle positions
canvas2.coords(circle1, com1[ct*2]-8, com1[(ct*2)+1]-8, com1[ct*2]+8, com1[(ct*2)+1]+8)
canvas2.coords(circle2, com1[ct*2]+225-8, com1[(ct*2)+1]-8, com1[ct*2]+225+8, com1[(ct*2)+1]+8)
canvas2.coords(circle3, com1[ct*2]+450-8, com1[(ct*2)+1]-8, com1[ct*2]+450+8, com1[(ct*2)+1]+8)
canvas2.coords(circle4, com1[ct*2]+675-8, com1[(ct*2)+1]-8, com1[ct*2]+675+8, com1[(ct*2)+1]+8)
canvas3.coords(circle5, com2[ct*2]-8, com2[(ct*2)+1]-8, com2[ct*2]+8, com2[(ct*2)+1]+8)
def sequence(*functions):
'''Funciton to combine multiple functions (so buttons can have multiple commands)'''
def func(*args, **kwargs):
return_value = None
for function in functions:
return_value = function(*args, **kwargs)
return return_value
return func
global save_note
def save_note():
'''function to append note to targets text file'''
with open(dpath[ct]+'/guiOut.txt', 'a') as file:
file.write(', Note: %s' % (e1.get()))
#CANVASES FOR POSITION SELECTION
#Position Check
canvas1 = Canvas(frame1, width=200, height=200, highlightthickness=0, cursor='@arrow.cur') #create canvas, size, cursor
canvas1.pack(expand = YES, fill = BOTH)
img1 = canvas1.create_image(0, 0, anchor=NW) #give canvas no image initially
circle0 = canvas1.create_oval(500, 500, 501, 501, outline='chartreuse2') #create out-of-sight circle
canvas1.bind("<ButtonPress-1>", selectloc1) #if click here, call selectloc1 function
canvas2 = Canvas(frame2, width=900, height=225, highlightthickness=0, cursor='@arrow.cur') #canvas frame, size, cursor
canvas2.pack(expand = YES, fill = BOTH)
img2 = canvas2.create_image(0, 0, anchor=NW) #no initial image
circle1 = canvas2.create_oval(500, 500, 501, 501, outline='chartreuse2') #create out-of-sight circle for each image
circle2 = canvas2.create_oval(500, 500, 501, 501, outline='chartreuse2')
circle3 = canvas2.create_oval(500, 500, 501, 501, outline='chartreuse2')
circle4 = canvas2.create_oval(500, 500, 501, 501, outline='black')
canvas2.bind("<ButtonPress-1>", selectloc2) #if click here, call selectloc2 function
canvas3 = Canvas(frame3, width=400, height=400, highlightthickness=0, cursor='@arrow.cur') #canvas frame, location, cursor
canvas3.pack(expand = YES, fill = BOTH)
img3 = canvas3.create_image(0, 0, anchor=NW) #set up for but don't include image yet
circle5 = canvas3.create_oval(500, 500, 501, 501, outline='black') #create out-of-sight circle
canvas3.bind("<ButtonPress-1>", selectloc3) #if click here, call selectloc3 function
canvas3.create_oval(0, 0, 400, 400, outline='white')
#LABELS AND BUTTONS
#Input Field for Notes
Label(root, text="Note:", fg='white', bg='black', font=("Helvetica", 12)).grid(row=7, column=6, sticky=SW, pady=35)
#Quality Check Label
Label(root, text='QUALITY CHECK', fg='white', bg='black', font=("Helvetica", 12, 'bold'))\
.grid(row=2, column=1, columnspan=3, padx=15, sticky=N) #location on grid
#Location Label
Label(root, text='LOCATION', fg='DarkOliveGreen4', bg='black', font=("Helvetica", 12, 'bold'))\
.grid(row=2, column=4, sticky=N)
#Companion Label
Label(root, text='COMPANION', fg='cadet blue', bg='black', font=("Helvetica", 12, 'bold'))\
.grid(row=2, column=6, padx=10, sticky=N)
#Image Labels
Label(root, text='DSS Database Image', fg='DarkOliveGreen4', bg='black', font=('Helvetica', 8))\
.grid(row=9, column=0, sticky=SW, padx=35, pady=55) #Database iamge label
Label(root, text='Full Field Fits N ^ E ->', fg='DarkOliveGreen4', bg='black', font=('Helvetica', 8))\
.grid(row=8, column=4, sticky=NW, padx=20, pady=10) #Full field
Label(root, text='Field = 1.5" across', fg='cadet blue', bg='black', font=('Helvetica', 8))\
.grid(row=9, column=4, sticky=SW, padx=10) #Full field
Label(root, text='Field = 8" across', fg='cadet blue', bg='black', font=('Helvetica', 8))\
.grid(row=8, column=7, sticky=NW, padx=30) #Full field
#Button, name, command, size, color, .position
G = Button(root, text = u'\u2713', command=lambda: sequence(editOut(1,'G:x'), editOut(2,'U:-'), editOut(3, 'B:-')),\
height=1, width=3, bg='chartreuse4', font=('Helvetica', 12)).grid(row=3, column=1, sticky=N) #good
U = Button(root, text="?", command=lambda: sequence(editOut(1,'G:-'), editOut(2,'U:x'), editOut(3, 'B:-')),\
height=1, width=3, bg='gold3', font=('Helvetica', 12, 'bold')).grid(row=3, column=2, sticky=N) #unsure
B = Button(root, text="X", command=lambda: sequence(editOut(1,'G:-'), editOut(2,'U:-'), editOut(3, 'B:x')),\
height=1, width=3, bg='IndianRed4', font=('Helvetica', 12, 'bold')).grid(row=3, column=3, sticky=N) #bad
Bck = Button(root, text="<", command=lambda: nxtTarget(-1), height=1, width=2, bg='grey',\
font=('Helvetica', 12, 'bold')).grid(row=4, column=1, sticky=NE) #back
Nxt = Button(root, text=">", command=lambda: nxtTarget(1), height=1, width=2, bg='grey',\
font=('Helvetica', 12, 'bold')).grid(row=4, column=3, sticky=NW) #next
FBW = Button(root, text="<<", command=lambda: nxtTarget(-10), height=1, width=2, bg='grey',\
font=('Helvetica', 12, 'bold')).grid(row=5, column=1, sticky=NE) #fast-backwards
FFW = Button(root, text=">>", command=lambda: nxtTarget(10), height=1, width=2, bg='grey',\
font=('Helvetica', 12, 'bold')).grid(row=5, column=3, sticky=NW) #fast-forward
NES = Button(root, text="Not Enough Stars", command=lambda: editOut(4, 'NES:x'), height=1, width=15, bg='DarkOliveGreen4',\
font=('Helvetica', 12)).grid(row=3, column=4, padx=20, sticky=N) #not enought stars
INP = Button(root, text="Incorrect Pointing", command=lambda: editOut(5, 'INP:x'), height=1, width=15, bg='DarkOliveGreen4',\
font=('Helvetica', 12)).grid(row=4, column=4, padx=20, sticky=N) #incorrect pointing
DDB = Button(root, text="Needs diff Database", command=lambda: editOut(6, 'DDB:x'), height=1, width=15, bg='DarkOliveGreen4',\
font=('Helvetica', 12)).grid(row=5, column=4, sticky=N, padx=20) #Needs different database
LOk = Button(root, text = 'Reset Location', command=lambda: sequence(editOut(9, 'Location:-'), rmCircle(0), editOut(5, 'INP:-'), editOut(4, 'NES:-'), editOut(6, 'DDB:-')),\
height=1, width=15, fg='DarkOliveGreen4', bg='gray25', font=('Helvetica', 12)).grid(row=7, column=6, sticky=NW) #location is OK
NC = Button(root, text="Reset Companion", command=lambda: sequence(editOut(10, 'Companion:-'), editOut(11, 'Far Companion:-'), rmCircle(1), editOut(7, 'CB:-'), editOut(8, 'NM:-')),\
height=1, width=15, fg='cadet blue', bg='gray25', font=('Helvetica', 12)).grid(row=7, column=6, pady=40, sticky=NW) #no companion
CB = Button(root, text="Poss. Close Binary", command=lambda: editOut(7, 'CB:x'),\
height=1, width=15, bg='cadet blue', font=('Helvetica', 12)).grid(row=3, column=6, sticky=NW) #possible close binary
NM = Button(root, text="Look at Manually", command=lambda: editOut(8, 'NM:x'),\
height=1, width=15, bg='cadet blue', font=('Helvetica', 12)).grid(row=4, column=6, sticky=NW) #no companion
nxtTarget(1) #Star GUI with first target
@staticmethod #use static function so can be called by all classes
def enterKey(event): #if press left arrow key, go back one target
save_note()
@staticmethod #use static function so can be called by all classes
def leftKey(event): #if press left arrow key, go back one target
nxtTarget(-1)
@staticmethod
def rightKey(event): #if press right arrow key, go to next target
nxtTarget(1)
@staticmethod
def spaceKey(event): #if press space bar, go to next target
nxtTarget(1)
@staticmethod
def ctrleft(event): #if press space bar, go to next target
nxtTarget(-50)
@staticmethod
def ctright(event): #if press space bar, go to next target
nxtTarget(50)
def on_closing(): #If window closes shut everything down
root.destroy() #Kill GUI
sys.exit() #shut down (might make python angry, can be removed)
root = Tk()
root.withdraw() #stop extra window from appearing
MyApp(root)
root.title('Tkinter Widgets')
root.mainloop()