def begin_session(self): set_state(self.root, 'disabled') set_state(self.switch_button, 'enabled') self.real_root.withdraw() # Try to find SUMA. If we can't, look for a config # file telling us where it is. If there is none, # ask the user where it is and create a config file. try: self.suma_file = subprocess.check_output( ["which", "suma"]).strip() except subprocess.CalledProcessError: config_name = os.path.join( os.path.dirname(inspect.stack()[-1][1]), 'suma.config') try: config_fi = open(config_name, 'r') config = config_fi.read() config_fi.close() self.suma_file = json.loads(config)["suma"] if not self.suma_file: raise IOError() except (IOError, KeyError): try: self.configure() except ValueError: # We can't continue without a path to SUMA. sys.exit(1) # If we're not connected, we need to choose a local dataset or server init = None if self.net_thread and self.net_thread.authenticated: mode = 'server' else: init_dialog = tk.Toplevel() init_dialog.title("COVI: Choose data source") if self.net_thread: self.net_thread.job_q.put(["die"]) # self.net_thread = NetworkThread() init = InitWindow(init_dialog, NetworkThread()) center_window(init_dialog) root.wait_window(init_dialog) mode = init.mode if mode == 'server': if init: self.net_thread = init.net_thread dset_dialog = ServerDsetWindow(self.real_root, net_thread=self.net_thread, title="COVI: %s: Datasets"%init.user_var.get()) else: dset_dialog = ServerDsetWindow(self.real_root, net_thread=self.net_thread, title="COVI: Server Datasets") if hasattr(dset_dialog, 'dset'): self.dset = dset_dialog.dset if type(self.dset) == list: dset_name = '/'.join(self.dset) else: dset_name = self.dset if type(self.dset) == list: self.proc_thread = ProcessingThread(dset=self.dset[1], net_thread=self.net_thread, owner=self.dset[0], suma_file = self.suma_file) else: self.proc_thread = ProcessingThread(dset=self.dset, net_thread=self.net_thread, suma_file = self.suma_file) self.proc_thread.start() elif mode == 'local': self.dset = init.dset_var.get() # TODO: Load dataset & surfaces sv_window = LocalSpecAndVolWindow(self.real_root, title="COVI: Select Spec " +"and Volume Files", dset_path=self.dset) if sv_window.spec_file and sv_window.vol_file: self.spec_file = sv_window.spec_file self.vol_file = sv_window.vol_file self.annot_file = sv_window.annot_file self.proc_thread = ProcessingThread( spec_file=self.spec_file, surfvol_file=self.vol_file, dset_path = self.dset, annot_file = self.annot_file, suma_file = self.suma_file) self.proc_thread.start() # Re-enable main window widgets set_state(self.real_root, 'enabled') self.real_root.deiconify()
class MainWindow: def begin_session(self): set_state(self.root, 'disabled') set_state(self.switch_button, 'enabled') self.real_root.withdraw() # Try to find SUMA. If we can't, look for a config # file telling us where it is. If there is none, # ask the user where it is and create a config file. try: self.suma_file = subprocess.check_output( ["which", "suma"]).strip() except subprocess.CalledProcessError: config_name = os.path.join( os.path.dirname(inspect.stack()[-1][1]), 'suma.config') try: config_fi = open(config_name, 'r') config = config_fi.read() config_fi.close() self.suma_file = json.loads(config)["suma"] if not self.suma_file: raise IOError() except (IOError, KeyError): try: self.configure() except ValueError: # We can't continue without a path to SUMA. sys.exit(1) # If we're not connected, we need to choose a local dataset or server init = None if self.net_thread and self.net_thread.authenticated: mode = 'server' else: init_dialog = tk.Toplevel() init_dialog.title("COVI: Choose data source") if self.net_thread: self.net_thread.job_q.put(["die"]) # self.net_thread = NetworkThread() init = InitWindow(init_dialog, NetworkThread()) center_window(init_dialog) root.wait_window(init_dialog) mode = init.mode if mode == 'server': if init: self.net_thread = init.net_thread dset_dialog = ServerDsetWindow(self.real_root, net_thread=self.net_thread, title="COVI: %s: Datasets"%init.user_var.get()) else: dset_dialog = ServerDsetWindow(self.real_root, net_thread=self.net_thread, title="COVI: Server Datasets") if hasattr(dset_dialog, 'dset'): self.dset = dset_dialog.dset if type(self.dset) == list: dset_name = '/'.join(self.dset) else: dset_name = self.dset if type(self.dset) == list: self.proc_thread = ProcessingThread(dset=self.dset[1], net_thread=self.net_thread, owner=self.dset[0], suma_file = self.suma_file) else: self.proc_thread = ProcessingThread(dset=self.dset, net_thread=self.net_thread, suma_file = self.suma_file) self.proc_thread.start() elif mode == 'local': self.dset = init.dset_var.get() # TODO: Load dataset & surfaces sv_window = LocalSpecAndVolWindow(self.real_root, title="COVI: Select Spec " +"and Volume Files", dset_path=self.dset) if sv_window.spec_file and sv_window.vol_file: self.spec_file = sv_window.spec_file self.vol_file = sv_window.vol_file self.annot_file = sv_window.annot_file self.proc_thread = ProcessingThread( spec_file=self.spec_file, surfvol_file=self.vol_file, dset_path = self.dset, annot_file = self.annot_file, suma_file = self.suma_file) self.proc_thread.start() # Re-enable main window widgets set_state(self.real_root, 'enabled') self.real_root.deiconify() def configure(self): config_name = os.path.join( os.path.dirname(inspect.stack()[-1][1]), 'suma.config') config_dialog = ConfigDialog(parent=self.root, title='COVI Configuration') self.suma_file = config_dialog.suma_file if self.suma_file: config = {"suma":self.suma_file} config_fi = open(config_name, 'w') json.dump(config, config_fi, indent=4, separators=(',',':')) if self.proc_thread and self.proc_thread.ready(): self.proc_thread.job_q.put_nowait(["suma_path", self.suma_file]) return True else: raise ValueError("The user did not specify the path to SUMA.") def __init__(self, real_root): self.proc_thread = False self.net_thread = False self.real_root = real_root self.real_root.resizable(0,0) self.real_root.title("COVI") self.menu_vars = [] self.real_root.protocol("WM_DELETE_WINDOW", self.cleanup) # Put all of our widgets inside a frame, not the Toplevel object self.root = ttk.Frame(real_root) root = self.root root.pack(fill=tk.BOTH, expand=tk.YES) self.body() #rootlabel = ttk.Label(root, # text="I'm the main window!\nWhen I grow up, I'll be full of widgets!") #rootlabel.pack() center_window(real_root) set_state(self.root, 'disabled') set_state(self.switch_button, 'enabled') self.begin_session() set_state(self.root, 'disabled') set_state(self.switch_button, 'enabled') self.poll() def poll(self): ''' Check for events from SUMA. ''' if self.proc_thread: if self.proc_thread not in threading.enumerate(): tkMessageBox.showwarning("Connection to SUMA lost", "The connection to SUMA has been lost. "+ "Reload your dataset to continue using COVI.") self.proc_not_ready() self.proc_thread = False if self.proc_thread and self.proc_thread.ready(): try: res = self.proc_thread.res_q.get_nowait() self.proc_thread.res_q.task_done() if res: if res[0] == 'node': self.node_number_label['text'] = '%i'%res[1] elif res[0] == 'cluster': self.cluster_number_label['text'] = '%i'%res[1] elif res[0] == 'area': self.curr_area_label['text'] = res[1] elif res[0] == 'ready': # Re-enable main window widgets set_state(self.real_root, 'enabled') except Empty: pass if self.net_thread and self.net_thread not in threading.enumerate(): tkMessageBox.showwarning("Connection to server lost", "The connection to the server has been lost. "+ "Reload your dataset to continue using COVI.") self.proc_not_ready() self.net_thread = False self.root.after(100, self.poll) def cleanup(self): if self.net_thread: self.net_thread.job_q.put_nowait(["close"]) self.net_thread.job_q.put_nowait(["die"]) if self.proc_thread: self.proc_thread.job_q.put_nowait(["die"]) self.real_root.quit() def proc_not_ready(self): ''' What to do if no dataset is loaded into SUMA. Disable interface widgets that won't work without SUMA and display a message. ''' set_state(self.root, 'disabled') set_state(self.switch_button, 'enabled') """ tkMessageBox.showwarning("Warning", "A dataset needs to be loaded into SUMA to do that.")""" def scale_command(self, event=None): threshold = self.threshold.get() self.threshold_label['text'] = str(threshold) def switch_command(self): ''' Switch datasets ''' answer = True if self.proc_thread and self.proc_thread.ready(): answer = tkMessageBox.askyesno("Warning", "This will end your current COVI session. Continue?") if answer: if self.proc_thread: self.proc_thread.job_q.put_nowait(["die"]) self.proc_thread = False self.proc_not_ready() self.begin_session() def open_command(self): pass def mode_command(self, mode): if self.proc_thread and self.proc_thread.ready(): self.proc_thread.job_q.put_nowait(["shape", mode]) else: self.proc_not_ready() def color_command(self, color): if self.proc_thread and self.proc_thread.ready(): if color == 'annot' and not self.proc_thread.annot: tkMessageBox.showerror("Coloring Error", "Can't use brain area-based coloring without an annot file.") self.color_var.set(self.last_color) return elif color == 'brightness' and self.proc_thread.no_surface: tkMessageBox.showerror("Coloring Error", "Coordinate-based coloring can't be used, since "+ "COVI could not find a valid FreeSurfer ASCII surface "+ "file for the current surface. You can use the AFNI "+ "program ConvertSurface to create a FreeSurfer ASCII "+ "file for the current surface.") self.color_var.set(self.last_color) return else: self.proc_thread.job_q.put_nowait(["color", color]) self.last_color = color else: self.proc_not_ready() def redraw_command(self): if self.proc_thread and self.proc_thread.ready(): threshold = float(self.threshold.get()) # Avoid divide by zero error threshold = max(threshold, 10**-9) self.proc_thread.job_q.put_nowait(["threshold", float(self.threshold.get())/100.]) self.proc_thread.job_q.put_nowait(["redraw"]) else: self.proc_not_ready() def relaunch_command(self): if self.proc_thread and self.proc_thread.ready(): self.proc_thread.job_q.put_nowait(['suma']) else: self.proc_not_ready() def disconnect_command(self): ''' End a network session and disconnect from the server ''' answer = tkMessageBox.askyesno("Warning", "This will end your current COVI session. Continue?") if not answer: return if self.net_thread: if self.net_thread in threading.enumerate(): self.net_thread.job_q.put(["close"]) self.net_thread.job_q.put(["die"]) self.net_thread = False if self.proc_thread and self.proc_thread.ready(): self.proc_thread.job_q.put(["die"]) else: tkMessageBox.showinfo("Can't disconnect from server", "COVI Client is not connected to a server.") def make_mode_menu(self, root_menu): ''' Make a menu to choose among graph modes. ''' if not hasattr(self, "mode_var"): self.mode_var = tk.StringVar() self.mode_var.set("sized spheres") mode_menu = tk.Menu(root_menu, tearoff=0) mode_menu.add_radiobutton(label="Paths", variable=self.mode_var, command=lambda: self.mode_command("paths"), value="paths") mode_menu.add_radiobutton(label="Spheres", variable=self.mode_var, command=lambda: self.mode_command("spheres"), value="spheres") mode_menu.add_radiobutton(label="Sized Spheres", variable=self.mode_var, command=lambda: self.mode_command("sized spheres"), value="sized spheres") return mode_menu def make_color_menu(self, root_menu): ''' Make a menu to choose among color modes. ''' if not hasattr(self, "mode_var"): self.mode_var = tk.StringVar() if not hasattr(self, "color_var"): self.color_var = tk.StringVar() self.color_var.set("heat") color_menu = tk.Menu(self.edit_menu,tearoff=0) color_menu.add_radiobutton(label="Heatmap", command=lambda: self.color_command("heat"), variable=self.color_var, value="heat") color_menu.add_radiobutton(label="Coordinate-based", command=lambda: self.color_command("brightness"), variable=self.color_var, value="brightness") color_menu.add_radiobutton(label=" Brain Area-based", command=lambda: self.color_command("annot"), variable=self.color_var, value="annot") return color_menu def body(self): self.threshold = tk.IntVar(0) self.menu = tk.Menu() self.file_menu = tk.Menu(self.menu, tearoff=0) self.file_menu.add_command(label="Open dataset/Connect to server", command=self.begin_session) self.file_menu.add_command(label="Disconnect from server", command=self.disconnect_command) self.file_menu.add_separator() self.file_menu.add_command(label="Quit", command=self.cleanup) self.menu.add_cascade(label="File", menu=self.file_menu) self.edit_menu = tk.Menu(self.menu, tearoff=0) self.edit_menu.add_cascade(label="Graph mode", menu=self.make_mode_menu(self.edit_menu)) self.last_color = 'heat' self.edit_menu.add_cascade(label="Color mode", menu=self.make_color_menu(self.edit_menu)) self.edit_menu.add_command(label="Relaunch SUMA", command=self.relaunch_command) self.edit_menu.add_command(label="Reconfigure", command=self.configure) self.menu.add_cascade(label="Edit", menu=self.edit_menu) self.real_root.config(menu=self.menu) self.scale = ttk.Scale(self.root, from_=100, to=0, variable=self.threshold, orient=tk.VERTICAL, command=self.scale_command) self.threshold.set(50) self.scale.grid(column=0, row=0, rowspan=8, sticky=(tk.N+tk.S), padx='3m', pady='1m') self.threshold_label = ttk.Label(self.root, text="0", justify=tk.LEFT) self.threshold_label.grid(column=0, row=8) self.scale_command() self.node_label = ttk.Label(self.root, text="Node") self.node_label.grid(column=1, row=0, sticky=tk.W+tk.E, padx='1m') self.node_number_label = ttk.Label(self.root, text='%i'%0, width='6', relief=tk.SUNKEN, justify=tk.RIGHT) self.node_number_label.grid(column=1, row=1, sticky=tk.W+tk.E, padx='1m') self.cluster_label = ttk.Label(self.root, text="Cluster") self.cluster_label.grid(column=1, row=2, sticky=tk.W+tk.E, padx='1m') self.cluster_number_label = ttk.Label(self.root, text='%i'%0, width='6', relief=tk.SUNKEN, justify=tk.RIGHT) self.cluster_number_label.grid(column=1, row=3, sticky=tk.W+tk.E, padx='1m') self.area_label = ttk.Label(self.root, text="Area") self.area_label.grid(column=1, row=4, sticky=tk.W+tk.E, padx='1m') self.curr_area_label = ttk.Label(self.root, text='', relief=tk.SUNKEN, justify=tk.RIGHT) self.curr_area_label.grid(column=1, row=5, sticky=tk.W+tk.E, padx='1m') """ self.open_button = ttk.Button(self.root, text= "Open\nDataset", command=self.open_command) self.open_button.grid(column=1, row=3, sticky=tk.W+tk.E, padx='1m') """ self.switch_button = ttk.Button(self.root, text= "Switch\nDataset", command=self.switch_command) self.switch_button.grid(column=1, row=6, sticky=tk.W+tk.E, padx='1m') self.mode_button = ttk.Menubutton(self.root, text= "Graph\nMode") self.mode_button['menu'] = self.make_mode_menu(self.mode_button) self.mode_button.grid(column=1, row=7, sticky=tk.W+tk.E, padx='1m') self.color_button = ttk.Menubutton(self.root, text= "Color\nMode") self.color_button['menu'] = self.make_color_menu(self.color_button) self.color_button.grid(column=1, row=8, sticky=tk.W+tk.E, padx='1m') self.redraw_button = ttk.Button(self.root, text= "Redraw", command=self.redraw_command) self.redraw_button.grid(column=0, row=9, sticky=(tk.W+tk.E), columnspan=2, padx='1m') add_padding(self.root)