mg-pso-gui 0.1.13__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mgpsogui/gui/HomePage.py CHANGED
@@ -56,517 +56,556 @@ stderr_queue = Queue()
56
56
  cosu_queue = Queue()
57
57
 
58
58
  class App(customtkinter.CTk):
59
- def __init__(self):
60
- super().__init__()
61
-
62
- self.option_manager = om.OptionManager()
63
-
64
- self.running_config = None
65
- self.selected_graph_name = None
66
-
67
- self.train_process = None
68
- self.minikube_process = None
69
- self.data_x = [0]
70
- self.data_y = [0]
71
-
72
- self.image_width = 1280
73
- self.image_height = 720
74
-
75
- # configure window
76
- self.title("CSIP PSO")
77
- self.geometry(f"{1920}x{1080}")
78
-
79
- # configure grid layout (4x4)
80
- self.grid_columnconfigure(0, weight=1)
81
- self.grid_rowconfigure(1, weight=1)
82
-
83
- header_padding_x = (5, 5)
84
- header_padding_y = (10, 10)
85
-
86
- self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0)
87
- self.sidebar_frame.grid(row=0, column=0, sticky="nsew")
88
- self.sidebar_frame.grid_columnconfigure(4, weight=1)
89
- self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CSIP PSO", font=customtkinter.CTkFont(size=20, weight="bold"))
90
- self.logo_label.grid(row=0, column=0, padx=(20, 10), pady=header_padding_y)
91
- self.save_button = customtkinter.CTkButton(self.sidebar_frame, text="Save", width=60, command=self.save_project)
92
- self.save_button.grid(row=0, column=1, padx=header_padding_x, pady=header_padding_y)
93
- self.load_button = customtkinter.CTkButton(self.sidebar_frame, text="Load", width=60, command=self.load_project)
94
- self.load_button.grid(row=0, column=2, padx=header_padding_x, pady=header_padding_y)
95
-
96
- # 4 is service URL
97
- self.service_label = customtkinter.CTkLabel(self.sidebar_frame, text="Service:", anchor="w")
98
- self.service_label.grid(row=0, column=3, padx=(80, 5), pady=header_padding_y)
99
- self.service_url = customtkinter.CTkEntry(self.sidebar_frame, textvariable=self.option_manager.get_arguments()['url'])
100
- self.service_url.grid(row=0, column=4, columnspan=1, padx=header_padding_x, pady=header_padding_y, sticky="nsew")
101
- refresh_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "refresh.png")), size=(20, 20))
102
- self.refresh_button = customtkinter.CTkButton(self.sidebar_frame, text=None, width=30, image=refresh_image, command=self.load)
103
- self.refresh_button.grid(row=0, column=5, padx=(5, 80), pady=header_padding_y)
104
-
105
- self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="Scale:", anchor="w")
106
- self.scaling_label.grid(row=0, column=6, padx=header_padding_x, pady=header_padding_y)
107
- self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["50%", "75%", "100%", "125%", "150%", "175%", "200%"], width=60,
108
- command=self.change_scaling_event)
109
- self.scaling_optionemenu.grid(row=0, column=7, padx=header_padding_x, pady=header_padding_y)
110
- self.scaling_optionemenu.set("100%")
111
-
112
- expand_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "expand.png")), size=(20, 20))
113
- self.new_window = customtkinter.CTkButton(self.sidebar_frame, text=None, width=30, image=expand_image, command=self.new_window)
114
- self.new_window.grid(row=0, column=8, padx=(5, 20), pady=header_padding_y)
115
-
116
- self.tabview = customtkinter.CTkTabview(self, bg_color="transparent", fg_color="transparent")
117
- self.tabview.grid(row=1, column=0, padx=(0, 0), pady=(10, 10), sticky="nsew")
118
- tab1 = "Platform"
119
- tab2 = "Setup"
120
- tab3 = "Logs"
121
- tab4 = "Results"
122
- #tab5 = "Results"
123
-
124
- self.tabview.add(tab1)
125
- self.tabview.add(tab2)
126
- self.tabview.add(tab4)
127
- self.tabview.add(tab3)
128
- #self.tabview.add(tab5)
129
-
130
- #self.tabview.configure(state="disabled")
131
-
132
- pt.create_tab(self, self.tabview.tab(tab1))
133
- st.create_tab(self, self.tabview.tab(tab2))
134
- rt.create_tab(self, self.tabview.tab(tab3))
135
- vt.create_tab(self, self.tabview.tab(tab4))
136
-
137
- self.footer_frame = customtkinter.CTkFrame(self, corner_radius=0)
138
- self.footer_frame.grid(row=2, column=0, sticky="nsew")
139
- self.footer_frame.grid_columnconfigure(4, weight=1)
140
-
141
- self.footer_progress_label = customtkinter.CTkLabel(self.footer_frame, text="Stopped", width=150, font=customtkinter.CTkFont(size=16, weight="bold"), anchor="w")
142
- self.footer_progress_label.grid(row=0, column=0, padx=(20, 5), pady=header_padding_y)
143
-
144
- self.footer_progress_bar = customtkinter.CTkProgressBar(self.footer_frame)
145
- self.footer_progress_bar.grid(row=0, column=4, padx=(50, 100), pady=header_padding_y, sticky="ew")
146
- self.footer_progress_bar.set(0)
147
-
148
- play_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "play.png")), size=(20, 20))
149
- self.new_window = customtkinter.CTkButton(self.footer_frame, text=None, width=30, image=play_image, command=self.run)
150
- self.new_window.grid(row=0, column=7, padx=(5, 5), pady=header_padding_y)
151
-
152
- stop_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "stop.png")), size=(20, 20))
153
- self.new_window = customtkinter.CTkButton(self.footer_frame, text=None, width=30, image=stop_image, command=self.stop)
154
- self.new_window.grid(row=0, column=8, padx=(5, 20), pady=header_padding_y)
155
-
156
- def _resize_image(self, event):
157
- self.graph_label.update_idletasks()
158
- new_width = self.graph_label.winfo_width()
159
- new_height = self.graph_label.winfo_height()
160
-
161
- alt_width = new_height * 1.77778
162
- alt_height = new_width / 1.77778
163
-
164
- if (new_width < new_height):
165
- new_height = alt_height
166
- else:
167
- new_width = alt_width
168
-
169
- self.image_width = new_width
170
- self.image_height = new_height
171
-
172
- self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(new_width, new_height))
173
- self.graph_label.configure(image=self.graph_image)
174
- self.graph_label.update_idletasks()
175
-
176
- def callback_test(self, *args):
177
- print("callback_test called")
178
-
179
- def update_graph(self, value):
180
- selected_graph = self.graph_selector_value.get()
181
- info = self.option_manager.get_project_data()
182
- folder = os.path.join(info['path'], info['name'])
183
-
184
- if not os.path.exists(folder):
185
- os.makedirs(folder)
186
-
187
- if (selected_graph == "Best Cost Stacked"):
188
- self.selected_graph_name = "best_cost_stacked"
189
- image_path = os.path.join(folder, self.selected_graph_name + ".png")
190
- if not os.path.exists(image_path):
191
- image_path = os.path.join("./images", "up.png")
192
- self.graph_image_obj = Image.open(image_path)
193
- self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
194
- self.graph_label.configure(image=self.graph_image)
195
- elif (selected_graph == "Best Cost by Round"):
196
- self.selected_graph_name = "best_cost_by_round"
197
- image_path = os.path.join(folder, self.selected_graph_name + ".png")
198
- if not os.path.exists(image_path):
199
- image_path = os.path.join("./images", "up.png")
200
- self.graph_image_obj = Image.open(image_path)
201
- self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
202
- self.graph_label.configure(image=self.graph_image)
203
- elif (selected_graph == "Iteration Table"):
204
- self.selected_graph_name = "table"
205
- image_path = os.path.join(folder, self.selected_graph_name + ".png")
206
- if not os.path.exists(image_path):
207
- image_path = os.path.join("./images", "up.png")
208
- self.graph_image_obj = Image.open(image_path)
209
- self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
210
- self.graph_label.configure(image=self.graph_image)
211
- elif (selected_graph == "Calibrated Parameters"):
212
- self.selected_graph_name = "calibrated_params_by_round"
213
- image_path = os.path.join(folder, self.selected_graph_name + ".png")
214
- if not os.path.exists(image_path):
215
- image_path = os.path.join("./images", "up.png")
216
- self.graph_image_obj = Image.open(image_path)
217
- self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
218
- self.graph_label.configure(image=self.graph_image)
219
-
220
- def save_project(self):
221
- metrics = self.option_manager.get_metrics()
222
- filename = asksaveasfilename(filetypes=[("JSON", "*.json")], initialfile="config", defaultextension="json", title="Save Project")
223
-
224
- try:
225
-
226
- # Convert metrics to json and save to file with proper spacing
227
- with open(filename, "w") as f:
228
- f.write(json.dumps(metrics, indent=4))
229
-
230
- self.save_button.configure(text="Saved!")
231
- self.after(3000, lambda: self.save_button.configure(text="Save"))
232
- except Exception as e:
233
- self.save_button.configure(text="Error!")
234
- print(e)
235
- self.after(3000, lambda: self.save_button.configure(text="Save"))
236
-
237
- def new_window(self):
238
- # Shell out and run ./main.py
239
- subprocess.Popen(["python3", "../mgpsogui.py"])
240
-
241
- def load_project(self):
242
-
243
- filename = askopenfilename(filetypes=[("JSON", "*.json")], title="Open Project", multiple=False)
244
- print(filename)
245
-
246
- try:
247
-
248
- # Load config.json and convert to metrics
249
- with open(filename, "r") as f:
250
- metrics = json.loads(f.read())
251
-
252
- self.option_manager.set_path(filename)
253
-
254
- if "arguments" in metrics:
255
- metrics["arguments"]["calibration_parameters"] = metrics["calibration_parameters"]
256
-
257
- if "service_parameters" in metrics:
258
- self.option_manager.set_service_parameters(metrics["service_parameters"])
259
- self.tabview.configure(state="enabled")
260
-
261
- print(metrics)
262
-
263
- self.option_manager.clear()
264
- self.option_manager.add_arguments(metrics["arguments"])
265
- self.option_manager.add_steps(metrics["steps"])
266
-
267
- self.steps_frame.clear()
268
- self.steps_frame.render()
269
-
270
- self.static_param_frame.clear()
271
- self.static_param_frame.render()
272
-
273
- self.calib_param_frame.clear()
274
- self.calib_param_frame.render()
275
-
276
- self.load_button.configure(text="Loaded!")
277
- self.after(3000, lambda: self.load_button.configure(text="Load"))
278
-
279
- except Exception as e:
280
- print(e)
281
- self.load_button.configure(text="Error!")
282
- self.after(3000, lambda: self.load_button.configure(text="Load"))
283
-
284
- def change_appearance_mode_event(self, new_appearance_mode: str):
285
- customtkinter.set_appearance_mode(new_appearance_mode)
286
-
287
- def change_scaling_event(self, new_scaling: str):
288
- new_scaling_float = int(new_scaling.replace("%", "")) / 100
289
- customtkinter.set_widget_scaling(new_scaling_float)
290
-
291
- def change_scaling_event(self, new_scaling: str):
292
- new_scaling_float = int(new_scaling.replace("%", "")) / 100
293
- customtkinter.set_widget_scaling(new_scaling_float)
294
-
295
- def make_request(self):
296
- service_url = self.service_url.get()
297
- try:
298
- response = requests.get(service_url)
299
-
300
- response_json = json.loads(response.text)
301
- status = response.status_code
302
-
303
- self.option_manager.set_service_parameters(response_json)
304
-
305
- self.service_status.delete('0.0', tk.END)
306
- self.service_status.insert(text=str(status), index='0.0')
307
- self.service_name.delete('0.0', tk.END)
308
- self.service_name.insert(text=str(response_json["metainfo"]["name"]), index='0.0')
309
- self.service_description.delete('0.0', tk.END)
310
- self.service_description.insert(text=str(response_json["metainfo"]["description"]), index='0.0')
311
- self.service_details.delete('0.0', tk.END)
312
- self.service_details.insert(text=json.dumps(response_json, indent=4), index='0.0')
313
-
314
- self.refresh_button.configure(fg_color="green")
315
- except Exception as e:
316
- self.refresh_button.configure(fg_color="red")
317
-
318
-
319
- def load(self):
320
- # Make HTTP request to service_url and save the result to bounds.json
321
-
322
- self.default_button_color = self.refresh_button.cget("fg_color")
323
-
324
- self.refresh_button.configure(fg_color="gray")
325
-
326
- self.after(10, self.make_request)
327
- self.after(3000, lambda: self.refresh_button.configure(fg_color=self.default_button_color))
328
-
329
- def run(self):
330
- metrics = self.option_manager.get_metrics()
331
- self.running_config = metrics
332
-
333
- #self.progress_bar.configure(mode="indeterminnate")
334
- #self.progress_bar.start()
335
- self.footer_progress_bar.configure(mode="indeterminnate")
336
- self.footer_progress_bar.start()
337
-
338
- self.data_x = [0]
339
- self.data_y = [0]
340
-
341
- self.progress_message_middle.configure(text="Calibration starting...")
342
- self.footer_progress_label.configure(text="Starting...")
343
-
344
- self.textbox.insert("0.0", "Starting calibration...\n\n")
345
- self.textbox.insert("0.0", "Calibration Parameters:\n")
346
- self.textbox.insert("0.0", json.dumps(metrics, indent=4) + "\n\n")
347
- try:
348
- info = self.option_manager.get_project_data()
349
- folder = os.path.join(info['path'], info['name'])
350
-
351
- self.train_process = Process(target=PSORunner.run_process, args=(stdout_queue, stderr_queue, results_queue, cosu_queue, metrics, folder))
352
- self.train_process.daemon = True
353
- self.train_process.start()
354
- self.after(1000, self.watch_loop)
355
- self.string_cache = ""
356
- self.data_cache = ""
357
-
358
- except Exception as e:
359
- self.textbox.insert("0.0", "An exception occurred!\n Exception: " + str(e) + "\n\n")
360
- self.textbox.insert("0.0", "Stack trace:\n")
361
- self.textbox.insert("0.0", traceback.format_exc())
362
- self.textbox.insert("0.0", "\n\n")
363
- self.textbox.insert("0.0", "Calibration failed!")
364
- self.progress_message_left.configure(text="")
365
- self.progress_message_middle.configure(text="Calibration failed! See error log below.")
366
- self.progress_message_right.configure(text="")
367
- self.footer_progress_label.configure(text="Failed")
368
- #self.progress_bar.stop()
369
- self.footer_progress_bar.stop()
370
- #self.progress_bar.configure(mode="determinate")
371
- self.footer_progress_bar.configure(mode="determinate")
372
- #self.progress_bar.set(0)
373
- self.footer_progress_bar.set(0)
374
-
375
- def stop(self):
376
- print("Stopping...")
377
- self.train_process.terminate()
378
-
379
- info = self.option_manager.get_project_data()
380
- folder = os.path.join(info['path'], info['name'])
381
-
382
- if not os.path.exists(folder):
383
- os.makedirs(folder)
384
-
385
- # Stop the process
386
- if (os.path.exists(os.path.join(folder, 'output.txt'))):
387
- os.remove(os.path.join(folder, 'output.txt'))
388
-
389
- if (os.path.exists(os.path.join(folder, 'error.txt'))):
390
- os.remove(os.path.join(folder, 'error.txt'))
391
-
392
- self.textbox.insert("0.0", "\nCalibration terminated!\n")
393
- #self.progress_bar.stop()
394
- self.footer_progress_bar.stop()
395
- #self.progress_bar.configure(mode="determinate")
396
- self.footer_progress_bar.configure(mode="determinate")
397
- #self.progress_bar.set(0)
398
- self.footer_progress_bar.set(0)
399
- self.progress_message_left.configure(text="")
400
- self.progress_message_middle.configure(text="Calibration stopped!")
401
- self.footer_progress_label.configure(text="Stopped")
402
- self.progress_message_right.configure(text="")
403
-
404
- def watch_loop(self):
405
- # Check if file exists:
406
-
407
- info = self.option_manager.get_project_data()
408
- folder = os.path.join(info['path'], info['name'])
409
-
410
- if not os.path.exists(folder):
411
- os.makedirs(folder)
412
-
413
- while True:
414
- try:
415
- stdout_line = stdout_queue.get_nowait()
416
-
417
- print("# " + stdout_line, flush=True)
418
- with open(os.path.join(folder, 'output.txt'), 'a') as f:
419
- f.write(stdout_line)
420
-
421
- except Empty:
422
- break
423
-
424
- while True:
425
- try:
426
- stderr_line = stderr_queue.get_nowait()
427
-
428
- print("? " + stderr_line, flush=True)
429
- with open(os.path.join(folder, 'error.txt'), 'a') as f:
430
- f.write(stderr_line)
431
-
432
- except Empty:
433
- break
434
-
435
- while True:
436
- try:
437
- cosu_line = cosu_queue.get_nowait()
438
-
439
- print("COSU " + cosu_line, flush=True)
440
- except Empty:
441
- break
442
-
443
- if (os.path.exists(os.path.join(folder, 'output.txt'))):
444
- with open(os.path.join(folder, 'output.txt'), 'r') as f:
445
- lines = f.readlines()
446
- lines_string = "".join(lines)
447
-
448
- new_characters = lines_string.replace(self.string_cache, "")
449
- # Update the textbox with characters not in self.string_cache
450
- self.textbox.insert('0.0', new_characters)
451
- self.string_cache = lines_string
452
- print(new_characters, end="")
453
-
454
- try:
455
- with open(os.path.join(folder, "output.txt"), "r") as f:
456
- text = f.read()
457
-
458
- calibrated_params_pattern = r"calibrated params: ({.*?})"
459
- best_particle_values_pattern = r"best particle values: (\[.*?\])"
460
- progress_pattern = r"Progress - best_round_cost:(.*?), rel_round_tol:(.*?), rtol:(.*?)\n"
461
-
462
- calibrated_params = re.findall(calibrated_params_pattern, text)
463
- best_particle_values = re.findall(best_particle_values_pattern, text)
464
- progress_values = re.findall(progress_pattern, text)
465
-
466
- for index, pp in enumerate(best_particle_values):
467
- pp = pp.strip()
468
- pp = pp.replace('[ ', '[')
469
- pp = pp.replace(' ', ',')
470
- pp = pp.replace(' ', ',')
471
- best_particle_values[index] = pp
472
-
473
- calibrated_params = [ast.literal_eval(i) for i in calibrated_params]
474
- best_particle_values = [ast.literal_eval(i) for i in best_particle_values]
475
- progress_values = [tuple(map(float, i)) for i in progress_values]
476
-
477
- print("Current params: " + str(calibrated_params), flush=True)
478
-
479
- GraphGenerator.calibrated_params_by_round(self.running_config['steps'], calibrated_params, self.option_manager)
480
- self.update_graph("")
481
- except Exception as e:
482
- # Print stack trace
483
- traceback.print_exc()
484
-
485
- print(e)
486
-
487
- if (os.path.exists(os.path.join(folder, 'error.txt'))):
488
- with open(os.path.join(folder, 'error.txt'), 'r') as f:
489
- lines = f.readlines()
490
- lines_string = "".join(lines)
491
- self.data_cache = lines_string
492
-
493
- pattern = r'(\d+)%\|.*\|(\d+)/(\d+)(?:,\sbest_cost=(\d+\.\d+))?' # The magic of AI
494
- matches = re.findall(pattern, self.data_cache)
495
- filtered_matches = [match for match in matches if match[3] != '']
496
- matches = filtered_matches
497
-
498
- if len(matches) > 0:
499
- df = pd.DataFrame(matches, columns=['percent', 'completed_rounds', 'total_rounds', 'best_cost'], dtype=float)
500
- df = df[df['best_cost'] != '']
501
- df = df.dropna()
502
- df = df.drop_duplicates()
503
- df['round_step'] = (df['completed_rounds'].diff() < 0).cumsum()
504
- df = df.drop_duplicates(subset=['completed_rounds', 'round_step'])
505
-
506
- GraphGenerator.best_cost_stacked(self.running_config['steps'], df, self.option_manager)
507
- GraphGenerator.best_cost_by_round(self.running_config['steps'], df, self.option_manager)
508
- GraphGenerator.table(self.running_config['steps'], df, self.option_manager)
509
- self.update_graph("")
510
-
511
- match = matches[-1]
512
- percent = int(match[0])
513
- completed_rounds = int(match[1])
514
- total_rounds = int(match[2])
515
- best_cost = float(match[3]) if match[3] else None
516
-
517
- if (percent > 0):
518
- #self.progress_bar.stop()
519
- self.footer_progress_bar.stop()
520
- #self.progress_bar.configure(mode="determinate")
521
- self.footer_progress_bar.configure(mode="determinate")
522
- #self.progress_bar.set(percent/100)
523
- self.footer_progress_bar.set(percent/100)
524
- self.progress_message_left.configure(text="Percent Complete: " + str(percent) + "%")
525
- self.progress_message_middle.configure(text=str(completed_rounds) + "/" + str(total_rounds))
526
- self.footer_progress_label.configure(text=str(completed_rounds) + "/" + str(total_rounds))
527
- self.progress_message_right.configure(text="Best Cost: " + str(best_cost))
528
- else:
529
- #self.progress_bar.configure(mode="indeterminate")
530
- self.footer_progress_bar.configure(mode="indeterminate")
531
- #self.progress_bar.start()
532
- self.footer_progress_bar.start()
533
- self.progress_message_left.configure(text="")
534
- self.progress_message_middle.configure(text="Starting new round...")
535
- self.footer_progress_label.configure(text="Next round...")
536
- self.progress_message_right.configure(text="")
537
-
538
-
539
-
540
- if self.train_process.is_alive():
541
- self.after(1000, self.watch_loop)
542
- else:
543
- # Check if queue is empty
544
- """
545
- if (self.process_queue.empty()):
546
- print("PROCESS FINISHED BUT WE GOT NO OBJECTS :(")
547
- self.footer_progress_label.configure(text="Failed")
548
- else:
549
- self.final_results = self.process_queue.get()
550
- print("PROCESS FINISHED WE GOT THINGS")
551
- print(self.final_results)
552
- self.footer_progress_label.configure(text="Finished")
553
- """
554
-
555
- #self.progress_bar.stop()
556
- self.footer_progress_bar.stop()
557
- #self.progress_bar.configure(mode="indeterminate")
558
- self.footer_progress_bar.configure(mode="indeterminate")
559
- #self.progress_bar.start()
560
- self.footer_progress_bar.start()
561
- self.progress_message_left.configure(text="")
562
- self.progress_message_middle.configure(text="Calibration finished!")
563
- self.progress_message_right.configure(text="")
564
- self.textbox.insert("0.0", "\nCalibration finished!\n")
59
+ def __init__(self):
60
+ super().__init__()
61
+
62
+ self.option_manager = om.OptionManager()
63
+
64
+ self.running_config = None
65
+ self.selected_graph_name = None
66
+
67
+ self.train_process = None
68
+ self.minikube_process = None
69
+ self.data_x = [0]
70
+ self.data_y = [0]
71
+
72
+ self.image_width = 1280
73
+ self.image_height = 720
74
+
75
+ # configure window
76
+ self.title("CSIP PSO")
77
+ self.geometry(f"{1920}x{1080}")
78
+
79
+ # configure grid layout (4x4)
80
+ self.grid_columnconfigure(0, weight=1)
81
+ self.grid_rowconfigure(1, weight=1)
82
+
83
+ header_padding_x = (5, 5)
84
+ header_padding_y = (10, 10)
85
+
86
+ self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0)
87
+ self.sidebar_frame.grid(row=0, column=0, sticky="nsew")
88
+ self.sidebar_frame.grid_columnconfigure(4, weight=1)
89
+ self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="CSIP PSO", font=customtkinter.CTkFont(size=20, weight="bold"))
90
+ self.logo_label.grid(row=0, column=0, padx=(20, 10), pady=header_padding_y)
91
+ self.save_button = customtkinter.CTkButton(self.sidebar_frame, text="Save", width=60, command=self.save_project)
92
+ self.save_button.grid(row=0, column=1, padx=header_padding_x, pady=header_padding_y)
93
+ self.load_button = customtkinter.CTkButton(self.sidebar_frame, text="Load", width=60, command=self.load_project)
94
+ self.load_button.grid(row=0, column=2, padx=header_padding_x, pady=header_padding_y)
95
+
96
+ # 4 is service URL
97
+ self.service_label = customtkinter.CTkLabel(self.sidebar_frame, text="Service:", anchor="w")
98
+ self.service_label.grid(row=0, column=3, padx=(80, 5), pady=header_padding_y)
99
+ self.service_url = customtkinter.CTkEntry(self.sidebar_frame, textvariable=self.option_manager.get_arguments()['url'])
100
+ self.service_url.grid(row=0, column=4, columnspan=1, padx=header_padding_x, pady=header_padding_y, sticky="nsew")
101
+ refresh_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "refresh.png")), size=(20, 20))
102
+ self.refresh_button = customtkinter.CTkButton(self.sidebar_frame, text=None, width=30, image=refresh_image, command=self.load)
103
+ self.refresh_button.grid(row=0, column=5, padx=(5, 80), pady=header_padding_y)
104
+
105
+ self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="Scale:", anchor="w")
106
+ self.scaling_label.grid(row=0, column=6, padx=header_padding_x, pady=header_padding_y)
107
+ self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["50%", "75%", "100%", "125%", "150%", "175%", "200%"], width=60,
108
+ command=self.change_scaling_event)
109
+ self.scaling_optionemenu.grid(row=0, column=7, padx=header_padding_x, pady=header_padding_y)
110
+ self.scaling_optionemenu.set("100%")
111
+
112
+ expand_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "expand.png")), size=(20, 20))
113
+ self.new_window = customtkinter.CTkButton(self.sidebar_frame, text=None, width=30, image=expand_image, command=self.new_window)
114
+ self.new_window.grid(row=0, column=8, padx=(5, 20), pady=header_padding_y)
115
+
116
+ self.tabview = customtkinter.CTkTabview(self, bg_color="transparent", fg_color="transparent")
117
+ self.tabview.grid(row=1, column=0, padx=(0, 0), pady=(10, 10), sticky="nsew")
118
+ tab1 = "Platform"
119
+ tab2 = "Setup"
120
+ tab3 = "Logs"
121
+ tab4 = "Results"
122
+ #tab5 = "Results"
123
+
124
+ self.tabview.add(tab1)
125
+ self.tabview.add(tab2)
126
+ self.tabview.add(tab4)
127
+ self.tabview.add(tab3)
128
+ #self.tabview.add(tab5)
129
+
130
+ #self.tabview.configure(state="disabled")
131
+
132
+ pt.create_tab(self, self.tabview.tab(tab1))
133
+ st.create_tab(self, self.tabview.tab(tab2))
134
+ rt.create_tab(self, self.tabview.tab(tab3))
135
+ vt.create_tab(self, self.tabview.tab(tab4))
136
+
137
+ self.footer_frame = customtkinter.CTkFrame(self, corner_radius=0)
138
+ self.footer_frame.grid(row=2, column=0, sticky="nsew")
139
+ self.footer_frame.grid_columnconfigure(4, weight=1)
140
+
141
+ self.footer_progress_label = customtkinter.CTkLabel(self.footer_frame, text="Stopped", width=150, font=customtkinter.CTkFont(size=16, weight="bold"), anchor="w")
142
+ self.footer_progress_label.grid(row=0, column=0, padx=(20, 5), pady=header_padding_y)
143
+
144
+ self.footer_progress_bar = customtkinter.CTkProgressBar(self.footer_frame)
145
+ self.footer_progress_bar.grid(row=0, column=4, padx=(50, 100), pady=header_padding_y, sticky="ew")
146
+ self.footer_progress_bar.set(0)
147
+
148
+ play_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "play.png")), size=(20, 20))
149
+ self.new_window = customtkinter.CTkButton(self.footer_frame, text=None, width=30, image=play_image, command=self.run)
150
+ self.new_window.grid(row=0, column=7, padx=(5, 5), pady=header_padding_y)
151
+
152
+ stop_image = customtkinter.CTkImage(Image.open(os.path.join("./images", "stop.png")), size=(20, 20))
153
+ self.new_window = customtkinter.CTkButton(self.footer_frame, text=None, width=30, image=stop_image, command=self.stop)
154
+ self.new_window.grid(row=0, column=8, padx=(5, 20), pady=header_padding_y)
155
+
156
+ def _resize_image(self, event):
157
+ self.graph_label.update_idletasks()
158
+ new_width = self.graph_label.winfo_width()
159
+ new_height = self.graph_label.winfo_height()
160
+
161
+ alt_width = new_height * 1.77778
162
+ alt_height = new_width / 1.77778
163
+
164
+ if (new_width < new_height):
165
+ new_height = alt_height
166
+ else:
167
+ new_width = alt_width
168
+
169
+ self.image_width = new_width
170
+ self.image_height = new_height
171
+
172
+ self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(new_width, new_height))
173
+ self.graph_label.configure(image=self.graph_image)
174
+ self.graph_label.update_idletasks()
175
+
176
+ def callback_test(self, *args):
177
+ print("callback_test called")
178
+
179
+ def update_graph(self, value):
180
+ selected_graph = self.graph_selector_value.get()
181
+ info = self.option_manager.get_project_data()
182
+ folder = os.path.join(info['path'], info['name'])
183
+
184
+ if not os.path.exists(folder):
185
+ os.makedirs(folder)
186
+
187
+ if (selected_graph == "Best Cost Stacked"):
188
+ self.selected_graph_name = "best_cost_stacked"
189
+ image_path = os.path.join(folder, self.selected_graph_name + ".png")
190
+ if not os.path.exists(image_path):
191
+ image_path = os.path.join("./images", "up.png")
192
+ self.graph_image_obj = Image.open(image_path)
193
+ self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
194
+ self.graph_label.configure(image=self.graph_image)
195
+ elif (selected_graph == "Best Cost by Round"):
196
+ self.selected_graph_name = "best_cost_by_round"
197
+ image_path = os.path.join(folder, self.selected_graph_name + ".png")
198
+ if not os.path.exists(image_path):
199
+ image_path = os.path.join("./images", "up.png")
200
+ self.graph_image_obj = Image.open(image_path)
201
+ self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
202
+ self.graph_label.configure(image=self.graph_image)
203
+ elif (selected_graph == "Iteration Table"):
204
+ self.selected_graph_name = "table"
205
+ image_path = os.path.join(folder, self.selected_graph_name + ".png")
206
+ if not os.path.exists(image_path):
207
+ image_path = os.path.join("./images", "up.png")
208
+ self.graph_image_obj = Image.open(image_path)
209
+ self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
210
+ self.graph_label.configure(image=self.graph_image)
211
+ elif (selected_graph == "Calibrated Parameters"):
212
+ self.selected_graph_name = "calibrated_params_by_round"
213
+ image_path = os.path.join(folder, self.selected_graph_name + ".png")
214
+ if not os.path.exists(image_path):
215
+ image_path = os.path.join("./images", "up.png")
216
+ self.graph_image_obj = Image.open(image_path)
217
+ self.graph_image = customtkinter.CTkImage(self.graph_image_obj, size=(self.image_width, self.image_height))
218
+ self.graph_label.configure(image=self.graph_image)
219
+
220
+ def save_project(self):
221
+ metrics = self.option_manager.get_metrics()
222
+ filename = asksaveasfilename(filetypes=[("JSON", "*.json")], initialfile="config", defaultextension="json", title="Save Project")
223
+
224
+ try:
225
+
226
+ # Convert metrics to json and save to file with proper spacing
227
+ with open(filename, "w") as f:
228
+ f.write(json.dumps(metrics, indent=4))
229
+
230
+ self.save_button.configure(text="Saved!")
231
+ self.after(3000, lambda: self.save_button.configure(text="Save"))
232
+ except Exception as e:
233
+ self.save_button.configure(text="Error!")
234
+ print(e)
235
+ self.after(3000, lambda: self.save_button.configure(text="Save"))
236
+
237
+ def new_window(self):
238
+ # Shell out and run ./main.py
239
+ subprocess.Popen(["python3", "../mgpsogui.py"])
240
+
241
+ def load_project(self):
242
+
243
+ filename = askopenfilename(filetypes=[("JSON", "*.json")], title="Open Project", multiple=False)
244
+ print(filename)
245
+
246
+ try:
247
+
248
+ # Load config.json and convert to metrics
249
+ with open(filename, "r") as f:
250
+ metrics = json.loads(f.read())
251
+
252
+ self.option_manager.set_path(filename)
253
+
254
+ if "arguments" in metrics:
255
+ metrics["arguments"]["calibration_parameters"] = metrics["calibration_parameters"]
256
+
257
+ if "service_parameters" in metrics:
258
+ self.option_manager.set_service_parameters(metrics["service_parameters"])
259
+ self.tabview.configure(state="enabled")
260
+
261
+ print(metrics)
262
+
263
+ self.option_manager.clear()
264
+ self.option_manager.add_arguments(metrics["arguments"])
265
+ self.option_manager.add_steps(metrics["steps"])
266
+
267
+ self.steps_frame.clear()
268
+ self.steps_frame.render()
269
+
270
+ self.static_param_frame.clear()
271
+ self.static_param_frame.render()
272
+
273
+ self.calib_param_frame.clear()
274
+ self.calib_param_frame.render()
275
+
276
+ self.load_button.configure(text="Loaded!")
277
+ self.after(3000, lambda: self.load_button.configure(text="Load"))
278
+
279
+ except Exception as e:
280
+ print(e)
281
+ self.load_button.configure(text="Error!")
282
+ self.after(3000, lambda: self.load_button.configure(text="Load"))
283
+
284
+ def change_appearance_mode_event(self, new_appearance_mode: str):
285
+ customtkinter.set_appearance_mode(new_appearance_mode)
286
+
287
+ def change_scaling_event(self, new_scaling: str):
288
+ new_scaling_float = int(new_scaling.replace("%", "")) / 100
289
+ customtkinter.set_widget_scaling(new_scaling_float)
290
+
291
+ def change_scaling_event(self, new_scaling: str):
292
+ new_scaling_float = int(new_scaling.replace("%", "")) / 100
293
+ customtkinter.set_widget_scaling(new_scaling_float)
294
+
295
+ def make_request(self):
296
+ service_url = self.service_url.get()
297
+ try:
298
+ response = requests.get(service_url)
299
+
300
+ response_json = json.loads(response.text)
301
+ status = response.status_code
302
+
303
+ self.option_manager.set_service_parameters(response_json)
304
+
305
+ self.service_status.delete('0.0', tk.END)
306
+ self.service_status.insert(text=str(status), index='0.0')
307
+ self.service_name.delete('0.0', tk.END)
308
+ self.service_name.insert(text=str(response_json["metainfo"]["name"]), index='0.0')
309
+ self.service_description.delete('0.0', tk.END)
310
+ self.service_description.insert(text=str(response_json["metainfo"]["description"]), index='0.0')
311
+ self.service_details.delete('0.0', tk.END)
312
+ self.service_details.insert(text=json.dumps(response_json, indent=4), index='0.0')
313
+
314
+ self.refresh_button.configure(fg_color="green")
315
+ except Exception as e:
316
+ self.refresh_button.configure(fg_color="red")
317
+
318
+
319
+ def load(self):
320
+ # Make HTTP request to service_url and save the result to bounds.json
321
+
322
+ self.default_button_color = self.refresh_button.cget("fg_color")
323
+
324
+ self.refresh_button.configure(fg_color="gray")
325
+
326
+ self.after(10, self.make_request)
327
+ self.after(3000, lambda: self.refresh_button.configure(fg_color=self.default_button_color))
328
+
329
+ def run(self):
330
+ metrics = self.option_manager.get_metrics()
331
+ self.running_config = metrics
332
+
333
+ #self.progress_bar.configure(mode="indeterminnate")
334
+ #self.progress_bar.start()
335
+ self.footer_progress_bar.configure(mode="indeterminnate")
336
+ self.footer_progress_bar.start()
337
+
338
+ self.data_x = [0]
339
+ self.data_y = [0]
340
+
341
+ self.progress_message_middle.configure(text="Calibration starting...")
342
+ self.footer_progress_label.configure(text="Starting...")
343
+
344
+ self.textbox.insert("0.0", "Starting calibration...\n\n")
345
+ self.textbox.insert("0.0", "Calibration Parameters:\n")
346
+ self.textbox.insert("0.0", json.dumps(metrics, indent=4) + "\n\n")
347
+ try:
348
+ info = self.option_manager.get_project_data()
349
+ folder = os.path.join(info['path'], info['name'])
350
+
351
+ self.train_process = Process(target=PSORunner.run_process, args=(stdout_queue, stderr_queue, results_queue, cosu_queue, metrics, folder))
352
+ self.train_process.daemon = True
353
+ self.train_process.start()
354
+ self.after(1000, self.watch_loop)
355
+ self.string_cache = ""
356
+ self.data_cache = ""
357
+
358
+ except Exception as e:
359
+ self.textbox.insert("0.0", "An exception occurred!\n Exception: " + str(e) + "\n\n")
360
+ self.textbox.insert("0.0", "Stack trace:\n")
361
+ self.textbox.insert("0.0", traceback.format_exc())
362
+ self.textbox.insert("0.0", "\n\n")
363
+ self.textbox.insert("0.0", "Calibration failed!")
364
+ self.progress_message_left.configure(text="")
365
+ self.progress_message_middle.configure(text="Calibration failed! See error log below.")
366
+ self.progress_message_right.configure(text="")
367
+ self.footer_progress_label.configure(text="Failed")
368
+ #self.progress_bar.stop()
369
+ self.footer_progress_bar.stop()
370
+ #self.progress_bar.configure(mode="determinate")
371
+ self.footer_progress_bar.configure(mode="determinate")
372
+ #self.progress_bar.set(0)
373
+ self.footer_progress_bar.set(0)
374
+
375
+ def stop(self):
376
+ print("Stopping...")
377
+ self.train_process.terminate()
378
+
379
+ info = self.option_manager.get_project_data()
380
+ folder = os.path.join(info['path'], info['name'])
381
+
382
+ if not os.path.exists(folder):
383
+ os.makedirs(folder)
384
+
385
+ # Stop the process
386
+ if (os.path.exists(os.path.join(folder, 'output.txt'))):
387
+ os.remove(os.path.join(folder, 'output.txt'))
388
+
389
+ if (os.path.exists(os.path.join(folder, 'error.txt'))):
390
+ os.remove(os.path.join(folder, 'error.txt'))
391
+
392
+ self.textbox.insert("0.0", "\nCalibration terminated!\n")
393
+ #self.progress_bar.stop()
394
+ self.footer_progress_bar.stop()
395
+ #self.progress_bar.configure(mode="determinate")
396
+ self.footer_progress_bar.configure(mode="determinate")
397
+ #self.progress_bar.set(0)
398
+ self.footer_progress_bar.set(0)
399
+ self.progress_message_left.configure(text="")
400
+ self.progress_message_middle.configure(text="Calibration stopped!")
401
+ self.footer_progress_label.configure(text="Stopped")
402
+ self.progress_message_right.configure(text="")
403
+
404
+ def watch_loop(self):
405
+ # Check if file exists:
406
+
407
+ info = self.option_manager.get_project_data()
408
+ folder = os.path.join(info['path'], info['name'])
409
+
410
+ if not os.path.exists(folder):
411
+ os.makedirs(folder)
412
+
413
+ while True:
414
+ try:
415
+ stdout_line = stdout_queue.get_nowait()
416
+
417
+ print("# " + stdout_line, flush=True)
418
+ with open(os.path.join(folder, 'output.txt'), 'a') as f:
419
+ f.write(stdout_line)
420
+
421
+ except Empty:
422
+ break
423
+
424
+ while True:
425
+ try:
426
+ stderr_line = stderr_queue.get_nowait()
427
+
428
+ print("? " + stderr_line, flush=True)
429
+ with open(os.path.join(folder, 'error.txt'), 'a') as f:
430
+ f.write(stderr_line)
431
+
432
+ except Empty:
433
+ break
434
+
435
+ while True:
436
+ try:
437
+ cosu_line = cosu_queue.get_nowait()
438
+
439
+ print("COSU " + cosu_line, flush=True)
440
+ except Empty:
441
+ break
442
+
443
+ if (os.path.exists(os.path.join(folder, 'output.txt'))):
444
+ with open(os.path.join(folder, 'output.txt'), 'r') as f:
445
+ lines = f.readlines()
446
+ lines_string = "".join(lines)
447
+
448
+ new_characters = lines_string.replace(self.string_cache, "")
449
+ # Update the textbox with characters not in self.string_cache
450
+ self.textbox.insert('0.0', new_characters)
451
+ self.string_cache = lines_string
452
+ print(new_characters, end="")
453
+
454
+ try:
455
+ with open(os.path.join(folder, "output.txt"), "r") as f:
456
+ text = f.read()
457
+
458
+ calibrated_params_pattern = r"calibrated params: ({.*?})"
459
+ best_particle_values_pattern = r"best particle values: (\[.*?\])"
460
+ progress_pattern = r"Progress - best_round_cost:(.*?), rel_round_tol:(.*?), rtol:(.*?)\n"
461
+
462
+ calibrated_params = re.findall(calibrated_params_pattern, text)
463
+ best_particle_values = re.findall(best_particle_values_pattern, text)
464
+ progress_values = re.findall(progress_pattern, text)
465
+
466
+ for index, pp in enumerate(best_particle_values):
467
+ pp = pp.strip()
468
+ pp = pp.replace('[ ', '[')
469
+ pp = pp.replace(' ', ',')
470
+ pp = pp.replace(' ', ',')
471
+ best_particle_values[index] = pp
472
+
473
+ calibrated_params = [ast.literal_eval(i) for i in calibrated_params]
474
+ best_particle_values = [ast.literal_eval(i) for i in best_particle_values]
475
+ progress_values = [tuple(map(float, i)) for i in progress_values]
476
+
477
+ print("Current params: " + str(calibrated_params), flush=True)
478
+
479
+ GraphGenerator.calibrated_params_by_round(self.running_config['steps'], calibrated_params, self.option_manager)
480
+ self.update_graph("")
481
+ except Exception as e:
482
+ # Print stack trace
483
+ traceback.print_exc()
484
+
485
+ print(e)
486
+
487
+ if (os.path.exists(os.path.join(folder, 'error.txt'))):
488
+ with open(os.path.join(folder, 'error.txt'), 'r') as f:
489
+ lines = f.readlines()
490
+ lines_string = "".join(lines)
491
+ self.data_cache = lines_string
492
+
493
+ pattern = r'(\d+)%\|.*\|(\d+)/(\d+)(?:,\sbest_cost=(\d+\.\d+))?' # The magic of AI
494
+ matches = re.findall(pattern, self.data_cache)
495
+ filtered_matches = [match for match in matches if match[3] != '']
496
+ matches = filtered_matches
497
+
498
+ if len(matches) > 0:
499
+ df = pd.DataFrame(matches, columns=['percent', 'completed_rounds', 'total_rounds', 'best_cost'], dtype=float)
500
+ df = df[df['best_cost'] != '']
501
+ df = df.dropna()
502
+ df = df.drop_duplicates()
503
+ df['round_step'] = (df['completed_rounds'].diff() < 0).cumsum()
504
+ df = df.drop_duplicates(subset=['completed_rounds', 'round_step'])
505
+
506
+ GraphGenerator.best_cost_stacked(self.running_config['steps'], df, self.option_manager)
507
+ GraphGenerator.best_cost_by_round(self.running_config['steps'], df, self.option_manager)
508
+ GraphGenerator.table(self.running_config['steps'], df, self.option_manager)
509
+ self.update_graph("")
510
+
511
+ match = matches[-1]
512
+ percent = int(match[0])
513
+ completed_rounds = int(match[1])
514
+ total_rounds = int(match[2])
515
+ best_cost = float(match[3]) if match[3] else None
516
+
517
+ if (percent > 0):
518
+ #self.progress_bar.stop()
519
+ self.footer_progress_bar.stop()
520
+ #self.progress_bar.configure(mode="determinate")
521
+ self.footer_progress_bar.configure(mode="determinate")
522
+ #self.progress_bar.set(percent/100)
523
+ self.footer_progress_bar.set(percent/100)
524
+ self.progress_message_left.configure(text="Percent Complete: " + str(percent) + "%")
525
+ self.progress_message_middle.configure(text=str(completed_rounds) + "/" + str(total_rounds))
526
+ self.footer_progress_label.configure(text=str(completed_rounds) + "/" + str(total_rounds))
527
+ self.progress_message_right.configure(text="Best Cost: " + str(best_cost))
528
+ else:
529
+ #self.progress_bar.configure(mode="indeterminate")
530
+ self.footer_progress_bar.configure(mode="indeterminate")
531
+ #self.progress_bar.start()
532
+ self.footer_progress_bar.start()
533
+ self.progress_message_left.configure(text="")
534
+ self.progress_message_middle.configure(text="Starting new round...")
535
+ self.footer_progress_label.configure(text="Next round...")
536
+ self.progress_message_right.configure(text="")
537
+
538
+
539
+
540
+ if self.train_process.is_alive():
541
+ self.after(1000, self.watch_loop)
542
+ else:
543
+ # Check if queue is empty
544
+ """
545
+ if (self.process_queue.empty()):
546
+ print("PROCESS FINISHED BUT WE GOT NO OBJECTS :(")
547
+ self.footer_progress_label.configure(text="Failed")
548
+ else:
549
+ self.final_results = self.process_queue.get()
550
+ print("PROCESS FINISHED WE GOT THINGS")
551
+ print(self.final_results)
552
+ self.footer_progress_label.configure(text="Finished")
553
+ """
554
+
555
+ #self.progress_bar.stop()
556
+ self.footer_progress_bar.stop()
557
+ #self.progress_bar.configure(mode="indeterminate")
558
+ self.footer_progress_bar.configure(mode="indeterminate")
559
+ #self.progress_bar.start()
560
+ self.footer_progress_bar.start()
561
+ self.progress_message_left.configure(text="")
562
+ self.progress_message_middle.configure(text="Calibration finished!")
563
+ self.progress_message_right.configure(text="")
564
+ self.textbox.insert("0.0", "\nCalibration finished!\n")
565
+
566
+ def get_results(self):
567
+ request: Client = Client()
568
+ for name, value in parameters.items():
569
+ # if parameter name has a / in it assume that is a file based parameter and therefore value needs to be an array
570
+ if "/" in name and type(value) is not list:
571
+ request.add_data(name, [value])
572
+ else:
573
+ request.add_data(name, value)
574
+
575
+ conf = {
576
+ 'service_timeout': 60.0 # (sec)
577
+ }
578
+ files: List[str] = [] #optional list of filenames
579
+
580
+ #Synchronous Call
581
+ result: Client = request.execute(CSIP_ENDPOINT, files=files, sync=True, conf=conf)
582
+
583
+ #Asynchronous Call
584
+ tsamp: float = 0
585
+ def callback(c: Client, progress: str):
586
+ tsamp2: float = time.time()
587
+ print('Halton Update {} - {} - {}'.format(halton_id, c.get_status(), tsamp2 - tsamp))
588
+
589
+
590
+ tsamp = time.time()
591
+ result: Client = request.execute_async(
592
+ CSIP_ENDPOINT,
593
+ files=files,
594
+ callback=callback,
595
+ first_poll=poll_time,
596
+ next_poll=poll_time,
597
+ conf=conf
598
+ )
599
+ # After recieving response
600
+ if result.is_finished():
601
+ print(result)
602
+ else:
603
+ print(result)
565
604
 
566
605
  def start():
567
- app = App()
568
- app.mainloop()
606
+ app = App()
607
+ app.mainloop()
569
608
 
570
609
  if __name__ == "__main__":
571
- app = App()
572
- app.mainloop()
610
+ app = App()
611
+ app.mainloop()