Source code for neurobooth_os.gui

# -*- coding: utf-8 -*-
"""
Created on Fri Apr  2 08:01:51 2021

@author: neurobooth
"""

import sys
import time
import threading
from optparse import OptionParser
from datetime import datetime

import PySimpleGUI as sg
import liesl

import neurobooth_os.main_control_rec as ctr_rec
from neurobooth_os.realtime.lsl_plotter import create_lsl_inlets, stream_plotter
from neurobooth_os.netcomm import get_messages_to_ctr, node_info, NewStdout
from neurobooth_os.layouts import _main_layout, _win_gen, _init_layout, write_task_notes
import neurobooth_os.iout.metadator as meta
from neurobooth_os.iout.split_xdf import split_sens_files, get_xdf_name
from neurobooth_os.iout import marker_stream
import neurobooth_os.config as cfg

def _process_received_data(serv_data, window):
    """ Gets string data from other servers and create PySimpleGui window events.

    Parameters
    ----------
    serv_data : str
        Data sent by other servers
    window : object
        PySimpleGui window object
    """
    
    # Split server name and data
    serv_data = serv_data.split(":::")[-1]
    for data_row in serv_data.split("\n"):

        if "-OUTLETID-" in data_row:
            # -OUTLETID-:outlet_name:uuid
            evnt, outlet_name, outlet_id = data_row.split(":")
            window.write_event_value('-OUTLETID-', f"['{outlet_name}', '{outlet_id}']")

        elif "UPDATOR:" in data_row:
            # UPDATOR:-elem_key-
            elem = data_row.split(":")[1]
            window.write_event_value('-update_butt-', elem)

        elif "Initiating task:" in data_row:
            # Initiating task:task_id:obs_id:tech_obs_log_id:tsk_strt_time
            _, task_id, obs_id, obs_log_id, tsk_strt_time = data_row.split(":")
            window.write_event_value('task_initiated', f"['{task_id}', '{obs_id}', '{obs_log_id}', '{tsk_strt_time}']")

        elif "Finished task:" in data_row:
            # Finished task: task_id
            _, task_id = data_row.split(":")
            window.write_event_value('task_finished', task_id)

        elif "-new_filename-" in data_row:
            # new file created, data_row = "-new_filename-:stream_name:video_filename"
            event, stream_name, filename = data_row.split(":")
            window.write_event_value(event, f"{stream_name}, {filename}]")
            

[docs]def gui(remote=False, database='neurobooth'): """Start the Graphical User Interface. Parameters ---------- remote : bool If True, uses ssh_tunnel to connect and database = mock_neurobooth to the database. Use False if on site. database : str The database name """ if remote: database = "mock_neurobooth" nodes = ('dummy_acq', 'dummy_stm') host_ctr, port_ctr = node_info("dummy_ctr") else: nodes = ('acquisition', 'presentation') host_ctr, port_ctr = node_info("control") conn = meta.get_conn(remote=remote, database=database) window = _win_gen(_init_layout, conn) plttr = stream_plotter() tech_obs_log = meta._new_tech_log_dict() stream_ids, inlets = {}, {} plot_elem, inlet_keys = [], [] # Create variables for out dict subject_id_date = None exit_flag=None # where to break break_ = False # break loop if True obs_log_id = None t_obs_id = None task_id = None rec_fname = None, # lsl file name session = None vidf_mrkr = None statecolors = {"-init_servs-": ["green", "yellow"], "-Connect-": ["green", "yellow"], } steps = [] # keep track of steps done event, values = window.read(.1) while True: event, values = window.read(.5) ############################################################ # Initial Window -> Select subject, study and tasks ############################################################ if event == "study_id": study_id = values[event] tech_obs_log["study_id"] = study_id collection_ids = meta.get_collection_ids(study_id, conn) window["collection_id"].update(values=collection_ids) elif event == 'find_subject': subject_df = meta.get_subject_ids(conn, values['first_name'], values['last_name']) window['dob'].update(values=subject_df['date_of_birth']) elif event == 'select_subject': subject = subject_df.iloc[window['dob'].get_indexes()] subject_id = subject.name first_name = subject['first_name_birth'] last_name = subject['last_name_birth'] window['dob'].update(values=[f'Subject ID: {subject_id}']) window['first_name'].update(disabled=True) window['last_name'].update(disabled=True) elif event == "collection_id": collection_id = values[event] tech_obs_log["collection_id"] = collection_id tasks_obs = meta.get_tasks(collection_id, conn) task_list = [] for task in tasks_obs: task_id, *_ = meta._get_task_param(task, conn) task_list.append(task_id) window["_tasks_"].update(value=", ".join(task_list)) elif event == "_init_sess_save_": if values["_tasks_"] == "": sg.PopupError('No task combo') else: sess_info = values sess_info.update({'subject_id': subject_id, 'first_name': first_name, 'last_name': last_name}) staff_id = sess_info['staff_id'] tech_obs_log["staff_id"] = sess_info['staff_id'] tech_obs_log["subject_id"] = sess_info['subject_id'] tech_obs_log["study_id-date"] = f'{subject_id}_{datetime.now().strftime("%Y-%m-%d")}' subject_id_date = tech_obs_log["study_id-date"] window.close() # Open new layout with main window window = _win_gen(_main_layout, sess_info, remote) # Start a threaded socket CTR server once main window generated callback_args = window server_thread = threading.Thread(target=get_messages_to_ctr, args=(_process_received_data, remote, host_ctr, port_ctr, callback_args,), daemon=True) server_thread.start() # Rerout print for ctr server to capture data in remote case if remote: time.sleep(.1) sys.stdout = NewStdout("mock", target_node="dummy_ctr", terminal_print=True) ############################################################ # Main Window -> Run neurobooth session ############################################################ # Start servers on STM, ACQ elif event == "-init_servs-": window['-init_servs-'].Update(button_color=('black', 'red')) event, values = window.read(.1) ctr_rec.start_servers(nodes=nodes, remote=remote, conn=conn) time.sleep(1) # Turn on devices and start LSL outlet stream elif event == '-Connect-': window['-Connect-'].Update(button_color=('black', 'red')) event, values = window.read(.1) vidf_mrkr = marker_stream('videofiles') # Create event to capture outlet_id window.write_event_value('-OUTLETID-', f"['{vidf_mrkr.name}', '{vidf_mrkr.outlet_id}']") ctr_rec.prepare_devices(f"{collection_id}:{str(tech_obs_log)}", nodes=nodes) print('Connecting devices') # Real-time plotting of inlet data. elif event == 'plot': # if no inlets send event to prepare devices and make popup error if len(inlets) == 0: window.write_event_value('-Connect-') sg.PopupError('No inlet devices detected, preparing. Press plot once prepared') if plttr.pltotting_ts is True: plttr.inlets = inlets else: plttr.start(inlets) # Start task presentation. elif event == 'Start': tasks = [k for k, v in values.items() if "task" in k and v == True] window['Start'].Update(button_color=('black', 'yellow')) if len(tasks): running_task = "-".join(tasks) # task_name can be list of task1-task2-task3 ctr_rec.task_presentation(running_task, sess_info['subject_id'], node=nodes[1]) steps.append("task_started") else: sg.PopupError('No task selected') elif event == "Pause tasks": if "task_started" not in steps: sg.PopupError('Tasks not started') else: ctr_rec.message_presentation("pause tasks", nodes) resp = sg.Popup('The next task will be paused', custom_text=('Continue tasks', 'Stop tasks')) if resp == 'Continue tasks': ctr_rec.message_presentation("unpause tasks", nodes) elif resp == 'Stop tasks': ctr_rec.message_presentation("stop tasks", nodes) # Save notes to a txt elif event == "_save_notes_": if values["_notes_taskname_"] == '': sg.PopupError('Pressed saving notes without task, select one in the dropdown list') continue write_task_notes(subject_id_date, staff_id, values['_notes_taskname_'], values['notes']) window["notes"].Update('') # Shut down the other servers and stops plotting elif event == 'Shut Down' or event == sg.WINDOW_CLOSED: plttr.stop() ctr_rec.shut_all(nodes=nodes) break ################################################################################## # Thread events from process_received_data -> received messages from other servers ################################################################################## out = dict(exit_flag=exit_flag, # where to break break_ = break_, # break loop if True obs_log_id = obs_log_id, t_obs_id = t_obs_id, task_id = task_id, rec_fname = rec_fname, # lsl file name session = session, vidf_mrkr = vidf_mrkr, ) out = ctr_event_handler(window, event, values, conn, subject_id_date, statecolors=statecolors, stream_ids=stream_ids, inlets=inlets, out=out) obs_log_id = out['obs_log_id'] t_obs_id = out['t_obs_id'] task_id = out['task_id'] rec_fname = out['rec_fname'] session = out['session'] vidf_mrkr = out['vidf_mrkr'] ################################################################################## # Conditionals handling inlets for plotting and recording ################################################################################## # Print LSL inlet names in GUI if inlet_keys != list(inlets): inlet_keys = list(inlets) window['inlet_State'].update("\n".join(inlet_keys)) window.close() if remote: sys.stdout = sys.stdout.terminal else: window['-OUTPUT-'].__del__() print("Session terminated")
def ctr_event_handler(window, event, values, conn, subject_id, statecolors=None, stream_ids={}, inlets={}, out=None): """Handles events from ctr server thread Parameters ---------- window : callable pysimple gui window event : str event name from window.read() values : dict values from window.read() conn : callable connector object to pgadmin database subject_id : str Name of the subject statecolors : dict, optional Color of the buttons, by default None stream_ids : dict, optional Ids of the lsl streams, by default {} inlets : dict, optional Lsl inlets, by default {} out : dict, optional Dictionary with variables, by default None Returns ------- out dict variables from out arg """ if out is None: out = dict(exit_flag=None, # where to break break_ = False, # break loop if True obs_log_id = None, t_obs_id = None, task_id = None, rec_fname = None, # lsl file name session = liesl.Session(), vidf_mrkr = marker_stream('videofiles'), ) # Update colors for: -init_servs-, -Connect-, Start buttons if event == "-update_butt-": if values['-update_butt-'] in list(statecolors): # 2 colors for init_servers and Connect, 1 connected, 2 connected if len(statecolors[values['-update_butt-']]): color = statecolors[values['-update_butt-']].pop() window[values['-update_butt-']].Update(button_color=('black', color)) # Signal start LSL session if both servers devices are ready: if values['-update_butt-'] == "-Connect-" and color == "green": window.write_event_value('start_lsl_session', 'none') # Create LSL session streamargs = [{'name': n} for n in list(inlets)] out["session"] = liesl.Session(prefix='', streamargs=streamargs, mainfolder=cfg.paths["data_out"] ) print("LSL session with: ", list(inlets)) if out['exit_flag'] =='prepared': out['break_'] = True # Create LSL inlet stream elif event == "-OUTLETID-": # event values -> f"['{outlet_name}', '{outlet_id}'] outlet_name, outlet_id = eval(values[event]) # update the inlet if new or different source_id if stream_ids.get(outlet_name) is None or outlet_id != stream_ids[outlet_name]: stream_ids[outlet_name] = outlet_id new_inlet = create_lsl_inlets({outlet_name: outlet_id}) inlets.update(new_inlet) # Signal a task started: record LSL data and update gui elif event == 'task_initiated': # event values -> f"['{task_id}', '{t_obs_id}', '{tech_obs_log_id}, '{tsk_strt_time}'] task_id, t_obs_id, obs_log_id, tsk_strt_time = eval(values[event]) out["obs_log_id"] = obs_log_id out["t_obs_id"] = t_obs_id out["task_id"] = task_id print(f"task initiated: task_id {task_id}, t_obs_id {t_obs_id}, obs_log_id :{obs_log_id}") # Start LSL recording rec_fname = f"{subject_id}_{tsk_strt_time}_{t_obs_id}" # Start LSL recording out["rec_fname"] = rec_fname out['session'].start_recording(out["rec_fname"]) window["task_title"].update("Running Task:") window["task_running"].update(task_id, background_color="red") window['Start'].Update(button_color=('black', 'red')) # Signal a task ended: stop LSL recording and update gui elif event == 'task_finished': task_id = values[event] # Stop LSL recording out['session'].stop_recording() window["task_running"].update(task_id, background_color="green") window['Start'].Update(button_color=('black', 'green')) xdf_fname = get_xdf_name(out['session'], out["rec_fname"]) split_sens_files(xdf_fname, out["obs_log_id"], out["t_obs_id"], conn) if out['exit_flag'] =='task_end': out['break_'] = True # Send a marker string with the name of the new video file created elif event == "-new_filename-": out["vidf_mrkr"].push_sample([values[event]]) print(f"pushed videfilename mark {values[event]}") return out def main(): """The starting point of Neurobooth""" parser = OptionParser() parser.add_option("-r", "--remote", dest="remote", action="store_true", default=False, help="Access database using remote connection") (options, args) = parser.parse_args() gui(remote=options.remote) if __name__ == '__main__': main()