예제 #1
0
    def save_simulation_data(self, attribute=None):
        data_file = os.path.join(self.data_loc, "npred_"+str(self.N_pred)+"_nwords_"+str(self.num_words_total)+"_lcon_"
                                 +str(int(self.lm_left_context))+"_wdiff_"+str(round(np.exp(self.win_diff_base)))
                                 +"_rot_"+str(self.rotate_index)+"_cor_"+str(self.easy_phrase)+"_fp_"
                                 +str(self.false_positive_rate)+".p")
        data_handel = PickleUtil(data_file)

        dist_id_file = os.path.join(self.data_loc, "dist_id.p")
        dist_id_handel = PickleUtil(dist_id_file)
        dist_id_handel.safe_save(self.click_dist)

        data_dict = dict()
        data_dict["N_pred"] = self.N_pred
        data_dict["prob_thresh"] = self.prob_thres
        data_dict["win_diff"] = self.win_diff_base
        data_dict["num_words"] = self.num_words_total
        data_dict["time_rotate"] = self.time_rotate
        data_dict["false_positive"] = self.false_positive_rate
        data_dict["errors"] = self.error_rate_avg
        data_dict["selections"] = self.sel_per_min
        data_dict["characters"] = self.char_per_min
        data_dict["presses_sel"] = self.press_per_sel
        data_dict["presses_char"] = self.press_per_char
        data_dict["presses_word"] = self.press_per_word
        data_dict["kde_mses"] = self.kde_errors_avg
        data_dict["kde"] = self.bc.get_histogram()
        data_dict["kde"] = self.bc.get_histogram()
        if attribute is not None:
            data_dict["attribute"] = attribute
        data_handel.safe_save(data_dict)
예제 #2
0
    def save_simulation_data(self, attribute=None):
        if attribute is not None:
            data_file = os.path.join(
                self.data_loc,
                "sorted_" + str(int(self.key_config == "sorted")) +
                "_nwords_" + str(self.num_word_preds) + "_wf_" +
                str(int(self.words_first)) + "_delay_" +
                str(round(self.start_scan_delay, 2)) + "_scan_" +
                str(self.scanning_delay) + "_atr_" + str(attribute) + "_fp_" +
                str(self.false_positive_rate) + ".p")
        else:
            data_file = os.path.join(
                self.data_loc,
                "sorted_" + str(int(self.key_config == "sorted")) +
                "_nwords_" + str(self.num_word_preds) + "_wf_" +
                str(int(self.words_first)) + "_delay_" +
                str(round(self.start_scan_delay, 2)) + "_cor_" +
                str(self.easy_phrase) + "_scan_" + str(self.scanning_delay) +
                "_fp_" + str(self.false_positive_rate) + ".p")

        data_handel = PickleUtil(data_file)

        data_dict = dict()
        data_dict["order"] = self.key_config
        data_dict["words_first"] = self.words_first
        data_dict["num_words"] = self.num_word_preds
        data_dict["delay"] = self.start_scan_delay
        data_dict["scan_delay"] = self.scanning_delay
        data_dict["easy_corpus"] = self.easy_phrase
        data_dict["false_positive"] = self.false_positive_rate
        data_dict["errors"] = self.error_rate_avg
        data_dict["selections"] = self.sel_per_min
        data_dict["characters"] = self.char_per_min
        data_dict["presses_char"] = self.press_per_char
        data_dict["presses_word"] = self.press_per_word

        if attribute is not None:
            data_dict["attribute"] = attribute
        data_handel.safe_save(data_dict)
예제 #3
0
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.user_num = 0
        self.gen_handle()
        self.press_hold = True
        self.num_presses = 2
        self.presses_remaining = int(self.num_presses)

        self.initUI()

    def initUI(self):
        self.active_color = QColor(70, 200, 255)
        self.inactive_color = QColor(240, 240, 240)
        self.reaction_timer = QTimer()
        self.reaction_timer.singleShot(np.random.randint(2000, 6000),
                                       self.start_reaction)

        self.square = QFrame(self)
        self.square_x, self.square_y = np.random.random_integers(
            200, 800, (2)).tolist()
        self.square.setGeometry(self.square_x, self.square_y, 100, 100)
        self.square.setStyleSheet("QWidget { background-color: %s }" %
                                  self.inactive_color.name())

        self.setWindowTitle('Reaction Time Measuring')
        self.setGeometry(800, 300, 1000, 1000)
        self.show()

    def gen_handle(self):
        handle_name = "data\\" + str(self.user_num)
        if 'data' not in os.listdir():
            os.mkdir("data")
            os.mkdir(handle_name)
        if str(self.user_num) not in os.listdir("data"):
            os.mkdir(handle_name)

        self.data_handel_rxn = PickleUtil(handle_name + "\\rxn_times.p")
        self.data_handel_dpt = PickleUtil(handle_name + "\\dp_times.p")

        self.reaction_times = self.data_handel_rxn.safe_load()
        if self.reaction_times is None:
            self.reaction_times = []
        print("Loaded ", len(self.reaction_times), "rxn clicks")

        self.double_press_times = self.data_handel_dpt.safe_load()
        if self.double_press_times is None:
            self.double_press_times = []
        print("Loaded ", len(self.double_press_times), "dpt clicks")

    def start_reaction(self):
        self.square.setStyleSheet("QFrame { background-color: %s }" %
                                  self.active_color.name())

        self.square_x, self.square_y = np.random.random_integers(
            200, 800, (2)).tolist()
        self.square.setGeometry(self.square_x, self.square_y, 100, 100)
        self.start_time = time()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Space:
            if self.press_hold:
                self.on_press()
                self.press_hold = True

        if e.key() == Qt.Key_Control:
            self.press_hold = True

    def on_press(self):
        # self.reaction_timer.singleShot(1000, self.end_reaction)
        self.presses_remaining -= 1

        if self.presses_remaining == self.num_presses - 1:
            reaction_time = time() - self.start_time
            self.reaction_times.append(reaction_time)

        if self.presses_remaining == 0:
            self.presses_remaining = int(self.num_presses)
            double_press_time = time(
            ) - self.reaction_times[-1] - self.start_time
            self.double_press_times.append(double_press_time)
            self.end_reaction()
            self.reaction_timer.singleShot(np.random.randint(2000, 4000),
                                           self.start_reaction)

    def end_reaction(self):
        self.square.setStyleSheet("QFrame { background-color: %s }" %
                                  self.inactive_color.name())

    def closeEvent(self, event):
        print("CLOSING THRU CLOSEEVENT")
        self.quit(event)
        # self.deleteLater()

    def quit(self, event=None):
        self.data_handel_rxn.safe_save(self.reaction_times)
        print("Saving ", len(self.reaction_times), " rxn clicks")
        self.data_handel_dpt.safe_save(self.double_press_times)
        print("Saving ", len(self.double_press_times), " dpt clicks")
        self.close()
예제 #4
0
    def gen_kernel(self):
        kernel = stats.gaussian_kde(self.rel_click_data)

        kernel_handle = PickleUtil("resources\\kde_kernel.p")
        kernel_handle.safe_save(kernel)
예제 #5
0
class Keyboard(MainWindow):
    def __init__(self, screen_res, app):
        super(Keyboard, self).__init__(screen_res)

        self.app = app

        self.font_scale = 1

        self.key_chars = kconfig.key_chars
        self.key_chars_sorted = kconfig.key_chars_sorted
        self.key_config = "sorted"
        # self.key_config = "default"

        self.num_words = 7
        self.words_first = True
        # self.words_first = False

        self.sound_set = True
        self.pause_set = True

        self.lm_prefix = ""
        self.left_context = ""
        self.typed_versions = [""]

        self.cwd = os.getcwd()
        self.gen_data_handel()

        self.up_handel = PickleUtil(
            os.path.join(self.user_handel, 'user_preferences.p'))
        user_preferences = self.up_handel.safe_load()

        if user_preferences is None:
            user_preferences = [
                'sorted', config.default_rotate_ind, config.default_pause_ind,
                True
            ]
            self.up_handel.safe_save(user_preferences)

        self.key_config, self.speed, self.pause_index, self.is_write_data = user_preferences

        self.scanning_delay = config.period_li[self.speed]
        self.extra_delay = config.pause_li[self.pause_index]

        self.params_handle_dict = {
            'speed': [],
            'extra_delay': [],
            'params': [],
            'start': [],
            'press': [],
            'choice': []
        }
        self.num_presses = 0

        self.last_press_time = time.time()
        self.last_update_time = time.time()
        self.next_frame_time = time.time()

        self.params_handle_dict['params'].append(
            [config.period_li[config.default_rotate_ind], config.theta0])
        self.params_handle_dict['start'].append(time.time())
        self.click_time_list = []

        lm_path = os.path.join(os.path.join(self.cwd, 'resources'),
                               'lm_word_medium.kenlm')
        vocab_path = os.path.join(os.path.join(self.cwd, 'resources'),
                                  'vocab_100k')

        self.lm = LanguageModel(lm_path, vocab_path)

        self.phrase_prompts = False
        if self.phrase_prompts:
            self.phrases = Phrases("resources/comm2.dev")
        else:
            self.phrases = None

        # determine keyboard positions
        # set up file handle for printing useful stuff
        # set up "typed" text
        self.typed = ""
        self.context = ""
        self.old_context_li = [""]
        self.last_add_li = [0]
        # set up "talked" text

        # check for speech
        # talk_fid = open(self.talk_file, 'wb')
        # write words
        self.generate_layout()

        self.draw_words()

        self.wpm_data = []
        self.decay_avg_wpm = 0
        self.wpm_time = 0
        self.error_data = []
        self.decay_avg_error = 1

        self.row_scan = True
        self.row_scan_num = -2
        self.col_scan = False
        self.col_scan_num = -1

        self.init_ui()

        # animate

        self.on_timer()

    def gen_data_handel(self):
        self.cwd = os.getcwd()
        self.data_path = user_data_dir('data', 'RowCol')
        if os.path.exists(self.data_path):
            user_files = list(os.walk(self.data_path))
            users = user_files[0][1]
        else:
            pathlib.Path(self.data_path).mkdir(parents=True, exist_ok=True)
            # os.mkdir(data_path)
            user_files = None
            users = []
        input_method = 'text'
        if user_files is not None and len(users) != 0:
            message = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Information, "Load User Data",
                "You can either create a new user profile or "
                "load an existing user profile.")
            message.addButton(QtWidgets.QPushButton('Create New User'),
                              QtWidgets.QMessageBox.YesRole)
            message.addButton(QtWidgets.QPushButton('Load Previous User'),
                              QtWidgets.QMessageBox.NoRole)
            message.setDefaultButton(QtWidgets.QMessageBox.Yes)
            response = message.exec_()
            if response == 0:
                input_method = 'text'
            else:
                input_method = 'list'

        if input_method == 'text':
            valid_user_id = False
            input_text = "Please input a Number that will be used to save your user information"
            while not valid_user_id:
                num, ok = QtWidgets.QInputDialog.getInt(
                    self, "User ID Number Input", input_text)
                if str(num) not in users:
                    valid_user_id = True
                else:
                    input_text = "The user ID you inputed already exists! \n please input a valid user ID or press " \
                                 "\"cancel\" to choose an existing one from a list"
                if ok == 0:
                    input_method = 'list'
                    break
            if input_method == 'text':
                self.user_id = num
                user_id_path = os.path.join(self.data_path, str(self.user_id))
                os.mkdir(user_id_path)

        if input_method == 'list':
            item, ok = QtWidgets.QInputDialog.getItem(
                self, "Select User ID", "List of save User IDs:", users, 0,
                False)
            self.user_id = item

        self.user_handel = os.path.join(self.data_path, str(self.user_id))
        user_id_files = list(os.walk(self.user_handel))
        user_id_calibrations = user_id_files[0][1]
        if len(user_id_calibrations) == 0:
            self.data_handel = os.path.join(self.user_handel, 'cal0')
            os.mkdir(self.data_handel)
            user_id_cal_files = None
            self.user_cal_num = 0
        else:
            user_id_cal_files = user_id_files[-1][2]
            self.data_handel = user_id_files[-1][0]
            self.user_cal_num = len(user_id_calibrations) - 1
        if user_id_cal_files is not None:
            self.use_num = sum([
                1 if 'params_data' in file_name else 0
                for file_name in user_id_cal_files
            ])
        else:
            self.use_num = 0
        print(self.data_handel)

    def generate_layout(self):
        if self.key_config == "sorted":
            self.keys_list = np.array(self.key_chars_sorted)
            closest_square = int(
                np.ceil(np.sqrt(len(self.keys_list) + self.num_words)))

            self.key_freq_map = np.zeros((closest_square, closest_square))
            for row_num, row in enumerate(self.key_freq_map):
                for col_num, _ in enumerate(row):
                    self.key_freq_map[row_num][col_num] = row_num + col_num
        else:
            self.keys_list = np.array(self.key_chars)
            closest_square = int(
                np.ceil(np.sqrt(len(self.keys_list) + self.num_words)))

            self.key_freq_map = np.zeros((closest_square, closest_square))
            for row_num, row in enumerate(self.key_freq_map):
                for col_num, _ in enumerate(row):
                    self.key_freq_map[row_num][
                        col_num] = row_num * closest_square + col_num

        self.key_layout = np.empty((closest_square, closest_square), dtype=str)

        for word in range(self.num_words):  # fill words first
            if self.words_first:
                word_index = np.unravel_index(word,
                                              (closest_square, closest_square))
            else:
                word_index = np.unravel_index(
                    closest_square**2 - self.num_words + word,
                    (closest_square, closest_square))

            self.key_layout[word_index] = kconfig.word_char
            self.key_freq_map[word_index] = float("inf")

        sorted_indicies = []
        for i in range(len(self.keys_list)):
            arg_min_index = np.unravel_index(self.key_freq_map.argmin(),
                                             self.key_freq_map.shape)
            sorted_indicies += [arg_min_index]
            self.key_freq_map[arg_min_index] = float("inf")

        sorted_indicies.reverse()
        for key in self.keys_list:
            lowest_index = sorted_indicies.pop()
            self.key_layout[lowest_index] = key

        print(self.key_layout)
        empty_row_counts = np.sum(np.where(self.key_layout != '', 1, 0),
                                  axis=1).tolist()
        if 0 in empty_row_counts:
            empty_index = empty_row_counts.index(0)
            self.key_layout = np.delete(self.key_layout, (empty_index), axis=0)

        self.key_rows_num = len(self.key_layout)
        self.key_cols_nums = np.sum(np.where(self.key_layout != '', 1, 0),
                                    axis=1).tolist()

        # shift empty cells

        for row_num, row in enumerate(self.key_layout):
            if "" in row:
                row_list = row.tolist()
                row_list.sort(key=lambda x: 2 if x == "" else 1)
                self.key_layout[row_num] = np.array(row_list)

    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Space:
            self.last_gap_time = time.time() - self.last_update_time
            self.last_press_time = time.time()
            self.save_click_time(self.last_press_time, self.last_gap_time,
                                 (self.row_scan_num, self.col_scan_num))

            self.on_press()

    def change_speed(self, value):
        old_rotate = config.period_li[self.speed]
        self.speed = value
        self.time_rotate = config.period_li[self.speed]
        self.params_handle_dict['speed'].append(
            [time.time(), old_rotate, self.scanning_delay])

    def change_extra_delay(self, value):
        old_pause_length = config.pause_li[self.pause_index]
        self.pause_index = value
        self.extra_delay = config.pause_li[self.pause_index]
        self.params_handle_dict['extra_delay'].append(
            [time.time(), old_pause_length, self.extra_delay])

    def toggle_sound_button(self, value):
        self.sound_set = value
        self.mainWidget.sldLabel.setFocus()

    def toggle_pause_button(self, value):
        self.pause_set = value
        self.mainWidget.sldLabel.setFocus()

    def draw_words(self):
        self.words_li = self.lm.get_words(self.left_context, self.lm_prefix,
                                          self.num_words)

    def on_timer(self):
        if self.focusWidget() == self.mainWidget.text_box:
            self.mainWidget.sldLabel.setFocus(
            )  # focus on not toggle-able widget to allow keypress event

        cur_time = time.time()
        if cur_time >= self.next_frame_time:
            self.update_frame()

    def update_frame(self):
        self.last_update_time = time.time()

        if self.row_scan:
            self.row_scan_num += 1
            if self.row_scan_num >= self.key_rows_num:
                self.row_scan_num = 0

            if self.row_scan_num == 0 and self.pause_set:
                self.next_frame_time += (config.period_li[self.speed] +
                                         self.extra_delay)
            else:
                self.next_frame_time += config.period_li[self.speed]

        elif self.col_scan:
            self.next_frame_time += config.period_li[self.speed]

            self.col_scan_num += 1
            if self.col_scan_num >= self.key_cols_nums[self.row_scan_num]:
                self.col_scan_num = 0

        self.mainWidget.highlight_grid()

    def on_press(self):

        if self.wpm_time == 0:
            self.wpm_time = time.time()

        if self.phrase_prompts:
            self.mainWidget.speed_slider.setEnabled(False)
            self.mainWidget.extra_delay_slider.setEnabled(False)
            self.mainWidget.speed_slider_label.setStyleSheet(
                'QLabel { color: grey }')
            self.mainWidget.sldLabel.setStyleSheet('QLabel { color: grey }')

            self.mainWidget.extra_delay_label.setStyleSheet(
                'QLabel { color: grey }')
            self.mainWidget.extra_sldLabel.setStyleSheet(
                'QLabel { color: grey }')

        if self.sound_set:
            self.play()
        if self.row_scan:
            self.row_scan = False
            self.col_scan = True
            self.next_frame_time = time.time()
            self.on_timer()

        elif self.col_scan:
            self.make_selection()
        self.num_presses += 1

    def save_click_time(self, last_press_time, last_gap_time, index):
        self.params_handle_dict['press'].append([last_press_time])
        self.click_time_list.append((last_gap_time, index))

    def make_selection(self):
        self.winner = self.key_layout[max(0, self.row_scan_num)][max(
            0, self.col_scan_num)]

        self.draw_typed()

        self.draw_words()
        self.mainWidget.update_grid()
        print(self.winner)

        self.col_scan = False
        self.row_scan = True
        self.row_scan_num = -1
        self.col_scan_num = -1

        self.next_frame_time = time.time()
        self.on_timer()

    def draw_typed(self):
        if len(self.typed_versions) > 0:
            previous_text = self.typed_versions[-1]
        else:
            previous_text = ""

        if "_" in previous_text:
            previous_text = previous_text.replace("_", " ")

        if self.winner == kconfig.word_char:
            new_text = self.mainWidget.labels_by_row[self.row_scan_num][
                self.col_scan_num].text()[1:] + ' '
            new_text = new_text[len(self.lm_prefix):]
        else:
            new_text = self.winner

        if self.winner == kconfig.back_char:
            # self.prefix = self.prefix[:-1]
            if self.typed_versions[-1] != '':
                self.typed_versions += [previous_text[:-1]]
                input_text = "<span style='color:#000000;'>" + self.typed_versions[
                    -1] + "</span>"
                self.mainWidget.text_box.setText(
                    "<span style='color:#000000;'>" + self.typed_versions[-1] +
                    "</span>")
            else:
                input_text = ""
        elif self.winner == kconfig.mybad_char:
            if len(self.typed_versions) > 1:
                self.typed_versions = self.typed_versions[:-1]
                input_text = "<span style='color:#000000;'>" + self.typed_versions[
                    -1] + "</span>"
                self.mainWidget.text_box.setText(
                    "<span style='color:#000000;'>" + self.typed_versions[-1] +
                    "</span>")
            else:
                input_text = ""
        elif self.winner == kconfig.clear_char:
            if len(self.typed_versions) > 1:
                self.typed_versions += [" "]
                self.mainWidget.text_box.setText("")
            input_text = ""
        else:
            self.typed_versions += [previous_text + new_text]
            if new_text in kconfig.break_chars:
                new_text = new_text + ' '
                if previous_text[-1] == " ":
                    previous_text = previous_text[:-1]
            if len(new_text) > 0 and new_text[-1] == " ":
                new_text = new_text[:-1] + "_"
                new_text = new_text[:-1] + "_"
            input_text = "<span style='color:#000000;'>" + previous_text + "</span><span style='color:#0000dd;'>" \
                         + new_text + "</span>"
            self.mainWidget.text_box.setText(
                "<span style='color:#000000;'>" + previous_text +
                "</span><span style='color:#0000dd;'>" + new_text + "</span>")
        self.mainWidget.text_box.update()
        self.update_prefixes()

        # write output
        if self.is_write_data:
            choice_dict = {
                "time": time.time(),
                "undo": self.winner == kconfig.mybad_char,
                "backspace": self.winner == kconfig.back_char,
                "typed": self.typed_versions[-1]
            }
            if self.phrase_prompts:
                choice_dict["target"] = self.phrases.cur_phrase

            self.params_handle_dict['choice'].append(choice_dict)

        if self.phrase_prompts:
            self.update_phrases(self.typed_versions[-1], input_text)

    def text_stat_update(self, phrase, typed):

        _, cur_error = calc_MSD(phrase, typed)

        self.error_data = [cur_error] + self.error_data
        decaying_weights = np.power(0.8, np.arange(len(self.error_data)))
        decaying_weights /= np.sum(decaying_weights)

        decay_avg_error = sum(np.array(self.error_data) * decaying_weights)
        error_delta = decay_avg_error / (self.decay_avg_error + 0.000001)
        self.decay_avg_error = decay_avg_error

        self.wpm_data = [(len(typed.split(" ")) - 1) /
                         (time.time() - self.wpm_time) * 60] + self.wpm_data
        self.wpm_time = 0
        decaying_weights = np.power(0.8, np.arange(len(self.wpm_data)))
        decaying_weights /= np.sum(decaying_weights)

        decay_avg_wpm = sum(np.array(self.wpm_data) * decaying_weights)
        wpm_delta = decay_avg_wpm / (self.decay_avg_wpm + 0.000001)
        self.decay_avg_wpm = decay_avg_wpm

        if error_delta > 1:
            error_red = int(min(4, error_delta) * 63)
            error_green = 0
        else:
            error_green = int(min(4, 1 / error_delta) * 63)
            error_red = 0

        if wpm_delta < 1:
            wpm_red = int(min(4, wpm_delta) * 63)
            wpm_green = 0
        else:
            wpm_green = int(min(4, 1 / wpm_delta) * 63)
            wpm_red = 0

        self.mainWidget.error_label.setStyleSheet("color: rgb(" +
                                                  str(error_red) + ", " +
                                                  str(error_green) + ", 0);")

        self.mainWidget.wpm_label.setStyleSheet("color: rgb(" + str(wpm_red) +
                                                ", " + str(wpm_green) +
                                                ", 0);")

        self.mainWidget.error_label.setText("Error Rate: " +
                                            str(round(decay_avg_error, 2)))
        self.mainWidget.wpm_label.setText("Words/Min: " +
                                          str(round(decay_avg_wpm, 2)))

    def reset_context(self):
        self.left_context = ""
        self.context = ""
        self.typed = ""
        self.lm_prefix = ""

    def update_phrases(self, cur_text, input_text):
        cur_phrase_typed, next_phrase = self.phrases.compare(cur_text)
        cur_phrase_highlighted = self.phrases.highlight(cur_text)

        if next_phrase:
            self.text_stat_update(self.phrases.cur_phrase,
                                  self.typed_versions[-1])

            self.typed_versions = ['']
            self.mainWidget.text_box.setText('')
            self.mainWidget.speed_slider.setEnabled(True)
            self.mainWidget.speed_slider_label.setStyleSheet(
                'QLabel { color: blue }')
            self.mainWidget.sldLabel.setStyleSheet('QLabel { color: blue }')

            self.mainWidget.extra_delay_slider.setEnabled(True)
            self.mainWidget.extra_delay_label.setStyleSheet(
                'QLabel { color: blue }')
            self.mainWidget.extra_sldLabel.setStyleSheet(
                'QLabel { color: blue }')

            self.clear_text = False
            undo_text = 'Clear'

            self.phrases.sample()
            input_text = ""
            cur_phrase_highlighted = self.phrases.highlight("")
            self.reset_context()

            if self.is_write_data:
                choice_dict = {
                    "time": time.time(),
                    "undo": False,
                    "backspace": False,
                    "typed": "",
                    "target": self.phrases.cur_phrase
                }
                self.params_handle_dict['choice'].append(choice_dict)

        self.mainWidget.text_box.setText("<p>" + cur_phrase_highlighted +
                                         "<\p><p>" + input_text +
                                         "</span><\p>")

    def update_prefixes(self):
        cur_text = self.typed_versions[-1]

        for bc in kconfig.break_chars:
            if bc in cur_text:
                cur_text = cur_text.split(bc)[-1]

        cur_text_words = cur_text.split(" ")
        print(cur_text_words)
        if cur_text_words[-1] == '' or cur_text_words[
                -1] in kconfig.break_chars:
            self.lm_prefix = ""
            self.left_context = cur_text
        else:
            self.lm_prefix = cur_text_words[-1]
            self.left_context = cur_text[:(-len(self.lm_prefix) - 1)]

    def play(self):
        sound_file = "icons/bell.wav"
        QtMultimedia.QSound.play(sound_file)

    def data_auto_save(self):
        if len(self.click_time_list) > 0:
            print("auto saving data")
            self.save_data()

    def closeEvent(self, event):
        print("CLOSING THRU CLOSEEVENT")
        self.quit(event)
        # self.deleteLater()

    def quit(self, event=None):
        self.save_data()
        self.close()

    def save_data(self):
        user_preferences = [
            self.key_config, self.speed, self.pause_index, self.is_write_data
        ]
        self.up_handel.safe_save(user_preferences)

        self.click_data_path = os.path.join(
            self.data_handel, 'click_time_log_' + str(self.use_num) + '.p')
        self.params_data_path = os.path.join(
            self.data_handel, 'params_data_use_num' + str(self.use_num) + '.p')
        print(self.params_data_path)
        PickleUtil(self.click_data_path).safe_save({
            'user id': self.user_id,
            'use_num': self.use_num,
            'click time list': self.click_time_list,
            'rotate index': self.speed
        })
        PickleUtil(self.params_data_path).safe_save(self.params_handle_dict)
예제 #6
0
class Keyboard(MainWindow):
    def __init__(self, screen_res, app):
        super(Keyboard, self).__init__(screen_res)

        self.app = app
        self.is_simulation = False
        self.pretrain_window = False

        # 2 is turn fully on, 1 is turn on but reduce, 0 is turn off
        self.word_pred_on = 2
        # Number of word clocks to display in case word prediction == 1 (reduced)
        self.reduce_display = 5

        # get user data before initialization
        self.gen_data_handel()

        self.up_handel = PickleUtil(
            os.path.join(self.user_handel, 'user_preferences.p'))
        user_preferences = self.up_handel.safe_load()
        if user_preferences is None:
            first_load = True
            user_preferences = [
                'default', 1, False, 'alpha', 'off', config.default_rotate_ind,
                True
            ]
            self.up_handel.safe_save(user_preferences)
        else:
            first_load = False

        self.clock_type, self.font_scale, self.high_contrast, self.layout_preference, self.pf_preference, \
            self.start_speed, self.is_write_data = user_preferences

        self.phrase_prompts = False  # set to true for data collection mode

        if self.phrase_prompts:
            self.phrases = Phrases(
                "resources/twitter-phrases/watch-combined.txt")

        else:
            self.phrases = None

        if self.layout_preference == 'alpha':
            self.target_layout = kconfig.alpha_target_layout
            self.key_chars = kconfig.key_chars
        elif self.layout_preference == 'qwerty':
            self.target_layout = kconfig.qwerty_target_layout
            self.key_chars = kconfig.key_chars
        elif self.layout_preference == 'emoji':
            self.target_layout = kconfig.emoji_target_layout
            self.key_chars = kconfig.emoji_keys
            self.word_pred_on = 0

        # set up dictionary tree
        # splash = StartWindow(screen_res, True)
        self.pause_animation = False

        self.output_manager = outputManager()
        self.is_output_text = False

        self.lm_prefix = ""
        self.left_context = ""

        self.cwd = os.getcwd()

        word_lm_path = os.path.join(
            os.path.join(self.cwd, 'resources'),
            'mix4_opt_min_lower_100k_4gram_2.5e-9_prob8_bo4_compress255.kenlm')
        char_lm_path = os.path.join(
            os.path.join(self.cwd, 'resources'),
            'mix4_opt_min_lower_12gram_6e-9_prob9_bo4_compress255.kenlm')
        vocab_path = os.path.join(os.path.join(self.cwd, 'resources'),
                                  'vocab_lower_100k.txt')
        char_path = os.path.join(os.path.join(self.cwd, 'resources'),
                                 'char_set.txt')

        self.lm = LanguageModel(word_lm_path, char_lm_path, vocab_path,
                                char_path)

        # initialize pygame and joystick
        if kconfig.target_evt is kconfig.joy_evt:
            pygame.init()
            if pygame.joystick.get_count() < 1:
                # no joysticks found
                print("Please connect a joystick.\n")
                self.quit(None)
            else:
                # create a new joystick object from
                # ---the first joystick in the list of joysticks
                Joy0 = pygame.joystick.Joystick(0)
                # tell pygame to record joystick events
                Joy0.init()
            # start looking for events
            self.parent.after(0, self.find_events)
        # not in selection pause
        self.in_pause = False

        # determine keyboard positions
        self.init_locs()
        # get old data if there is such
        # Just for default. Loaded again when bc initializes
        self.rotate_index = config.default_rotate_ind
        # set up file handle for printing useful stuff
        self.undefined = False

        self.params_handle_dict = {
            'speed': [],
            'params': [],
            'start': [],
            'press': [],
            'choice': []
        }
        self.num_presses = 0

        self.params_handle_dict['params'].append(
            [config.period_li[config.default_rotate_ind], config.theta0])
        self.params_handle_dict['start'].append(time.time())

        self.gen_scale()
        self.pause_set = True
        # set up "typed" text
        self.typed = ""
        self.btyped = ""
        self.context = ""
        self.old_context_li = [""]
        self.last_add_li = [0]
        # set up "talked" text
        # self.talk_file = "talk.txt"
        self.sound_set = True
        self.press_lock = False
        self.press_lock_status = False

        # check for speech
        # talk_fid = open(self.talk_file, 'wb')
        # write words
        self.init_words()

        self.bars = kconfig.bars

        self.bc_init = False

        self.previous_undo_text = ''
        self.previous_winner = 0

        self.wpm_data = []
        self.decay_avg_wpm = 0
        self.wpm_time = 0
        self.error_data = []
        self.decay_avg_error = 1

        self.clear_text = False
        self.pretrain = False

        self.emoji_box_highlight = [-1, -1]
        self.init_ui()

        self.mainWidget.clockgrid_widget.update_word_clocks(self.words_li)

        sound_file = "icons/bell.wav"
        self.sound_player = QtMultimedia.QSound(sound_file)

        self.time_rotate = config.period_li[self.start_speed]
        # get language model results
        self.gen_word_prior(False)

        self.clock_spaces = np.zeros((len(self.clock_centers), 2))

        self.bc = BroderClocks(self)
        self.mainWidget.change_value(self.start_speed)

        self.bc.init_follow_up(self.word_score_prior)

        # draw histogram
        self.init_histogram()

        self.save_environment()

        self.consent = False

        if first_load:
            self.pretrain = True
            self.welcome = Pretraining(self, screen_res)

        # animate

        # record to prevent double tap
        self.last_key_press_time = time.time()
        self.last_release_time = time.time()

        self.update_radii = False
        self.on_timer()

    def gen_data_handel(self):
        self.cwd = os.getcwd()
        self.data_path = user_data_dir('data', 'Nomon')
        if os.path.exists(self.data_path):
            user_files = list(os.walk(self.data_path))
            users = user_files[0][1]
        else:
            pathlib.Path(self.data_path).mkdir(parents=True, exist_ok=True)
            user_files = None
            users = []
        input_method = 'text'
        if user_files is not None and len(users) != 0:
            message = QtWidgets.QMessageBox()
            message.setText("You can either create a new user profile or "
                            "load an existing user profile.")
            message.setWindowTitle("Load User")
            message.addButton('Create New User', QtWidgets.QMessageBox.YesRole)
            message.addButton('Load Previous User',
                              QtWidgets.QMessageBox.NoRole)
            message.setDefaultButton(QtWidgets.QMessageBox.Yes)
            response = message.exec_()
            if response == 0:
                input_method = 'text'
            else:
                input_method = 'list'

        if input_method == 'text':
            valid_user_id = False
            input_text = "Please input a Number that will be used to save your user information"
            while not valid_user_id:
                num, ok = QtWidgets.QInputDialog.getInt(
                    self, "User ID Number Input", input_text)
                if str(num) not in users:
                    valid_user_id = True
                else:
                    input_text = "The user ID you inputed already exists! \n please input a valid user ID or press " \
                                 "\"cancel\" to choose an existing one from a list"
                if ok == 0:
                    input_method = 'list'
                    break
            if input_method == 'text':
                self.user_id = num
                user_id_path = os.path.join(self.data_path, str(self.user_id))
                os.mkdir(user_id_path)

        if input_method == 'list':
            item, ok = QtWidgets.QInputDialog.getItem(
                self, "Select User ID", "List of save User IDs:", users, 0,
                False)
            self.user_id = item

        self.user_handel = os.path.join(self.data_path, str(self.user_id))
        user_id_files = list(os.walk(self.user_handel))
        user_id_calibrations = user_id_files[0][1]
        if len(user_id_calibrations) == 0:
            self.data_handel = os.path.join(self.user_handel, 'cal0')
            os.mkdir(self.data_handel)
            user_id_cal_files = None
            self.user_cal_num = 0
        else:
            user_id_cal_files = user_id_files[-1][2]
            self.data_handel = user_id_files[-1][0]
            self.user_cal_num = len(user_id_calibrations) - 1
        if user_id_cal_files is not None:
            self.use_num = sum([
                1 if 'params_data' in file_name else 0
                for file_name in user_id_cal_files
            ])
        else:
            self.use_num = 0
        print(self.data_handel)

    def save_environment(self):
        self.rotate_index_past = self.rotate_index
        self.window_size_past = self.frameGeometry()
        self.clock_type_past = self.clock_type
        self.layout_preference_past = self.layout_preference
        self.high_contrast_past = self.high_contrast

    def data_auto_save(self):
        if len(self.bc.click_time_list) > 0:
            print("auto saving data")
            self.bc.save_when_quit()

    def find_events(self):
        # check everything in the queue of pygame events
        events = pygame.event.get()
        for event in events:
            # event type for pressing any of the joystick buttons down
            if event.type == pygame.JOYBUTTONDOWN:
                # generate the event I've defined
                self.canvas.event_generate(kconfig.joy_evt)

        # return to check for more events in a moment
        self.parent.after(20, self.find_events)

    def keyPressEvent(self, e):

        # if e.key() == QtCore.Qt.Key_Space:
        #     if self.press_lock:
        #         if not self.press_lock_status:
        #             self.on_press()
        #             self.press_lock_status = True
        #     else:
        #         self.on_press()

        if e.key() == QtCore.Qt.Key_Control:
            if self.press_lock:
                if self.press_lock_status:
                    self.press_lock_status = False

        if e.key() == QtCore.Qt.Key_Return:
            if self.phrase_prompts:
                if len(self.typed_versions) > 0:
                    self.update_phrases(self.typed_versions[-1],
                                        "",
                                        next_phrase=True)
                else:
                    self.update_phrases("", "", next_phrase=True)

    def init_locs(self):
        # size of keyboard
        key_chars = kconfig.key_chars

        self.N_rows = len(key_chars)
        self.N_keys_row = []
        self.N_keys = 0
        self.N_alpha_keys = 0

        for row in range(self.N_rows):
            n_keys = len(key_chars[row])

            for col in range(n_keys):
                if not isinstance(key_chars[row], list):
                    if (key_chars[row][col] in kconfig.main_chars
                            and (len(key_chars[row]) == 1)):
                        self.N_alpha_keys = self.N_alpha_keys + 1

                    elif ((key_chars[row] == kconfig.space_char)
                          and (len(key_chars[row]) == 1)):
                        self.N_alpha_keys = self.N_alpha_keys + 1

                    elif (key_chars[row] == kconfig.break_chars[1]
                          and (len(key_chars[row]) == 1)):
                        self.N_alpha_keys = self.N_alpha_keys + 1

            self.N_keys_row.append(n_keys)
            self.N_keys += n_keys

        # print "NKEYS is " + str(self.N_keys)
        # print "And N_alpha_keys is " + str(self.N_alpha_keys)

        # width difference when include letter
        word_clock_offset = 7 * kconfig.clock_rad
        rect_offset = word_clock_offset - kconfig.clock_rad
        word_offset = 8.5 * kconfig.clock_rad
        rect_end = rect_offset + kconfig.word_w

        # clock, key, word locations
        self.clock_centers = []
        self.win_diffs = []
        self.word_locs = []
        self.char_locs = []
        self.rect_locs = []
        self.keys_li = []
        self.keys_ref = []
        index = 0  # how far into the clock_centers matrix
        word = 0  # word index
        key = 0  # key index
        # kconfig.N_pred = 2 # number of words per key
        self.key_height = 6.5 * kconfig.clock_rad
        self.w_canvas = 0
        self.index_to_wk = []  # overall index to word or key index
        for row in range(0, self.N_rows):
            y = row * self.key_height
            self.w_canvas = max(
                self.w_canvas, self.N_keys_row[row] *
                (6 * kconfig.clock_rad + kconfig.word_w))
            for col in range(0, self.N_keys_row[row]):
                x = col * (6 * kconfig.clock_rad + kconfig.word_w)
                # predictive words
                self.clock_centers.append(
                    [x + word_clock_offset, y + 1 * kconfig.clock_rad])
                self.clock_centers.append(
                    [x + word_clock_offset, y + 3 * kconfig.clock_rad])
                self.clock_centers.append(
                    [x + word_clock_offset, y + 5 * kconfig.clock_rad])
                # win diffs
                self.win_diffs.extend([
                    config.win_diff_base, config.win_diff_base,
                    config.win_diff_base
                ])
                # word position
                self.word_locs.append(
                    [x + word_offset, y + 1 * kconfig.clock_rad])
                self.word_locs.append(
                    [x + word_offset, y + 3 * kconfig.clock_rad])
                self.word_locs.append(
                    [x + word_offset, y + 5 * kconfig.clock_rad])
                # rectangles
                self.rect_locs.append([
                    x + rect_offset, y, x + rect_end, y + 2 * kconfig.clock_rad
                ])
                self.rect_locs.append([
                    x + rect_offset, y + 2 * kconfig.clock_rad, x + rect_end,
                    y + 4 * kconfig.clock_rad
                ])
                self.rect_locs.append([
                    x + rect_offset, y + 4 * kconfig.clock_rad, x + rect_end,
                    y + 6 * kconfig.clock_rad
                ])
                # indices
                self.index_to_wk.append(word)
                self.index_to_wk.append(word + 1)
                self.index_to_wk.append(word + 2)
                index += 3
                word += 3

                ## key character
                # reference to index of key character
                key_char = key_chars[row][col]
                self.keys_li.append(key_chars[row][col])
                self.keys_ref.append(index)
                self.index_to_wk.append(key)
                # key character position
                self.char_locs.append(
                    [x + 2 * kconfig.clock_rad, y + 3 * kconfig.clock_rad])
                # clock position for key character
                self.clock_centers.append(
                    [x + 1 * kconfig.clock_rad, y + 3 * kconfig.clock_rad])
                # rectangles
                self.rect_locs.append(
                    [x, y, x + rect_offset, y + 6 * kconfig.clock_rad])
                # win diffs
                if (key_char == kconfig.mybad_char) or (
                        key_char == kconfig.yourbad_char) or (
                            key_char == kconfig.back_char
                        ):  # or (key_char == kconfig.break_char)
                    self.win_diffs.append(config.win_diff_high)
                else:
                    self.win_diffs.append(config.win_diff_base)
                index += 1
                key += 1

    def gen_scale(self):
        scale_length = self.w_canvas / 2  # (len(kconfig.key_chars[0])-1)*kconfig.word_w
        tick_int = int((len(config.period_li) - 1) * kconfig.word_pt * 3 /
                       (1.0 * scale_length)) + 1
        self.time_rotate = config.period_li[self.rotate_index]

    def toggle_pause_button(self, value):
        self.pause_set = value
        self.mainWidget.sldLabel.setFocus()

    def toggle_sound_button(self, value):
        self.sound_set = value
        self.mainWidget.sldLabel.setFocus()

    def toggle_talk_button(self, value):
        self.talk_set = value
        self.mainWidget.sldLabel.setFocus(
        )  # focus on not toggle-able widget to allow keypress event

    def toggle_learn_button(self, value):
        config.is_learning = value
        self.mainWidget.sldLabel.setFocus(
        )  # focus on not toggle-able widget to allow keypress event

    def change_speed(self, index):
        # speed (as shown on scale)
        speed_index = int(index)
        # period (as stored in config.py)
        self.rotate_index = speed_index
        old_rotate = self.time_rotate
        self.time_rotate = config.period_li[self.rotate_index]
        self.bc.clock_inf.clock_util.change_period(self.time_rotate)

        # note period change in log file
        self.params_handle_dict['speed'].append(
            [time.time(), old_rotate, self.time_rotate])

        # update the histogram
        self.draw_histogram()

    def init_histogram(self):
        # histogram
        bars = self.bc.get_histogram()
        self.bars = bars

        # undo_text
        self.undo_loc = [
            (self.N_keys_row[self.N_rows - 1] - 1) *
            (6 * kconfig.clock_rad + kconfig.word_w) - self.w_canvas / 2,
            2 * kconfig.clock_rad
        ]

    def draw_histogram(self, bars=None):
        if bars == None:
            bars = self.bc.get_histogram()
        # bars = [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        self.bars = bars
        self.mainWidget.histogram.update()

    def init_words(self):
        if self.word_pred_on == 1:
            num_words_total = 5
        elif self.word_pred_on == 0:
            num_words_total = 0
        else:
            num_words_total = kconfig.num_words_total
        (self.words_li, self.word_freq_li,
         self.key_freq_li) = self.lm.get_words(self.left_context,
                                               self.context,
                                               self.keys_li,
                                               num_words_total=num_words_total)
        if self.layout_preference == "emoji":
            self.key_freq_li = np.array([
                np.log(1 / len(self.key_chars))
                for i in range(len(self.key_chars))
            ])

        self.word_id = []
        self.word_pair = []
        word = 0
        index = 0
        windex = 0
        self.words_on = []
        self.words_off = []
        self.word_list = []
        # self.flag_args = []

        temp_word_list = [
            word_item for sublist in self.words_li for word_item in sublist
        ]
        for word_item in temp_word_list:
            if word_item != '':
                self.word_list.append(word_item)

        # print "TURNED ON AND WORD LIST IS" + str(self.word_list)

        len_con = len(self.context)
        for key in range(0, self.N_alpha_keys):
            for pred in range(0, kconfig.N_pred):
                word_str = self.words_li[key][pred]
                len_word = len(word_str)
                if (len_con > 1) and (len_word > kconfig.max_chars_display):
                    word_str = "+" + word_str[len_con:len_word]
                self.word_pair.append((key, pred))
                if word_str == '':
                    self.words_off.append(index)
                else:

                    self.words_on.append(index)

                windex += 1
                word += 1
                index += 1
            self.words_on.append(index)
            self.word_pair.append((key, ))
            index += 1
        for key in range(self.N_alpha_keys, self.N_keys):
            for pred in range(0, kconfig.N_pred):
                word_str = self.words_li[key][pred]
                self.word_pair.append((key, pred))
                self.words_off.append(index)
                index += 1
            self.words_on.append(index)
            self.word_pair.append((key, ))
            index += 1
        self.typed_versions = ['']

    def draw_words(self):
        if self.word_pred_on == 1:
            num_words_total = 5
        elif self.word_pred_on == 0:
            num_words_total = 0
        else:
            num_words_total = kconfig.num_words_total
        (self.words_li, self.word_freq_li,
         self.key_freq_li) = self.lm.get_words(self.left_context,
                                               self.context,
                                               self.keys_li,
                                               num_words_total=num_words_total)

        self.mainWidget.clockgrid_widget.update_word_clocks(self.words_li)

        if self.layout_preference == "emoji":
            self.key_freq_li = np.array([
                np.log(1 / len(self.key_chars))
                for i in range(len(self.key_chars))
            ])

        word = 0
        index = 0
        self.words_on = []
        self.words_off = []
        self.word_list = []

        temp_word_list = [
            word_item for sublist in self.words_li for word_item in sublist
        ]
        for word_item in temp_word_list:
            if word_item != '' and all(c in "abcdefghijklmnopqrstuvwxyz\' "
                                       for c in word_item):
                self.word_list.append(word_item)

        len_con = len(self.context)

        windex = 0
        for key in range(0, self.N_alpha_keys):
            for pred in range(0, kconfig.N_pred):
                word_str = self.words_li[key][pred]
                len_word = len(word_str)
                if len_con > 1 and len_word > kconfig.max_chars_display:
                    word_str = "+" + word_str[len_con:len_word]
                if word_str == '':
                    self.words_off.append(index)
                else:

                    self.words_on.append(index)

                windex += 1
                word += 1
                index += 1
            self.words_on.append(index)

            self.word_pair.append((key, ))
            index += 1
        for key in range(self.N_alpha_keys, self.N_keys):
            for pred in range(0, kconfig.N_pred):
                self.word_pair.append((key, pred))
                self.words_off.append(index)
                index += 1
            self.words_on.append(index)
            self.word_pair.append((key, ))
            index += 1

    def gen_word_prior(self, is_undo):
        self.word_score_prior = []
        N_on = len(self.words_on)
        if not is_undo:
            for index in self.words_on:
                pair = self.word_pair[index]
                # word case
                if len(pair) == 2:
                    (key, pred) = pair
                    prob = self.word_freq_li[key][pred] + np.log(
                        kconfig.rem_prob)
                    self.word_score_prior.append(prob)
                else:
                    key = pair[0]
                    prob = self.key_freq_li[key]
                    prob = prob + np.log(kconfig.rem_prob)
                    if self.keys_li[key] == kconfig.mybad_char or self.keys_li[
                            key] == kconfig.yourbad_char:
                        prob = np.log(kconfig.undo_prob)
                    # if self.keys_li[key] in kconfig.break_chars:
                    #     prob = np.log(kconfig.break_prob)
                    if self.keys_li[key] == kconfig.back_char:
                        prob = np.log(kconfig.back_prob)
                    if self.keys_li[key] == kconfig.clear_char:
                        prob = np.log(kconfig.undo_prob)
                    if self.keys_li[key] == kconfig.speak_char:
                        prob = np.log(kconfig.undo_prob)

                    self.word_score_prior.append(prob)
        else:
            for index in self.words_on:
                pair = self.word_pair[index]
                if len(pair) == 1:
                    key = pair[0]
                    if (self.keys_li[key] == kconfig.mybad_char) or (
                            self.keys_li[key] == kconfig.yourbad_char):
                        prob = kconfig.undo_prob
                        self.word_score_prior.append(np.log(prob))
                    else:
                        self.word_score_prior.append(-float("inf"))
                else:
                    self.word_score_prior.append(-float("inf"))

    def reset_context(self):
        self.left_context = ""
        self.context = ""
        self.typed = ""
        self.lm_prefix = ""
        self.draw_words()

    def update_phrases(self, cur_text, input_text, next_phrase=False):
        cur_phrase_typed, _ = self.phrases.compare(cur_text)
        cur_phrase_highlighted = self.phrases.highlight(cur_text)

        if next_phrase:
            if len(cur_text + input_text) > 0:
                self.text_stat_update(self.phrases.cur_phrase,
                                      self.typed_versions[-1])

            self.typed_versions = ['']
            self.mainWidget.text_box.setText('')
            self.mainWidget.speed_slider.setEnabled(True)
            self.mainWidget.speed_slider_label.setStyleSheet(
                'QLabel { color: blue }')
            self.mainWidget.sldLabel.setStyleSheet('QLabel { color: blue }')

            self.clear_text = False
            undo_text = 'Clear'

            self.phrases.sample()
            input_text = ""
            cur_phrase_highlighted = self.phrases.highlight("")
            self.reset_context()

            if self.is_write_data:
                choice_dict = {
                    "time": time.time(),
                    "undo": False,
                    "backspace": False,
                    "typed": "",
                    "target": self.phrases.cur_phrase
                }
                self.params_handle_dict['choice'].append(choice_dict)

        self.mainWidget.text_box.setText("<p>" + cur_phrase_highlighted +
                                         "<\p><p>" + input_text +
                                         "</span><\p>")

        if self.layout_preference == 'emoji':
            if len(self.phrases.cur_phrase) > len(cur_text):
                target_emoji = self.phrases.cur_phrase[len(cur_text)]
                self.emoji_box_highlight = np.where(
                    np.array(self.target_layout[:-1]) == target_emoji)
            else:
                self.emoji_box_highlight = [-1, -1]
            self.mainWidget.update()
            # next_emoji_clock = self.mainWidget.clockgrid_widget.clocks[4*key_index+3]

    def draw_typed(self):
        new_text = ''
        redraw_words = False
        # phrases

        delete = False
        undo = False
        undo_text = ""
        if self.phrase_prompts:
            previous_text = self.mainWidget.text_box.toPlainText().split(
                "\n")[-1]
        else:
            previous_text = self.mainWidget.text_box.toPlainText()

        if len(previous_text) > 0 and previous_text[-1] == "_":
            previous_text = previous_text[:-1] + " "

        if len(self.last_add_li) > 1:
            last_add = self.last_add_li[-1]
            if last_add > 0:  # typed something
                new_text = self.typed[-last_add:len(self.typed)]

                undo_text = new_text
            elif last_add == -1:  # backspace
                new_text = ''
                delete = True
                undo_text = kconfig.back_char
        else:
            new_text = ''
            undo_text = new_text

        if new_text in [". ", ", ", "? ", "! "]:
            previous_text = previous_text[:-1]
            redraw_words = True

        index = self.previous_winner
        if self.mainWidget.clockgrid_widget.clocks[index] != '':
            if self.mainWidget.clockgrid_widget.clocks[index].text in [
                    kconfig.mybad_char, 'Undo'
            ]:
                undo = True
                delete = False
        if self.typed_versions[-1] == '' and len(self.typed_versions) > 1:
            undo_text = 'Clear'

        if self.clear_text:
            if self.is_output_text:
                self.output_manager.remove_text(len(self.typed_versions[-1]))
            self.typed_versions += ['']
            input_text = ""
            self.lm_prefix = ""
            self.mainWidget.text_box.setText('')
            self.clear_text = False
            undo_text = 'Clear'

        elif delete:
            # self.prefix = self.prefix[:-1]
            if self.typed_versions[-1] != '':
                self.typed_versions += [previous_text[:-1]]
                new_text = self.typed_versions[-1]
                if len(new_text) > 0 and new_text[-1] == " ":
                    new_text = new_text[:-1] + "_"

                input_text = "<span style='color:#000000;'>" + new_text + "</span>"
                self.mainWidget.text_box.setText(
                    "<span style='color:#000000;'>" + new_text + "</span>")

                if self.is_output_text:
                    self.output_manager.type_text(kconfig.back_char)
            else:
                input_text = ""
        elif undo:
            if len(self.typed_versions) > 1:
                if self.is_output_text:
                    cur_length = len(self.typed_versions[-1])
                    prev_length = len(self.typed_versions[-2])
                    if prev_length > cur_length:
                        text_diff_length = prev_length - cur_length
                        text_diff = self.typed_versions[-2][-text_diff_length:]
                        self.output_manager.type_text(text_diff)
                    else:
                        if cur_length >= 2 and self.typed_versions[-1][
                                -2:] in ["? ", "! ", ", ", ". "]:
                            self.output_manager.type_text(kconfig.back_char +
                                                          kconfig.back_char +
                                                          " ")
                        else:
                            self.output_manager.remove_text(cur_length -
                                                            prev_length)

                self.typed_versions = self.typed_versions[:-1]

                new_text = self.typed_versions[-1]

                if len(new_text) > 0 and new_text[-1] == " ":
                    new_text = new_text[:-1] + "_"

                input_text = "<span style='color:#000000;'>" + new_text + "</span>"
                self.mainWidget.text_box.setText(
                    "<span style='color:#000000;'>" + new_text + "</span>")
            else:
                input_text = ""
        else:
            self.typed_versions += [previous_text + new_text]

            if self.is_output_text:
                text_to_output = new_text
                for break_char in kconfig.break_chars:
                    text_to_output = text_to_output.replace(
                        break_char + " ", kconfig.back_char + break_char + " ")
                self.output_manager.type_text(text_to_output)

            if len(new_text) > 0 and new_text[-1] == " ":
                new_text = new_text[:-1] + "_"

            input_text = "<span style='color:#000000;'>" + previous_text + "</span><span style='color:#0000dd;'>"\
                         + new_text + "</span>"

            self.mainWidget.text_box.setText(
                "<span style='color:#000000;'>" + previous_text +
                "</span><span style='color:#0000dd;'>" + new_text + "</span>")

        if undo_text == kconfig.mybad_char:
            undo_text = "Undo"
        elif undo_text == kconfig.back_char:
            undo_text = "Backspace"
        elif undo_text == kconfig.clear_char:
            undo_text = "Clear"

        self.previous_undo_text = undo_text
        self.mainWidget.undo_label.setText("<font color='green'>" + undo_text +
                                           "</font>")

        if self.phrase_prompts:
            self.update_phrases(self.typed_versions[-1], input_text)

    def text_stat_update(self, phrase, typed):

        _, cur_error = calc_MSD(phrase, typed)

        self.error_data = [cur_error] + self.error_data
        decaying_weights = np.power(0.8, np.arange(len(self.error_data)))
        decaying_weights /= np.sum(decaying_weights)

        decay_avg_error = sum(np.array(self.error_data) * decaying_weights)
        error_delta = decay_avg_error / (self.decay_avg_error + 0.000001)
        self.decay_avg_error = decay_avg_error

        self.wpm_data = [(len(typed.split(" ")) - 1) /
                         (time.time() - self.wpm_time) * 60] + self.wpm_data
        self.wpm_time = 0
        decaying_weights = np.power(0.8, np.arange(len(self.wpm_data)))
        decaying_weights /= np.sum(decaying_weights)

        decay_avg_wpm = sum(np.array(self.wpm_data) * decaying_weights)
        wpm_delta = decay_avg_wpm / (self.decay_avg_wpm + 0.000001)
        self.decay_avg_wpm = decay_avg_wpm

        if error_delta > 1:
            error_red = int(min(4, error_delta) * 63)
            error_green = 0
        else:
            error_green = int(min(4, 1 / error_delta) * 63)
            error_red = 0

        if wpm_delta < 1:
            wpm_red = int(min(4, wpm_delta) * 63)
            wpm_green = 0
        else:
            wpm_green = int(min(4, 1 / wpm_delta) * 63)
            wpm_red = 0

        self.mainWidget.error_label.setStyleSheet("color: rgb(" +
                                                  str(error_red) + ", " +
                                                  str(error_green) + ", 0);")

        self.mainWidget.wpm_label.setStyleSheet("color: rgb(" + str(wpm_red) +
                                                ", " + str(wpm_green) +
                                                ", 0);")

        self.mainWidget.error_label.setText("Error Rate: " +
                                            str(round(decay_avg_error, 2)))
        self.mainWidget.wpm_label.setText("Words/Min: " +
                                          str(round(decay_avg_wpm, 2)))

    def on_pause(self):
        self.mainWidget.pause_timer.start(kconfig.pause_length)
        self.in_pause = True
        self.mainWidget.keygrid_widget.highlight = True
        self.mainWidget.keygrid_widget.update()

    def end_pause(self):
        self.mainWidget.pause_timer.stop()
        self.in_pause = False
        self.mainWidget.keygrid_widget.highlight = False
        self.mainWidget.keygrid_widget.update()
        self.on_timer()

    def on_timer(self):
        if self.focusWidget() == self.mainWidget.text_box:
            self.mainWidget.sldLabel.setFocus(
            )  # focus on not toggle-able widget to allow keypress event

        if self.bc_init:
            self.bc.clock_inf.clock_util.increment(self.words_on)

        is_input = win32api.GetAsyncKeyState(win32con.VK_SPACE)
        if is_input:
            if not self.output_manager.ignore_keyevent:
                win32api.keybd_event(0x20, 0, win32con.KEYEVENTF_KEYUP, 0)
                if self.is_output_text:
                    win32api.keybd_event(0x08, 0, 0, 0)
                    win32api.keybd_event(0x08, 0, win32con.KEYEVENTF_KEYUP, 0)
                if not self.pretrain:
                    self.on_press()
            else:
                self.output_manager.ignore_keyevent = False

    def on_press(self):

        if time.time() - self.last_key_press_time > 1:
            self.last_key_press_time = time.time()
            # self.canvas.focus_set()
            if self.wpm_time == 0:
                self.wpm_time = time.time()

            if self.sound_set:
                self.play()

            if self.phrase_prompts:
                self.mainWidget.speed_slider.setEnabled(False)
                self.mainWidget.speed_slider_label.setStyleSheet(
                    'QLabel { color: grey }')
                self.mainWidget.sldLabel.setStyleSheet(
                    'QLabel { color: grey }')

            if self.is_write_data:
                self.num_presses += 1

            self.bc.select()

    def play(self):

        self.sound_player.play()

    def highlight_winner(self, index):
        if self.mainWidget.clockgrid_widget.clocks[index] != '':
            self.mainWidget.clockgrid_widget.clocks[index].selected = True
            self.mainWidget.highlight_timer.start(kconfig.pause_length)

    def end_highlight(self):
        index = self.previous_winner

        if self.mainWidget.clockgrid_widget.clocks[index] != '':
            self.mainWidget.clockgrid_widget.clocks[index].selected = False
            self.mainWidget.highlight_timer.stop()

    def talk_winner(self, talk_string):
        pass

    def make_choice(self, index):
        is_undo = False
        is_equalize = False
        is_backspace = False

        # now pause (if desired)
        if self.pause_set == 1:
            self.on_pause()
            self.mainWidget.pause_timer.start(kconfig.pause_length)

        # highlight winner
        self.previous_winner = index
        self.highlight_winner(index)

        # initialize talk string
        talk_string = ""

        # if selected a key
        if (index - kconfig.N_pred) % (kconfig.N_pred + 1) == 0:
            new_char = self.keys_li[self.index_to_wk[index]]

            # special characters
            if new_char == kconfig.speak_char:
                new_char = ""

                if len(self.typed_versions) > 0:
                    text_to_speak = self.typed_versions[-1]
                    text_to_speak = text_to_speak.replace("_", " ")
                else:
                    text_to_speak = ""

                self.output_manager.speak_text(text_to_speak)
                self.old_context_li.append(self.context)
                self.context = ""
                self.last_add_li.append(0)
            elif new_char == kconfig.space_char:
                if len(self.context) > 1:
                    talk_string = self.context
                else:
                    talk_string = "space"

                new_char = ' '
                self.old_context_li.append(self.context)
                self.context = ""
                self.last_add_li.append(1)
            elif new_char == kconfig.mybad_char or new_char == kconfig.yourbad_char:
                talk_string = new_char
                # if added characters that turn
                if len(self.last_add_li) > 1:
                    last_add = self.last_add_li.pop()
                    self.context = self.old_context_li.pop()
                    if last_add > 0:  # if added text that turn
                        self.typed = self.typed[0:-last_add]
                    elif last_add == -1:  # if backspaced that turn
                        letter = self.btyped[-1]
                        self.btyped = self.btyped[0:-1]
                        self.typed += letter
                if new_char == kconfig.yourbad_char:
                    is_equalize = True
                new_char = ''
                is_undo = True
            elif new_char == kconfig.back_char:
                talk_string = new_char
                is_backspace = True
                # if delete the last character that turn
                self.old_context_li.append(self.context)
                print(self.context)
                lt = len(self.typed)
                if lt > 0:  # typed anything yet?
                    self.btyped += self.typed[-1]
                    self.last_add_li.append(-1)
                    self.typed = self.typed[0:-1]
                    lt -= 1
                    if lt == 0:
                        self.context = ""
                    elif len(self.context) > 0:
                        self.context = self.context[0:-1]
                    elif not (self.typed[-1]).isalpha():
                        self.context = ""
                    else:
                        i = -1
                        while (i >= -lt) and (self.typed[i].isalpha()):
                            i -= 1
                        self.context = self.typed[i + 1:lt]
                new_char = ''
            elif new_char == kconfig.clear_char:
                talk_string = 'clear'

                new_char = '_'
                self.old_context_li.append(self.context)
                self.context = ""
                self.last_add_li.append(1)

                self.reset_context()

                self.clear_text = True

            elif new_char.isalpha(
            ) or new_char == "'" or new_char in kconfig.emoji_keys:
                talk_string = new_char
                self.old_context_li.append(self.context)
                self.context += new_char
                self.last_add_li.append(1)

            if new_char in [".", ",", "?", "!"]:
                talk_string = "Full stop"
                self.old_context_li.append(self.context)
                self.context = ""
                self.typed += new_char
                if " " + new_char in self.typed:
                    self.last_add_li.append(2)
                self.typed = self.typed.replace(" " + new_char, new_char + " ")
            else:
                self.typed += new_char

        # if selected a word
        else:
            key = self.index_to_wk[index] // kconfig.N_pred
            pred = self.index_to_wk[index] % kconfig.N_pred
            new_word = self.words_li[key][pred]
            new_selection = new_word
            length = len(self.context)
            talk_string = new_word.rstrip(kconfig.space_char)  # talk string
            if length > 0:
                self.typed = self.typed[0:-length]
            self.typed += new_word
            self.last_add_li.append(len(new_word) - len(self.context))
            self.old_context_li.append(self.context)
            self.context = ""

        # update the screen
        if self.context != "":
            self.left_context = self.typed[:-len(self.context)]
        else:
            self.left_context = self.typed

        # write output
        if self.is_write_data:
            choice_dict = {
                "time": time.time(),
                "undo": is_undo,
                "backspace": is_backspace,
                "typed": self.typed
            }
            if self.phrase_prompts:
                choice_dict["target"] = self.phrases.cur_phrase

            self.params_handle_dict['choice'].append(choice_dict)

        self.draw_words()
        self.draw_typed()
        # update the word prior
        self.gen_word_prior(is_undo)

        # # talk the string
        # if self.talk_set.get() == 1:
        #     self.talk_winner(talk_string)
        return self.words_on, self.words_off, self.word_score_prior, is_undo, is_equalize

    def present_choice(self):
        self.draw_histogram()
        # self.canvas.update_idletasks()

    def closeEvent(self, event):
        print("CLOSING THRU CLOSEEVENT")
        self.quit(event)
        # self.deleteLater()

    def quit(self, event=None):
        self.bc.quit_bc()
        self.close()

    def launch_help(self):
        help_window = Pretraining(self,
                                  self.mainWidget.screen_res,
                                  help_screen=True)
        help_window.show()

        self.pretrain = True
        self.mainWidget.repaint()

    def launch_retrain(self):
        retrain_window = Pretraining(self, self.mainWidget.screen_res)
        retrain_window.screen_num = 3
        retrain_window.next_screen()
        retrain_window.show()
        # retrain_window.setCentralWidget(retrain_window.mainWidget)

        retrain_window.retrain = True
        self.pretrain = True
        self.mainWidget.update()