def manual_move(self, e):
        """
        Allows user to manually move the position of the NS probe. Opens a terminal console if not open already.
        Creates and shows instance of ManualMoveGUI to allow direct input from the user.

        :param e: Event handler.
        :return: Nothing.
        """
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        try:
            step = float(self.grid_tctrl.GetValue())
        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nPlease input numerical values only."
            )
            return
        manual = ManualMoveGUI(self, "Manual Movement", step)
        manual.Show(True)
    def run_correction(self, target_index):
        """
        Runs the 'Correct Previous Value' option from the Post Scan GUI. Starts and runs an instance of
        CorrectionThread to retake a measurement in the specified coordinate.

        :param target_index: the index in the grid that the user chooses to correct.
        :return: Nothing.
        """
        savedir = self.save_tctrl.GetValue()
        # Finding the measurement type
        meas_type = self.type_rbox.GetStringSelection()
        # Finding the measurement field
        meas_field = self.field_rbox.GetStringSelection()
        # Finding the measurement side
        meas_side = self.side_rbox.GetStringSelection()
        # Finding the RBW setting
        meas_rbw = self.rbw_rbox.GetStringSelection()
        self.corr_thread = CorrectionThread(
            self, target_index, self.run_thread.num_steps,
            float(self.dwell_tctrl.GetValue()), self.run_thread.span_start,
            self.run_thread.span_stop, self.values, self.grid, self.curr_row,
            self.curr_col, savedir, self.run_thread.comment, meas_type,
            meas_field, meas_side, meas_rbw, self.max_fname)
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        self.corr_thread.start()
    def reset_motors(self, e):
        """
        Resets the motors back to their default position. Starts and runs instance of ResetThread to facilitate motor
        resets. Opens terminal console if not open already.

        :param e: Event handler.
        :return: Nothing.
        """
        self.disablegui()
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        ResetThread(self).start()
    def run_post_scan(self):
        """
        Plots the area scan results and prompts the user for a post-scan option ('Exit', 'Zoom Scan', 'Correct previous
        value', 'Save data'). Called by the area scan threads (AreaScanThread, ZoomScanThread, CorrectionThread)
        once threads are closed.

        :return: Nothing.
        """
        # Plot the scan
        plotvals = np.copy(self.values)
        plotvals = np.rot90(plotvals)
        plt.close()
        plt.imshow(plotvals,
                   interpolation='bilinear',
                   extent=[0, plotvals.shape[1] - 1, 0, plotvals.shape[0] - 1])
        plt.title('Area Scan Heat Map')
        cbar = plt.colorbar()
        cbar.set_label('Signal Level')
        plt.show(block=False)

        # Post Scan GUI - User selects which option to proceed with
        with PostScanGUI(self,
                         title="Post Scan Options",
                         style=wx.DEFAULT_DIALOG_STYLE | wx.OK) as post_dlg:
            if post_dlg.ShowModal() == wx.ID_OK:
                choice = post_dlg.option_rbox.GetStringSelection()
                print("Choice: ", choice)
            else:
                print("No option selected - Area Scan Complete.")
                self.enablegui()
                return

        if choice == 'Zoom Scan':
            try:
                zdwell = float(self.zdwell_tctrl.GetValue())
            except ValueError:
                self.errormsg(
                    "Invalid scan parameters.\nPlease input numerical values only."
                )
                return
            savedir = self.save_tctrl.GetValue()
            # Finding the measurement type
            meas_type = self.type_rbox.GetStringSelection()
            # Finding the measurement field
            meas_field = self.field_rbox.GetStringSelection()
            # Finding the measurement side
            meas_side = self.side_rbox.GetStringSelection()
            # Finding the RBW setting
            meas_rbw = self.rbw_rbox.GetStringSelection()
            # Finding the measurement
            meas = self.meas_rbox.GetStringSelection()
            self.zoom_thread = ZoomScanThread(
                self, zdwell, self.run_thread.span_start,
                self.run_thread.span_stop, savedir, self.run_thread.comment,
                meas_type, meas_field, meas_side, meas_rbw, meas,
                self.run_thread.num_steps, self.values, self.grid,
                self.curr_row, self.curr_col)
            if not self.console_frame:
                self.console_frame = ConsoleGUI(self, "Console")
            self.console_frame.Show(True)
            sys.stdout = TextRedirector(
                self.console_frame.console_tctrl
            )  # Redirect text from stdout to the console
            sys.stderr = TextRedirector(
                self.console_frame.console_tctrl
            )  # Redirect text from stderr to the console
            self.zoom_thread.start()

        elif choice == 'Correct Previous Value':
            loc_gui = LocationSelectGUI(self, "Location Selection", self.grid)
            loc_gui.Show(True)
        elif choice == 'Save Data':
            pass
        elif choice == 'Exit':
            print("Area Scan Complete. Exiting module.")
            self.enablegui()
    def run_area_scan(self, e):
        """
        Begins general area scan based on the measurement settings specified on the GUI.
        Starts and runs an instance of AreaScanThread to perform automatic area scan.
        Opens console GUI to help user track progress of the program.

        :param e: Event handler.
        :return: Nothing.
        """
        # Make sure entries are valid
        if self.save_tctrl.GetValue() is None or \
                self.save_tctrl.GetValue() is '' or \
                not os.path.exists(self.save_tctrl.GetValue()):
            self.errormsg(
                "Please select a valid save directory for the output files.")
            return
        try:
            self.save_configuration()
            x = float(self.x_tctrl.GetValue())
            y = float(self.y_tctrl.GetValue())
            step = float(self.grid_tctrl.GetValue())
            dwell = float(self.dwell_tctrl.GetValue())
            span_start = float(self.span_start_tctrl.GetValue())
            span_stop = float(self.span_stop_tctrl.GetValue())
            start_pos = (float(self.x_pos_tctrl.GetValue()),
                         float(self.y_pos_tctrl.GetValue()))
        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nPlease input numerical values only."
            )
            return
        # Build comment for savefiles
        if self.eut_model_tctrl.GetValue() is '' or self.eut_sn_tctrl.GetValue() is '' or \
                self.initials_tctrl.GetValue() is '' or self.test_num_tctrl.GetValue() is '':
            self.errormsg(
                "Please fill out all entries in the 'Test Information' section."
            )
            return
        comment = "Model of EUT: " + self.eut_model_tctrl.GetValue() + \
                  " - \r\nS/N of EUT: " + self.eut_sn_tctrl.GetValue() + \
                  " - \r\nTest Engineer Initials: " + self.initials_tctrl.GetValue() + \
                  " - \r\nTest Number: " + self.test_num_tctrl.GetValue()
        savedir = self.save_tctrl.GetValue()
        # Finding the measurement type
        meas_type = self.type_rbox.GetStringSelection()
        # Finding the measurement field
        meas_field = self.field_rbox.GetStringSelection()
        # Finding the measurement side
        meas_side = self.side_rbox.GetStringSelection()
        # Finding the RBW setting
        meas_rbw = self.rbw_rbox.GetStringSelection()
        # Finding the measurement
        meas = self.meas_rbox.GetStringSelection()
        self.run_thread = AreaScanThread(self, x, y, step, dwell, span_start,
                                         span_stop, savedir, comment,
                                         meas_type, meas_field, meas_side,
                                         meas_rbw, meas, start_pos)
        # self.disablegui()
        logger.info("")
        logger.info(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        print("Running general scan...")
        self.run_thread.start()
class MainFrame(wx.Frame):
    """
    Main GUI frame of the entire NS testing program. Handles all children GUI and children threads for automated
    measurement-taking.
    """
    def __init__(self, parent, title):
        """
        :param parent: Parent object calling the MainFrame.
        :param title: Title for the MainFrame window.
        """
        wx.Frame.__init__(self, parent, title=title, size=(600, 750))
        self.scan_panel = wx.Panel(self)

        # Variables
        self.run_thread = None
        self.zoom_thread = None
        self.corr_thread = None
        self.console_frame = None

        self.curr_row = 0  # Grid coordinate row
        self.curr_col = 0  # Grid coordinate col
        self.values = None  # np.array storing area scan values
        self.zoom_values = None  # np.array storing zoom scan values
        self.grid = None  # np.array storing 'trajectory' of scans
        self.max_fname = ''  # Name of the image file for the max measurement

        # Accelerator Table/Shortcut Keys
        save_id = 115
        run_id = 116
        manual_id = 117
        reset_id = 118
        help_id = 119
        pause_id = 120
        resume_id = 121
        self.accel_tbl = wx.AcceleratorTable([
            (wx.ACCEL_CTRL, ord('s'), save_id),
            (wx.ACCEL_CTRL, ord('r'), run_id),
            (wx.ACCEL_CTRL, ord('m'), manual_id),
            (wx.ACCEL_CTRL, ord('t'), reset_id),
            (wx.ACCEL_CTRL, ord('p'), pause_id),
            (wx.ACCEL_CTRL, ord('e'), resume_id),
            (wx.ACCEL_CTRL, ord('h'), help_id)
        ])
        self.SetAcceleratorTable(self.accel_tbl)

        # UI Elements
        self.scan_settings_text = wx.StaticText(self.scan_panel,
                                                label="Area Scan Settings")
        self.scan_settings_text.SetFont(
            wx.Font(10, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.x_distance_text = wx.StaticText(self.scan_panel,
                                             label="X Distance")
        self.x_distance_text.SetFont(
            wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.xdesc_text = wx.StaticText(
            self.scan_panel,
            label="Horizontal length of measurement region (in cm)")
        self.x_tctrl = wx.TextCtrl(self.scan_panel)
        self.x_tctrl.SetValue(str(4 * 2.8))

        self.y_distance_text = wx.StaticText(self.scan_panel,
                                             label="Y Distance")
        self.y_distance_text.SetFont(
            wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.ydesc_text = wx.StaticText(
            self.scan_panel,
            label="Vertical length of measurement region (in cm)")
        self.y_tctrl = wx.TextCtrl(self.scan_panel)
        self.y_tctrl.SetValue(str(6 * 2.8))

        self.grid_step_dist_text = wx.StaticText(self.scan_panel,
                                                 label="Grid Step Distance")
        self.grid_step_dist_text.SetFont(
            wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.griddesc_text = wx.StaticText(
            self.scan_panel,
            label="Distance between measurement points (in cm)")
        self.grid_tctrl = wx.TextCtrl(self.scan_panel)
        self.grid_tctrl.SetValue(str(2.8))

        self.start_point_text = wx.StaticText(self.scan_panel,
                                              label="Starting Point")
        self.start_point_text.SetFont(
            wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.posdesc_text = wx.StaticText(
            self.scan_panel,
            label="To use default starting point, set both row and col to 0")
        self.x_pos_text = wx.StaticText(self.scan_panel, label="row :")
        self.x_pos_tctrl = wx.TextCtrl(self.scan_panel)
        self.x_pos_tctrl.SetValue(str(25))
        self.y_pos_text = wx.StaticText(self.scan_panel, label="col :")
        self.y_pos_tctrl = wx.TextCtrl(self.scan_panel)
        self.y_pos_tctrl.SetValue(str(40))

        self.times_text = wx.StaticText(self.scan_panel,
                                        label="Dwell Time Settings")
        self.times_text.SetFont(wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.dwell_time_text = wx.StaticText(
            self.scan_panel,
            label="Pre-Measurement Dwell Time (Area scan, in sec)")
        self.dwell_tctrl = wx.TextCtrl(self.scan_panel)
        self.dwell_tctrl.SetValue(str(3))
        self.zoom_scan_dwell_time_text = wx.StaticText(
            self.scan_panel,
            label="Pre-Measurement Dwell Time (Zoom scan, in sec)")
        self.zdwell_tctrl = wx.TextCtrl(self.scan_panel)
        self.zdwell_tctrl.SetValue(str(3))

        self.span_text = wx.StaticText(self.scan_panel, label="Span Settings")
        self.span_text.SetFont(wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.span_start_text = wx.StaticText(self.scan_panel,
                                             label="Start (MHz):")
        self.span_start_tctrl = wx.TextCtrl(self.scan_panel)
        self.span_start_tctrl.SetValue(str(0.005))
        self.span_stop_text = wx.StaticText(self.scan_panel,
                                            label="Stop (MHz):")
        self.span_stop_tctrl = wx.TextCtrl(self.scan_panel)
        self.span_stop_tctrl.SetValue(str(5))

        self.save_dir_text = wx.StaticText(self.scan_panel,
                                           label="Save Directory")
        self.save_dir_text.SetFont(
            wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.savedesc_text = wx.StaticText(
            self.scan_panel,
            label="Directory to save measurement text and image files")
        self.save_tctrl = wx.TextCtrl(self.scan_panel)
        # self.save_tctrl.SetValue("C:\\Users\changhwan.choi\Desktop\hello")  # TODO :Debugging
        self.save_btn = wx.Button(self.scan_panel, save_id, "Browse")
        self.Bind(wx.EVT_BUTTON, self.select_save_dir, self.save_btn)

        self.auto_checkbox = wx.CheckBox(
            self.scan_panel,
            label="Automatic Measurements")  # TODO: may not use
        self.auto_checkbox.SetValue(True)

        self.test_info_text = wx.StaticText(self.scan_panel,
                                            label="Test Information")
        self.test_info_text.SetFont(
            wx.Font(10, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.eut_model_text = wx.StaticText(self.scan_panel,
                                            label="Model of EUT: ")
        self.eut_model_tctrl = wx.TextCtrl(self.scan_panel)
        self.eut_sn_text = wx.StaticText(self.scan_panel, label="S/N of EUT: ")
        self.eut_sn_tctrl = wx.TextCtrl(self.scan_panel)
        self.initials_text = wx.StaticText(self.scan_panel,
                                           label="Test Engineer Initials: ")
        self.initials_tctrl = wx.TextCtrl(self.scan_panel)
        self.test_num_text = wx.StaticText(self.scan_panel,
                                           label="Test Number: ")
        self.test_num_tctrl = wx.TextCtrl(self.scan_panel)

        self.measurement_specs_text = wx.StaticText(
            self.scan_panel, label="Measurement Specifications")
        self.measurement_specs_text.SetFont(
            wx.Font(10, wx.DECORATIVE, wx.NORMAL, wx.BOLD))
        self.type_rbox = wx.RadioBox(self.scan_panel,
                                     label="Type",
                                     choices=['Limb', 'Body'],
                                     style=wx.RA_SPECIFY_COLS,
                                     majorDimension=1)
        self.field_rbox = wx.RadioBox(
            self.scan_panel,
            label="Field",
            choices=['Electric', 'Magnetic (Mode A)', 'Magnetic (Mode B)'],
            style=wx.RA_SPECIFY_COLS,
            majorDimension=1)
        self.side_rbox = wx.RadioBox(
            self.scan_panel,
            label="Side",
            choices=['Front', 'Back', 'Top', 'Bottom', 'Left', 'Right'],
            style=wx.RA_SPECIFY_COLS,
            majorDimension=1)
        self.side_rbox.SetSelection(1)
        self.rbw_rbox = wx.RadioBox(self.scan_panel,
                                    label="RBW",
                                    choices=[
                                        '300 kHz', '10 kHz', '100 kHz',
                                        '3 kHz', '30 kHz', '1 kHz'
                                    ],
                                    style=wx.RA_SPECIFY_COLS,
                                    majorDimension=2)
        self.rbw_rbox.SetSelection(2)
        self.meas_rbox = wx.RadioBox(self.scan_panel,
                                     label="Measurement",
                                     choices=['Highest Peak', 'WideBand'],
                                     style=wx.RA_SPECIFY_COLS,
                                     majorDimension=1)

        self.reset_btn = wx.Button(self.scan_panel, reset_id, "Reset Motors")
        self.Bind(wx.EVT_BUTTON, self.reset_motors, self.reset_btn)
        self.manual_btn = wx.Button(self.scan_panel, manual_id,
                                    "Manual Movement")
        self.Bind(wx.EVT_BUTTON, self.manual_move, self.manual_btn)
        self.run_btn = wx.Button(self.scan_panel, run_id, "Run")
        self.Bind(wx.EVT_BUTTON, self.run_area_scan, self.run_btn)

        # Menu Bar
        menubar = wx.MenuBar()
        helpmenu = wx.Menu()
        shortcuthelp_item = wx.MenuItem(helpmenu,
                                        help_id,
                                        text="Shortcuts",
                                        kind=wx.ITEM_NORMAL)
        helpmenu.Append(shortcuthelp_item)
        menubar.Append(helpmenu, 'Help')
        self.Bind(wx.EVT_MENU, self.showshortcuts, id=help_id)
        self.SetMenuBar(menubar)

        # Sizers/Layout, Static Lines, & Static Boxes
        self.saveline_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.checkbox_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.pos_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.span_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.test_info_sizer = wx.GridSizer(rows=4, cols=2, hgap=0, vgap=0)
        self.text_input_sizer = wx.BoxSizer(wx.VERTICAL)
        self.radio_input_sizer = wx.BoxSizer(wx.VERTICAL)
        self.btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.mainh_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.mainv_sizer = wx.BoxSizer(wx.VERTICAL)

        self.text_input_sizer.Add(self.scan_settings_text,
                                  proportion=0,
                                  border=3,
                                  flag=wx.BOTTOM)
        self.text_input_sizer.Add(self.x_distance_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.xdesc_text, proportion=0, flag=wx.LEFT)
        self.text_input_sizer.Add(self.x_tctrl,
                                  proportion=0,
                                  flag=wx.LEFT | wx.EXPAND)
        self.text_input_sizer.Add(self.y_distance_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.ydesc_text, proportion=0, flag=wx.LEFT)
        self.text_input_sizer.Add(self.y_tctrl,
                                  proportion=0,
                                  flag=wx.LEFT | wx.EXPAND)
        self.text_input_sizer.Add(self.grid_step_dist_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.griddesc_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.grid_tctrl,
                                  proportion=0,
                                  flag=wx.LEFT | wx.EXPAND)

        self.text_input_sizer.Add(self.start_point_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.posdesc_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.pos_sizer.Add(self.x_pos_text, proportion=0, flag=wx.LEFT)
        self.pos_sizer.Add(self.x_pos_tctrl,
                           proportion=1,
                           flag=wx.LEFT | wx.RIGHT | wx.EXPAND,
                           border=5)
        self.pos_sizer.Add(self.y_pos_text, proportion=0, flag=wx.LEFT)
        self.pos_sizer.Add(self.y_pos_tctrl,
                           proportion=1,
                           flag=wx.LEFT | wx.EXPAND,
                           border=5)
        self.text_input_sizer.Add(self.pos_sizer, proportion=0, flag=wx.EXPAND)

        self.text_input_sizer.Add(self.times_text, proportion=0, flag=wx.LEFT)
        self.text_input_sizer.Add(self.dwell_time_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.dwell_tctrl, proportion=0, flag=wx.LEFT)
        self.text_input_sizer.Add(self.zoom_scan_dwell_time_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.zdwell_tctrl,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.span_text, proportion=0, flag=wx.LEFT)
        self.span_sizer.Add(self.span_start_text, proportion=0, flag=wx.LEFT)
        self.span_sizer.Add(self.span_start_tctrl,
                            proportion=1,
                            flag=wx.LEFT | wx.RIGHT | wx.EXPAND,
                            border=5)
        self.span_sizer.Add(self.span_stop_text, proportion=0, flag=wx.LEFT)
        self.span_sizer.Add(self.span_stop_tctrl,
                            proportion=1,
                            flag=wx.LEFT | wx.EXPAND,
                            border=5)
        self.text_input_sizer.Add(self.span_sizer,
                                  proportion=0,
                                  flag=wx.EXPAND)
        self.text_input_sizer.Add(self.save_dir_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.text_input_sizer.Add(self.savedesc_text,
                                  proportion=0,
                                  flag=wx.LEFT)
        self.saveline_sizer.Add(self.save_tctrl,
                                proportion=1,
                                flag=wx.LEFT | wx.EXPAND)
        self.saveline_sizer.Add(self.save_btn,
                                proportion=0,
                                flag=wx.ALIGN_RIGHT | wx.LEFT,
                                border=5)
        self.checkbox_sizer.Add(self.auto_checkbox,
                                proportion=0,
                                flag=wx.ALIGN_LEFT | wx.ALL,
                                border=5)
        self.text_input_sizer.Add(self.saveline_sizer,
                                  proportion=0,
                                  flag=wx.LEFT | wx.EXPAND)
        self.text_input_sizer.Add(self.checkbox_sizer,
                                  proportion=0,
                                  flag=wx.LEFT | wx.EXPAND)
        self.text_input_sizer.Add(wx.StaticLine(self.scan_panel,
                                                wx.ID_ANY,
                                                style=wx.LI_HORIZONTAL),
                                  proportion=0,
                                  border=5,
                                  flag=wx.TOP | wx.BOTTOM | wx.EXPAND)
        self.text_input_sizer.Add(self.test_info_text,
                                  proportion=0,
                                  flag=wx.BOTTOM,
                                  border=3)
        self.test_info_sizer.Add(self.eut_model_text, proportion=0)
        self.test_info_sizer.Add(self.eut_model_tctrl,
                                 proportion=0,
                                 flag=wx.EXPAND)
        self.test_info_sizer.Add(self.eut_sn_text, proportion=0)
        self.test_info_sizer.Add(self.eut_sn_tctrl,
                                 proportion=0,
                                 flag=wx.EXPAND)
        self.test_info_sizer.Add(self.initials_text, proportion=0)
        self.test_info_sizer.Add(self.initials_tctrl,
                                 proportion=0,
                                 flag=wx.EXPAND)
        self.test_info_sizer.Add(self.test_num_text, proportion=0)
        self.test_info_sizer.Add(self.test_num_tctrl,
                                 proportion=0,
                                 flag=wx.EXPAND)
        self.text_input_sizer.Add(self.test_info_sizer,
                                  proportion=0,
                                  flag=wx.EXPAND)

        self.radio_input_sizer.Add(self.measurement_specs_text,
                                   proportion=0,
                                   border=3,
                                   flag=wx.BOTTOM)
        self.radio_input_sizer.Add(self.type_rbox,
                                   proportion=0,
                                   flag=wx.ALL | wx.EXPAND)
        self.radio_input_sizer.Add(self.field_rbox,
                                   proportion=0,
                                   flag=wx.ALL | wx.EXPAND)
        self.radio_input_sizer.Add(self.side_rbox,
                                   proportion=0,
                                   flag=wx.ALL | wx.EXPAND)
        self.radio_input_sizer.Add(self.rbw_rbox,
                                   proportion=0,
                                   flag=wx.ALL | wx.EXPAND)
        self.radio_input_sizer.Add(self.meas_rbox,
                                   proportion=0,
                                   flag=wx.ALL | wx.EXPAND)

        self.mainh_sizer.Add(self.text_input_sizer,
                             proportion=2,
                             border=5,
                             flag=wx.ALL | wx.EXPAND)
        self.mainh_sizer.Add(wx.StaticLine(self.scan_panel,
                                           wx.ID_ANY,
                                           style=wx.LI_VERTICAL),
                             proportion=0,
                             border=5,
                             flag=wx.TOP | wx.BOTTOM | wx.EXPAND)
        self.mainh_sizer.Add(self.radio_input_sizer,
                             proportion=1,
                             border=5,
                             flag=wx.ALL | wx.EXPAND)

        self.btn_sizer.Add(self.reset_btn,
                           proportion=1,
                           border=5,
                           flag=wx.ALIGN_RIGHT | wx.LEFT | wx.TOP | wx.BOTTOM)
        self.btn_sizer.Add(self.manual_btn,
                           proportion=1,
                           border=5,
                           flag=wx.ALIGN_RIGHT | wx.LEFT | wx.TOP | wx.BOTTOM)
        self.btn_sizer.Add(self.run_btn,
                           proportion=1,
                           border=5,
                           flag=wx.ALIGN_RIGHT | wx.ALL)

        self.mainv_sizer.Add(self.mainh_sizer,
                             proportion=1,
                             border=0,
                             flag=wx.ALL | wx.EXPAND)
        self.mainv_sizer.Add(wx.StaticLine(self.scan_panel,
                                           wx.ID_ANY,
                                           style=wx.LI_HORIZONTAL),
                             proportion=0,
                             border=0,
                             flag=wx.ALL | wx.EXPAND)
        self.mainv_sizer.Add(self.btn_sizer,
                             proportion=0,
                             border=5,
                             flag=wx.ALIGN_RIGHT)

        # load previous configuration when initialize the panel
        self.load_configuration()

        self.scan_panel.SetSizer(self.mainv_sizer)
        pan_size = self.scan_panel.GetSize()
        print(pan_size)
        self.SetSize(pan_size)
        self.SetMinSize(pan_size)
        self.SetMaxSize(pan_size)
        self.SetAutoLayout(True)
        # self.scan_panel.Fit()
        self.mainv_sizer.Fit(self.scan_panel)
        self.Layout()
        self.Show(True)

    def select_save_dir(self, e):
        """
        Opens quick dialog to select the save directory for the output files (.txt, .png) from the automatic
        measurements. Writes the directory name on the TextCtrl object on the GUI (self.save_tctrl).

        :param e: Event handler.
        :return: Nothing.
        """
        # TODO: Currently, there is a problem with wx.DirDialog, so I have resorted to using multidirdialog
        # TODO: When the bugs are fixed on their end, revert back to the nicer looking wx.DirDialog
        with mdd.MultiDirDialog(None,
                                "Select save directory for output files.",
                                style=mdd.DD_DIR_MUST_EXIST
                                | mdd.DD_NEW_DIR_BUTTON) as dlg:
            if dlg.ShowModal() == wx.ID_CANCEL:
                return
            # Correcting name format to fit future save functions
            path = dlg.GetPaths()[0]
            path = path.split(':')[0][-1] + ':' + path.split(':)')[1]
            self.save_tctrl.SetValue(path)
        # with wx.DirDialog(self, "Select save directory for '.txt' and '.png' files.",
        #                   style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST) as dlg:
        #    if dlg.ShowModal() == wx.ID_CANCEL:
        #        return
        #    self.save_dir = dlg.GetPath()
        #    self.save_tctrl.SetValue(self.save_dir)
        #    if os.path.exists(parentpath):

    def save_configuration(self, filename='prev_config.txt'):
        """
        Save current configuration to a txt file

        """
        try:
            config = {}
            config['x'] = self.x_tctrl.GetValue()
            config['y'] = self.y_tctrl.GetValue()
            config['step'] = self.grid_tctrl.GetValue()
            config['start_pos'] = (self.x_pos_tctrl.GetValue(),
                                   self.y_pos_tctrl.GetValue())
            config['dwell'] = self.dwell_tctrl.GetValue()
            config['zdwell'] = self.zdwell_tctrl.GetValue()
            config['start'] = self.span_start_tctrl.GetValue()
            config['stop'] = self.span_stop_tctrl.GetValue()
            config['checkbox'] = self.auto_checkbox.GetValue()
            config['type'] = self.type_rbox.GetSelection()
            config['field'] = self.field_rbox.GetSelection()
            config['side'] = self.side_rbox.GetSelection()
            config['rbw'] = self.rbw_rbox.GetSelection()
            config['measurement'] = self.meas_rbox.GetSelection()
            config['dir'] = self.save_tctrl.GetValue()

            json.dump(config, open(filename, 'w'))

        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nCannot save current configuration.")
            return

        return

    def load_configuration(self, filename='prev_config.txt'):
        """
        Load the saved configuration

        """
        # TODO: Add error exception
        if os.path.exists(filename):
            config = json.load(open(filename))
            self.x_tctrl.SetValue(config['x'])
            self.y_tctrl.SetValue(config['y'])
            self.grid_tctrl.SetValue(config['step'])
            self.x_pos_tctrl.SetValue(config['start_pos'][0])
            self.y_pos_tctrl.SetValue(config['start_pos'][1])
            self.dwell_tctrl.SetValue(config['dwell'])
            self.zdwell_tctrl.SetValue(config['zdwell'])
            self.span_start_tctrl.SetValue(config['start'])
            self.span_stop_tctrl.SetValue(config['stop'])
            self.auto_checkbox.SetValue(config['checkbox'])
            self.type_rbox.SetSelection(int(config['type']))
            self.field_rbox.SetSelection(int(config['field']))
            self.side_rbox.SetSelection(int(config['side']))
            self.rbw_rbox.SetSelection(int(config['rbw']))
            self.meas_rbox.SetSelection(int(config['measurement']))
            self.save_tctrl.SetValue(config['dir'])

    def run_area_scan(self, e):
        """
        Begins general area scan based on the measurement settings specified on the GUI.
        Starts and runs an instance of AreaScanThread to perform automatic area scan.
        Opens console GUI to help user track progress of the program.

        :param e: Event handler.
        :return: Nothing.
        """
        # Make sure entries are valid
        if self.save_tctrl.GetValue() is None or \
                self.save_tctrl.GetValue() is '' or \
                not os.path.exists(self.save_tctrl.GetValue()):
            self.errormsg(
                "Please select a valid save directory for the output files.")
            return
        try:
            self.save_configuration()
            x = float(self.x_tctrl.GetValue())
            y = float(self.y_tctrl.GetValue())
            step = float(self.grid_tctrl.GetValue())
            dwell = float(self.dwell_tctrl.GetValue())
            span_start = float(self.span_start_tctrl.GetValue())
            span_stop = float(self.span_stop_tctrl.GetValue())
            start_pos = (float(self.x_pos_tctrl.GetValue()),
                         float(self.y_pos_tctrl.GetValue()))
        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nPlease input numerical values only."
            )
            return
        # Build comment for savefiles
        if self.eut_model_tctrl.GetValue() is '' or self.eut_sn_tctrl.GetValue() is '' or \
                self.initials_tctrl.GetValue() is '' or self.test_num_tctrl.GetValue() is '':
            self.errormsg(
                "Please fill out all entries in the 'Test Information' section."
            )
            return
        comment = "Model of EUT: " + self.eut_model_tctrl.GetValue() + \
                  " - \r\nS/N of EUT: " + self.eut_sn_tctrl.GetValue() + \
                  " - \r\nTest Engineer Initials: " + self.initials_tctrl.GetValue() + \
                  " - \r\nTest Number: " + self.test_num_tctrl.GetValue()
        savedir = self.save_tctrl.GetValue()
        # Finding the measurement type
        meas_type = self.type_rbox.GetStringSelection()
        # Finding the measurement field
        meas_field = self.field_rbox.GetStringSelection()
        # Finding the measurement side
        meas_side = self.side_rbox.GetStringSelection()
        # Finding the RBW setting
        meas_rbw = self.rbw_rbox.GetStringSelection()
        # Finding the measurement
        meas = self.meas_rbox.GetStringSelection()
        self.run_thread = AreaScanThread(self, x, y, step, dwell, span_start,
                                         span_stop, savedir, comment,
                                         meas_type, meas_field, meas_side,
                                         meas_rbw, meas, start_pos)
        # self.disablegui()
        logger.info("")
        logger.info(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        print("Running general scan...")
        self.run_thread.start()

    def run_post_scan(self):
        """
        Plots the area scan results and prompts the user for a post-scan option ('Exit', 'Zoom Scan', 'Correct previous
        value', 'Save data'). Called by the area scan threads (AreaScanThread, ZoomScanThread, CorrectionThread)
        once threads are closed.

        :return: Nothing.
        """
        # Plot the scan
        plotvals = np.copy(self.values)
        plotvals = np.rot90(plotvals)
        plt.close()
        plt.imshow(plotvals,
                   interpolation='bilinear',
                   extent=[0, plotvals.shape[1] - 1, 0, plotvals.shape[0] - 1])
        plt.title('Area Scan Heat Map')
        cbar = plt.colorbar()
        cbar.set_label('Signal Level')
        plt.show(block=False)

        # Post Scan GUI - User selects which option to proceed with
        with PostScanGUI(self,
                         title="Post Scan Options",
                         style=wx.DEFAULT_DIALOG_STYLE | wx.OK) as post_dlg:
            if post_dlg.ShowModal() == wx.ID_OK:
                choice = post_dlg.option_rbox.GetStringSelection()
                print("Choice: ", choice)
            else:
                print("No option selected - Area Scan Complete.")
                self.enablegui()
                return

        if choice == 'Zoom Scan':
            try:
                zdwell = float(self.zdwell_tctrl.GetValue())
            except ValueError:
                self.errormsg(
                    "Invalid scan parameters.\nPlease input numerical values only."
                )
                return
            savedir = self.save_tctrl.GetValue()
            # Finding the measurement type
            meas_type = self.type_rbox.GetStringSelection()
            # Finding the measurement field
            meas_field = self.field_rbox.GetStringSelection()
            # Finding the measurement side
            meas_side = self.side_rbox.GetStringSelection()
            # Finding the RBW setting
            meas_rbw = self.rbw_rbox.GetStringSelection()
            # Finding the measurement
            meas = self.meas_rbox.GetStringSelection()
            self.zoom_thread = ZoomScanThread(
                self, zdwell, self.run_thread.span_start,
                self.run_thread.span_stop, savedir, self.run_thread.comment,
                meas_type, meas_field, meas_side, meas_rbw, meas,
                self.run_thread.num_steps, self.values, self.grid,
                self.curr_row, self.curr_col)
            if not self.console_frame:
                self.console_frame = ConsoleGUI(self, "Console")
            self.console_frame.Show(True)
            sys.stdout = TextRedirector(
                self.console_frame.console_tctrl
            )  # Redirect text from stdout to the console
            sys.stderr = TextRedirector(
                self.console_frame.console_tctrl
            )  # Redirect text from stderr to the console
            self.zoom_thread.start()

        elif choice == 'Correct Previous Value':
            loc_gui = LocationSelectGUI(self, "Location Selection", self.grid)
            loc_gui.Show(True)
        elif choice == 'Save Data':
            pass
        elif choice == 'Exit':
            print("Area Scan Complete. Exiting module.")
            self.enablegui()

    def update_values(self, call_thread):
        """
        Updates the variables stored in the MainFrame based on the measurement results returned from the scans.
        Called by the area scan threads (AreaScanThread, ZoomScanThread, CorrectionThread).

        :param call_thread: The thread calling the update method and updating the variables stored in the MainFrame.
        :return: Nothing.
        """
        self.curr_row = call_thread.curr_row
        self.curr_col = call_thread.curr_col
        if type(call_thread) is AreaScanThread:
            self.values = call_thread.values
            self.grid = call_thread.grid
            self.max_fname = call_thread.max_fname
        elif type(call_thread) is CorrectionThread:
            self.values = call_thread.values
        elif type(call_thread) is ZoomScanThread:
            self.zoom_values = call_thread.zoom_values

    def run_correction(self, target_index):
        """
        Runs the 'Correct Previous Value' option from the Post Scan GUI. Starts and runs an instance of
        CorrectionThread to retake a measurement in the specified coordinate.

        :param target_index: the index in the grid that the user chooses to correct.
        :return: Nothing.
        """
        savedir = self.save_tctrl.GetValue()
        # Finding the measurement type
        meas_type = self.type_rbox.GetStringSelection()
        # Finding the measurement field
        meas_field = self.field_rbox.GetStringSelection()
        # Finding the measurement side
        meas_side = self.side_rbox.GetStringSelection()
        # Finding the RBW setting
        meas_rbw = self.rbw_rbox.GetStringSelection()
        self.corr_thread = CorrectionThread(
            self, target_index, self.run_thread.num_steps,
            float(self.dwell_tctrl.GetValue()), self.run_thread.span_start,
            self.run_thread.span_stop, self.values, self.grid, self.curr_row,
            self.curr_col, savedir, self.run_thread.comment, meas_type,
            meas_field, meas_side, meas_rbw, self.max_fname)
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        self.corr_thread.start()

    def manual_move(self, e):
        """
        Allows user to manually move the position of the NS probe. Opens a terminal console if not open already.
        Creates and shows instance of ManualMoveGUI to allow direct input from the user.

        :param e: Event handler.
        :return: Nothing.
        """
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        try:
            step = float(self.grid_tctrl.GetValue())
        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nPlease input numerical values only."
            )
            return
        manual = ManualMoveGUI(self, "Manual Movement", step)
        manual.Show(True)

    def reset_motors(self, e):
        """
        Resets the motors back to their default position. Starts and runs instance of ResetThread to facilitate motor
        resets. Opens terminal console if not open already.

        :param e: Event handler.
        :return: Nothing.
        """
        self.disablegui()
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        ResetThread(self).start()

    def enablegui(self):
        """
        Re-enables all MainFrame GUI elements.

        :return: Nothing.
        """
        self.x_tctrl.Enable(True)
        self.y_tctrl.Enable(True)
        self.grid_tctrl.Enable(True)
        self.dwell_tctrl.Enable(True)
        self.zdwell_tctrl.Enable(True)
        self.span_start_tctrl.Enable(True)
        self.span_stop_tctrl.Enable(True)
        self.save_tctrl.Enable(True)
        self.auto_checkbox.Enable(True)
        self.save_btn.Enable(True)
        self.eut_model_tctrl.Enable(True)
        self.eut_sn_tctrl.Enable(True)
        self.initials_tctrl.Enable(True)
        self.test_num_tctrl.Enable(True)
        self.type_rbox.Enable(True)
        self.field_rbox.Enable(True)
        self.side_rbox.Enable(True)
        self.rbw_rbox.Enable(True)
        self.reset_btn.Enable(True)
        self.manual_btn.Enable(True)
        self.run_btn.Enable(True)

    def disablegui(self):
        """
        Disables all MainFrame GUI elements.

        :return: Nothing.
        """
        self.x_tctrl.Enable(False)
        self.y_tctrl.Enable(False)
        self.grid_tctrl.Enable(False)
        self.dwell_tctrl.Enable(False)
        self.zdwell_tctrl.Enable(False)
        self.span_start_tctrl.Enable(False)
        self.span_stop_tctrl.Enable(False)
        self.save_tctrl.Enable(False)
        self.auto_checkbox.Enable(False)
        self.save_btn.Enable(False)
        self.eut_model_tctrl.Enable(False)
        self.eut_sn_tctrl.Enable(False)
        self.initials_tctrl.Enable(False)
        self.test_num_tctrl.Enable(False)
        self.type_rbox.Enable(False)
        self.field_rbox.Enable(False)
        self.side_rbox.Enable(False)
        self.rbw_rbox.Enable(False)
        self.reset_btn.Enable(False)
        self.manual_btn.Enable(False)
        self.run_btn.Enable(False)

    def showshortcuts(self, e):
        """
        Opens simple dialog listing the different shortcuts of the program.

        :param e: Event handler.
        :return: Nothing.
        """
        shortcuts_string = "Shortcuts:\n" +\
                           "Select Save Directory: Ctrl + S\n" +\
                           "Reset Motors: Ctrl + T\n" +\
                           "Manual Movement: Ctrl + M\n" +\
                           "Run Analysis: Ctrl + E\n" +\
                           "Check Shortcut Keys: Ctrl + H"
        with wx.MessageDialog(self,
                              shortcuts_string,
                              'Shortcut Keys',
                              style=wx.OK | wx.ICON_QUESTION
                              | wx.CENTER) as dlg:
            dlg.ShowModal()

    def errormsg(self, errmsg):
        """
        Shows an error message as a wx.Dialog.

        :param errmsg: String error message to show in the message dialog.
        :return: Nothing
        """
        with wx.MessageDialog(self,
                              errmsg,
                              style=wx.OK | wx.ICON_ERROR | wx.CENTER) as dlg:
            dlg.ShowModal()
    def run_area_scan(self, e):
        """
        Begins general area scan based on the measurement settings specified on the GUI.
        Starts and runs an instance of AreaScanThread to perform automatic area scan.
        Opens console GUI to help user track progress of the program.

        :param e: Event handler.
        :return: Nothing.
        """
        # Make sure entries are valid
        if self.save_tctrl.GetValue() is None or \
                self.save_tctrl.GetValue() is '' or \
                not os.path.exists(self.save_tctrl.GetValue()):
            self.errormsg(
                "Please select a valid save directory for the output files.")
            return
        try:
            self.save_configuration()
            x = float(self.x_tctrl.GetValue())
            y = float(self.y_tctrl.GetValue())
            step = float(self.grid_tctrl.GetValue())
            dwell = float(self.dwell_tctrl.GetValue())
            span_start = float(self.span_start_tctrl.GetValue())
            span_stop = float(self.span_stop_tctrl.GetValue())
            start_pos = int(self.pos_tctrl.GetValue())
            zoom_scan = self.zoom_checkbox.GetValue()
        except ValueError:
            self.errormsg(
                "Invalid scan parameters.\nPlease input numerical values only."
            )
            return
        # Build comment for savefiles
        if self.eut_model_tctrl.GetValue() is '' or self.eut_sn_tctrl.GetValue() is '' or \
                self.initials_tctrl.GetValue() is '' or self.test_num_tctrl.GetValue() is '':
            self.errormsg(
                "Please fill out all entries in the 'Test Information' section."
            )
            return
        comment = "Model of EUT: " + self.eut_model_tctrl.GetValue() + \
                  " - \r\nS/N of EUT: " + self.eut_sn_tctrl.GetValue() + \
                  " - \r\nTest Engineer Initials: " + self.initials_tctrl.GetValue() + \
                  " - \r\nTest Number: " + self.test_num_tctrl.GetValue()
        savedir = self.save_tctrl.GetValue()
        # Finding the measurement type
        meas_type = self.type_rbox.GetStringSelection()
        # Finding the measurement field
        meas_field = self.field_rbox.GetStringSelection()
        # Finding the measurement side
        meas_side = self.side_rbox.GetStringSelection()
        # Finding the RBW setting
        meas_rbw = self.rbw_rbox.GetStringSelection()
        # Finding the measurement
        meas = self.meas_rbox.GetStringSelection()
        start_pos = int(self.pos_tctrl.GetValue())
        self.logger = Log(self.getLogFileName()).getLogger()

        if zoom_scan:
            # convert grid number to row and col
            try:
                zdwell = float(self.zdwell_tctrl.GetValue())
            except ValueError:
                self.errormsg(
                    "Invalid scan parameters.\nPlease input numerical values only."
                )
                return
            # Preparation
            step_unit = 0.00508  # TODO: Used the default step_unit for now, fix this when unit change
            num_steps = step / step_unit
            x_points = int(np.ceil(np.around(x / step, decimals=3))) + 1
            y_points = int(np.ceil(np.around(y / step, decimals=3))) + 1
            #print("x_points: ", x_points)
            #print("y_points: ", y_points)
            self.run_thread = ZoomScanThread(
                self, zdwell, span_start, span_stop, savedir, comment,
                meas_type, meas_field, meas_side, meas_rbw, meas, num_steps,
                self.values, self.grid, self.curr_row, self.curr_col,
                zoom_scan, start_pos, x_points, y_points)
        else:
            try:
                self.run_thread = AreaScanThread(self, x, y, step, dwell,
                                                 span_start, span_stop,
                                                 savedir, comment, meas_type,
                                                 meas_field, meas_side,
                                                 meas_rbw, meas, start_pos)
            except:
                self.run_thread.join()

        # self.disablegui() # TODO:Check if need disable gui
        self.logger.info(datetime.now().strftime("%Y/%m/%d"))
        if not self.console_frame:
            self.console_frame = ConsoleGUI(self, "Console")
        self.console_frame.Show(True)
        sys.stdout = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stdout to the console
        sys.stderr = TextRedirector(
            self.console_frame.console_tctrl
        )  # Redirect text from stderr to the console
        print("Running general scan...")
        self.logger.info("Running general scan...")
        self.run_thread.start()