Beispiel #1
0
    def image_select(self, file_image=None):
        """
        Select an image from a file dialogue box and update on screen.
        """

        if file_image is None:
            file_image = filedialog.askopenfilename(
                initialdir='Images',
                title="Select Image",
                filetypes=(("png images", "*.png"), ("jpeg images", "*.jpeg"),
                           ("All files", "*.*")))
        file_name = ntpath.basename(file_image)
        image_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': file_image,
            'name_image': file_name
        }
        try:
            self.image = MyImage(image_configs)
        except NoFileError as e:
            e.advice = 'Select a different image.'
            super().error_window(e)
            return
        self.label_image.configure(image=self.image.original_tkinter)
        self.label_imagemod.configure(image=self.image.modified_tkinter)
        self.label_image_title.configure(text=self.image.name_image)
        self.label_imagemod_title.configure(text='%s, Modified' %
                                            (self.image.name_image))
Beispiel #2
0
    def setup_image_default(self, frame_top: tk.Frame, frame_bottom: tk.Frame):
        """
        Set up the default images on main window.
        """

        image_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': 'Images/Sample Image.png',
            'name_image': 'Sample Image'
        }
        try:
            self.image = MyImage(image_configs)
        except NoFileError as e:
            e.advice = 'Place a new default image in the correct directory.'
            super().error_window(e)
            return
        self.label_image_title = tk.Label(frame_top)
        self.label_image_title.pack()
        self.label_image = tk.Label(frame_top,
                                    image=self.image.original_tkinter)
        self.label_image.pack()
        self.label_imagemod_title = tk.Label(frame_bottom,
                                             text='Modified Sample Image')
        self.label_imagemod_title.pack()
        self.label_imagemod = tk.Label(frame_bottom,
                                       image=self.image.modified_tkinter)
        self.label_imagemod.pack()
Beispiel #3
0
    def setup_grating_default(self, frame: tk.Frame):
        """
        Set up the default gratings on main window.
        """

        self.grating_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_path': 'Images/Sample_Grating.png',
            'grating_name': 'Sample_Grating.png',
            'g_type': 'SawTooth',
            'g_angle': 0,
            'y_max': 255,
            'y_min': 0,
            'period': 100,
            'reverse': 0
        }
        grating_preview_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': 'Images/Sample_Grating.png',
            'name_image': 'Sample_Grating.png'
        }
        try:
            self.label_grating_title = tk.Label(frame, text='Grating Preview')
            self.label_grating_title.pack()
            self.grating_preview = MyImage(grating_preview_configs)
            self.label_grating = tk.Label(
                frame, image=self.grating_preview.original_tkinter)
            self.label_grating.pack()
        except NoFileError as e:
            e.advice = 'Place a new default grating in the correct directory.'
            super().error_window(e)
            return
Beispiel #4
0
class SLM_Image(HologramCreator):
    def __init__(self, root: tk.Tk):
        """
        Constructor call with parent constructor.
        """

        #Create a root with HologramCreator, the parent.
        window_configs = {
            'Window Title': 'SLM Image Hologram Creator -- ' +
            'Copyright 2020, Luke Kurlandski and Matthew Van Soelen, all rights reserved',
            'Frames Vertical': 4,
            'Frames Horizontal': 5
        }
        super().__init__(root, window_configs)
        self.item_list = []
        self.list_box = None
        self.slm = None
        self.grating_name = None
        self.grating_file_path = None

        #Apply some frame modifications for large wigits.
        self.frames[1][2].grid(row=1,
                               column=2,
                               pady=10,
                               rowspan=200,
                               sticky='NW')
        self.frames[2][2].grid(row=2,
                               column=2,
                               pady=10,
                               rowspan=200,
                               sticky='W')
        self.frames[0][3].grid(row=0,
                               column=3,
                               pady=10,
                               rowspan=200,
                               sticky='NW',
                               padx=10)
        self.frames[0][4].grid(row=0,
                               column=4,
                               pady=10,
                               rowspan=200,
                               sticky='NE',
                               padx=10)

        #Setup main window with HologramCreator, the parent.
        super().setup_film(self.frames[0][0])
        super().setup_image_select(self.frames[1][0])
        super().setup_initialize_experiment(self.frames[2][0])
        super().setup_while_running(self.frames[3][0])
        super().setup_grating_options(self.frames[0][1])
        super().setup_exposure_details(self.frames[0][3])
        super().setup_ignore_details(self.frames[0][3])
        super().setup_laser_details(self.frames[0][3])
        super().setup_grating_details(self.frames[0][3])
        super().setup_image_array(self.frames[0][4])
        super().setup_experiment_details_view(self.frames[2][1])

        #Setup main window with SLM_Image, the self.
        self.setup_menu()
        self.setup_image_default(self.frames[1][2], self.frames[2][2])
        self.setup_grating_default(self.frames[0][2])
        self.setup_list_view(self.frames[1][1])

        #Open the previous experiment
        self.open_experiment('Experiments/Previous Experiment.txt')

##############################################################################
#Setup Menu and Main Window
##############################################################################

    def setup_menu(self):
        """
        Set up the menu for the main window.
        """

        #Create label-command pairs in a dictionary for the various submenus.
        submenu_file = {
            'Quit':
            self.root.destroy,
            'Open Experiment':
            self.open_experiment,
            'Open Previous':
            lambda: self.open_experiment('Experiments/Previous Experiment.txt'
                                         ),
            'Open Example':
            lambda: self.open_experiment('Experiments/Example Experiment.txt'),
            'Clear Inputs':
            self.clear_experiment
        }
        submenu_serial = {
            'Motor':
            lambda: self.set_serial_configs(
                {
                    'Serial Name': 'Motor',
                    'File Name': 'Equipment/Motor Serial.txt'
                }),
            'Shutter':
            lambda: self.set_serial_configs(
                {
                    'Serial Name': 'Shutter',
                    'File Name': 'Equipment/Shutter Serial.txt'
                }),
            'Laser':
            lambda: self.set_serial_configs(
                {
                    'Serial Name': 'Laser',
                    'File Name': 'Equipment/Laser Serial.txt'
                })
        }
        submenu_equipment = {
            'Motor':
            lambda: self.set_equipment_settings('Equipment/Motor Settings.txt',
                                                'Motor'),
            'Shutter':
            lambda: self.set_equipment_settings(
                'Equipment/Shutter Settings.txt', 'Shutter'),
            'Laser':
            lambda: self.set_equipment_settings('Equipment/Laser Settings.txt',
                                                'Laser')
        }
        submenu_view = {
            'Image as Array':
            lambda: self.display_image_array(self.item.image),
            'Mapping Graph': lambda: self.generate_plot(self.item)
        }
        submenu_help = {
            'General':
            lambda: self.help_window('Help/General.txt'),
            'Set Up Film':
            lambda: self.help_window('Help/Set Up Film.txt'),
            'Image Selection':
            lambda: self.help_window('Help/Exposure Details.txt'),
            'Exposure Details':
            lambda: self.help_window('Help/Set Up Film.txt'),
            'Initialize Experiment':
            lambda: self.help_window('Help/Initialize Experiment.txt'),
            'While Running':
            lambda: self.help_window('Help/While Running.txt')
        }
        #Dictionary of dictionaries to describe the whole menu.
        menu_total = {
            #Name of Submenu : dictionary of label-command pairs
            'File': submenu_file,
            'Serial': submenu_serial,
            'Equipment': submenu_equipment,
            'View': submenu_view,
            'Help': submenu_help
        }
        #Pass to parent method to create a main menu.
        self.main_menu = super().create_mainmenu(self.root, menu_total)

    def setup_image_default(self, frame_top: tk.Frame, frame_bottom: tk.Frame):
        """
        Set up the default images on main window.
        """

        image_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': 'Images/Sample Image.png',
            'name_image': 'Sample Image'
        }
        try:
            self.image = MyImage(image_configs)
        except NoFileError as e:
            e.advice = 'Place a new default image in the correct directory.'
            super().error_window(e)
            return
        self.label_image_title = tk.Label(frame_top)
        self.label_image_title.pack()
        self.label_image = tk.Label(frame_top,
                                    image=self.image.original_tkinter)
        self.label_image.pack()
        self.label_imagemod_title = tk.Label(frame_bottom,
                                             text='Modified Sample Image')
        self.label_imagemod_title.pack()
        self.label_imagemod = tk.Label(frame_bottom,
                                       image=self.image.modified_tkinter)
        self.label_imagemod.pack()

    def setup_grating_default(self, frame: tk.Frame):
        """
        Set up the default gratings on main window.
        """

        self.grating_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_path': 'Images/Sample_Grating.png',
            'grating_name': 'Sample_Grating.png',
            'g_type': 'SawTooth',
            'g_angle': 0,
            'y_max': 255,
            'y_min': 0,
            'period': 100,
            'reverse': 0
        }
        grating_preview_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': 'Images/Sample_Grating.png',
            'name_image': 'Sample_Grating.png'
        }
        try:
            self.label_grating_title = tk.Label(frame, text='Grating Preview')
            self.label_grating_title.pack()
            self.grating_preview = MyImage(grating_preview_configs)
            self.label_grating = tk.Label(
                frame, image=self.grating_preview.original_tkinter)
            self.label_grating.pack()
        except NoFileError as e:
            e.advice = 'Place a new default grating in the correct directory.'
            super().error_window(e)
            return

    def update_list(self):
        self.list_box.delete(0, tk.END)
        for item in self.item_list:
            self.list_box.insert(tk.END,
                                 "%d: %s" % (self.item_list.index(item), item))

    def add_item(self):
        if len(self.item_list) < 4:
            self.collect_raw_data()
            self.modify_and_map()
            self.item_details.update({
                'map_timing': self.map_timing,
                'map_laser_power': self.map_laser_power
            })
            self.grating = MyGrating(self.grating_configs)
            item = ListItem(self.image, self.grating, self.item_details)
            self.item_list.append(item)
            self.update_list()

    def remove_item(self):

        index = self.list_box.curselection()

        if not len(index) == 0:
            index = index[0]

            self.list_box.delete(index)
            del self.item_list[index]

    def clear_items(self):
        self.list_box.delete(0, tk.END)
        self.item_list.clear()

    def fill_item_deatils(self, item):

        # Change grating, images and titles
        self.label_image.configure(image=item.image.original_tkinter)
        self.label_imagemod.configure(image=item.image.modified_tkinter)
        self.label_grating.configure(image=item.grating.grating_preview_tk)
        self.label_image_title.configure(text='%s' % (item.image.name_image))
        self.label_imagemod_title.configure(text='%s, Modified' %
                                            (item.image.name_image))

        # Change info in Selection Details view
        self.image_name_label.config(text="Image Name: %s" %
                                     (item.image.name_image))
        self.grating_type_label.config(text="Grating Type: %s" %
                                       (item.grating.configs['g_type']))
        if item.grating.configs['g_type'] == 'Custom':
            self.rotation_angle_label.config(text="Rotation Angle: N/A")
            self.grating_name_label.config(
                text="Grating Name: %s" %
                (item.grating.configs['grating_name']))
            self.y_min_label.config(text="Y min: N/A")
            self.y_max_label.config(text="Y max: N/A")
            self.period_label.config(text="Period: N/A")
            self.reverse_label.config(text="Reverse: N/A")
        else:
            self.grating_name_label.config(text="Grating Name: N/A")
            self.rotation_angle_label.config(text="Rotation Angle: %s" %
                                             (item.grating.configs['g_angle']))
            self.y_min_label.config(text="Y min: %s" %
                                    (item.grating.configs['y_min']))
            self.y_max_label.config(text="Y max: %s" %
                                    (item.grating.configs['y_max']))
            self.period_label.config(text="Period: %s" %
                                     (item.grating.configs['period']))
            if item.grating.configs['reverse'] == 1:
                result = "Yes"
            else:
                result = "No"
            self.reverse_label.config(text="Reverse: %s" % (result))
        # Change text boxes info
        self.text_exposure.delete(1.0, tk.END)
        self.text_exposure.insert(1.0, item.item_details['strings_exposure'])

        self.text_ignore.delete(1.0, tk.END)
        self.text_ignore.insert(1.0, item.item_details['strings_ignore'])

        self.text_laser.delete(1.0, tk.END)
        self.text_laser.insert(1.0, item.item_details['strings_laser'])

        self.text_grating_color.delete(1.0, tk.END)
        self.text_grating_color.insert(
            1.0, item.item_details['strings_grating_color'])

        super().insert_image_array(item.image, self.text_array)

    def onselect(self, event):
        index = self.list_box.curselection()
        if not len(index) == 0:
            w = event.widget
            index = index[0]
            self.item = self.item_list[index]

            self.fill_item_deatils(self.item)

    def setup_list_view(self, frame: tk.Frame):

        self.list_box = tk.Listbox(frame, width=40)
        self.list_box.grid(row=0, column=0, columnspan=3)

        self.add_button = tk.Button(frame, text='Add', command=self.add_item)
        self.add_button.grid(row=1, column=0)

        self.add_button = tk.Button(frame,
                                    text='Remove',
                                    command=self.remove_item)
        self.add_button.grid(row=1, column=1)

        self.clear_list_button = tk.Button(frame,
                                           text='Clear List',
                                           command=self.clear_items)
        self.clear_list_button.grid(row=1, column=2)

        self.list_select = self.list_box.bind(
            '<<ListboxSelect>>', lambda event: self.onselect(event))

    def grating_select(self, file_path=None):
        """
        Select an image from a file dialogue box and update on screen.
        """

        if file_path is None:
            self.grating_file_path = filedialog.askopenfilename(
                initialdir='Images',
                title="Select Image",
                filetypes=(("png images", "*.png"), ("jpeg images", "*.jpeg"),
                           ("All files", "*.*")))
        else:
            self.grating_file_path = file_path

        self.grating_name = ntpath.basename(self.grating_file_path)
        self.type_var.set('Custom')

##############################################################################
#Choose Image
##############################################################################

    def image_select(self, file_image=None):
        """
        Select an image from a file dialogue box and update on screen.
        """

        if file_image is None:
            file_image = filedialog.askopenfilename(
                initialdir='Images',
                title="Select Image",
                filetypes=(("png images", "*.png"), ("jpeg images", "*.jpeg"),
                           ("All files", "*.*")))
        file_name = ntpath.basename(file_image)
        image_configs = {
            'max_display_x': 200,
            'max_display_y': 200,
            'file_image': file_image,
            'name_image': file_name
        }
        try:
            self.image = MyImage(image_configs)
        except NoFileError as e:
            e.advice = 'Select a different image.'
            super().error_window(e)
            return
        self.label_image.configure(image=self.image.original_tkinter)
        self.label_imagemod.configure(image=self.image.modified_tkinter)
        self.label_image_title.configure(text=self.image.name_image)
        self.label_imagemod_title.configure(text='%s, Modified' %
                                            (self.image.name_image))

##############################################################################
#Data Processing Driver Function
##############################################################################

    def prepare_for_experiment(self):
        """
        Drives the processes to process data, calls other methods.
        """

        #Read equipment data from non-consolidated files and store in variables.
        try:
            self.equipment_configs_motor = self.read_equipment_data('Motor')
        except FileFormatError as e:
            super().error_window(e)
            return
        try:
            self.equipment_configs_shutter = self.read_equipment_data(
                'Shutter')
        except FileFormatError as e:
            super().error_window(e)
            return
        try:
            self.equipment_configs_laser = self.read_equipment_data('Laser')
        except FileFormatError as e:
            super().error_window(e)
            return
        #Get data from main window.
        try:
            self.collect_raw_data()
        except InputError as e:
            e.advice = 'Advice you read to the guide for proper input format.'
            super().error_window(e)
            return
        #Write main window data into an experiment file.
        try:
            self.write_experiment()
        except NoFileError as e:
            super().error_window(e)
            return
        #Store all data into a single file.
        try:
            self.consolidate_files()
        except NoFileError as e:
            super().error_window(e)
            return
        #Further processing of data into mappings, and modifify to images.
        self.modify_and_map()
        #Generate a time estimation
        self.run_time()

##############################################################################
#Data Processing Worker Functions
##############################################################################

    def read_equipment_data(self, equipment: str):
        """
        Get the equipment data from equipment files.
        """

        settings = super().read_file('Equipment/' + equipment +
                                     ' Settings.txt')
        serial = super().read_file('Equipment/' + equipment + ' Serial.txt')
        configs_old = {**serial, **settings}
        configs_new = {}
        try:
            for old_key in configs_old.keys():
                new_key = old_key.replace(equipment, '').replace('Serial',
                                                                 '').lstrip()
                configs_new[new_key] = configs_old[old_key]
            return configs_new
        except Exception as e:
            message = ('An error occurred processing data from ' + equipment +
                       ' files:\n\tEquipment/' + equipment +
                       ' Settings.txt\n\t'
                       'Equipment/' + equipment + ' Motor Serial.txt')
            advice = 'Delete these files.'
            raise FileFormatError(message, e, advice)

    def collect_raw_data(self):
        """
        Pull raw data from window and save in variables.
        """

        #Hologram width.
        try:
            self.hologram_width = 1000 * float(self.entry_width.get().strip())
        except ValueError as e:
            message = 'Hologram width must be a floating point.'
            raise InputError(message, e)
        try:
            self.hologram_height = 1000 * float(
                self.entry_height.get().strip())
        except ValueError as e:
            message = 'Hologram height must be a floating point.'
            raise InputError(message, e)
        #Spot size
        try:
            val = self.entry_spot.get().strip()
            if val != '':
                self.spot_size = float(val)
            else:
                self.spot_size = -1
        except ValueError as e:
            message = 'Spot size must be a floating point.'
            raise InputError(message, e)
        #Pixels Horizontal.
        try:
            val = self.entry_pixel_x.get().strip()
            if val != '':
                self.pixels_x = int(val)
            else:
                self.pixels_x = self.image.original_PIL.width
        except ValueError as e:
            message = 'Horizontal Pixels must be an int.'
            raise InputError(message, e)
        #Pixels Vertical.
        try:
            val = self.entry_pixel_y.get().strip()
            if val != '':
                self.pixels_y = int(val)
            else:
                self.pixels_y = self.image.original_PIL.height
        except ValueError as e:
            message = 'Vertical Pixels must be an int.'
            raise InputError(message, e)

        #Grating Type
        try:
            val = self.type_var.get().strip()
            if val != '':
                self.grating_configs['g_type'] = str(val)
            else:
                self.grating_configs['g_type'] = 'SawTooth'
        except ValueError as e:
            message = 'Grating type must be a string'
            raise InputError(message, e)

        if self.grating_configs['g_type'] == 'Custom':
            self.grating_configs['max_display_x'] = 1920
            self.grating_configs['max_display_x'] = 1152
            self.grating_configs['grating_name'] = self.grating_name
            self.grating_configs['file_path'] = self.grating_file_path
        else:
            #Rotation Angle
            try:
                #pdb.set_trace()
                val = self.entry_angle.get().strip()
                if val != '':
                    self.grating_configs['g_angle'] = int(val)
                else:
                    self.grating_configs['g_angle'] = 0
                    #pdb.set_trace()
            except ValueError as e:
                message = 'Rotation angle must be an int'
                raise InputError(message, e)

            #Ymin
            try:
                val = self.entry_ymin.get().strip()
                if val != '':
                    self.grating_configs['y_min'] = int(val)
                else:
                    self.grating_configs['y_min'] = 0
            except ValueError as e:
                message = 'Y min must be an int'
                raise InputError(message, e)

            #Ymax
            try:
                val = self.entry_ymax.get().strip()
                if val != '':
                    self.grating_configs['y_max'] = int(val)
                else:
                    self.grating_configs['y_max'] = 0
            except ValueError as e:
                message = 'Y max must be an int'
                raise InputError(message, e)

            #Period
            try:
                val = self.entry_period.get().strip()
                if val != '' or val > 0:
                    self.grating_configs['period'] = int(val)
                else:
                    self.grating_configs['period'] = 100
            except ValueError as e:
                message = 'Period width (pixels) must be an int greater than 0'
                raise InputError(message, e)
            self.grating_configs['reverse'] = self.g_reverse.get()

        self.cropping = self.entry_crop.get().strip()
        self.strings_exposure = self.text_exposure.get(1.0, 'end-1c').strip()
        self.strings_ignore = self.text_ignore.get(1.0, 'end-1c').strip()
        self.strings_laser = self.text_laser.get(1.0, 'end-1c').strip()
        self.strings_grating_color = self.text_grating_color.get(
            1.0, 'end-1c').strip()

        self.item_details = {
            'strings_exposure': self.strings_exposure,
            'strings_ignore': self.strings_ignore,
            'strings_laser': self.strings_laser,
            'strings_grating_color': self.strings_grating_color
        }

    def write_experiment(self):
        """
        Get a file from the user and write experiment data there.
        """

        #Get file from user in dialogue box and write to files.
        self.file_experiment = ''
        while self.file_experiment == '':
            self.file_experiment = super().get_save_file()
        #Put all data in a dictionary for writing to file.
        datas = {
            'Hologram Width': self.hologram_width,
            'Hologram Height': self.hologram_height,
            'Spot Size': self.spot_size,
            'Pixels Horizontal': self.pixels_x,
            'Pixels Vertical': self.pixels_y,
            'Cropping': self.cropping,
        }
        index = 1

        for item in self.item_list:
            item_dict = {
                'Strings Exposure %d' % index:
                item.item_details['strings_exposure'],
                'Strings Ignore %d' % index:
                item.item_details['strings_ignore'],
                'Strings Laser %d' % index:
                item.item_details['strings_laser'],
                'Strings Grating Color %d' % index:
                item.item_details['strings_grating_color'],
                'Image File %d' % index:
                item.image.file_image,
                'Grating File %d' % index:
                item.grating.file_path,
                'grating_type %d' % index:
                item.grating.configs['g_type']
            }
            if item_dict['grating_type %d' % index] != 'Custom':
                item_dict.update({
                    'rotation_angle %d' % index:
                    item.grating.configs['g_angle'],
                    'y_min %d' % index:
                    item.grating.configs['y_min'],
                    'y_max %d' % index:
                    item.grating.configs['y_max'],
                    'period %d' % index:
                    item.grating.configs['period'],
                    'reverse %d' % index:
                    item.grating.configs['reverse']
                })
            print(item_dict)
            datas.update(item_dict)
            index += 1

        super().write_file('Experiments/Previous Experiment.txt', datas, 'w')
        super().write_file(self.file_experiment, datas, 'w')

    def consolidate_files(self):
        """
        Write all data files into one file for simple experiment documentation.
        """

        #Get all the data from the settings files and append to main save file.
        data_list = []
        try:
            data_list.append(super().read_file('Equipment/Motor Settings.txt'))
            data_list.append(
                super().read_file('Equipment/Shutter Settings.txt'))
            data_list.append(super().read_file('Equipment/Laser Settings.txt'))
            data_list.append(super().read_file('Equipment/Motor Serial.txt'))
            data_list.append(super().read_file('Equipment/Shutter Serial.txt'))
            data_list.append(super().read_file('Equipment/Laser Serial.txt'))
        except NoFileError as e:
            raise e
        data_dict = {}
        for data in data_list:
            data_dict.update(data)
        super().write_file(self.file_experiment, data_dict, 'a')
        super().write_file('Experiments/Previous Experiment.txt', data_dict,
                           'a')

    def modify_and_map(self):
        """
        Process the data by modifying images, creating mappings, delta x, y.
        """

        #Modify the image and display.
        self.image.downsize_image((self.pixels_x, self.pixels_y))
        self.image.crop_image(self.cropping)
        super().insert_image_array(self.image, self.text_array)
        self.label_imagemod.configure(image=self.image.modified_tkinter)
        #Process other data into mappings of pixel values and delta distances.
        configs_timing = {
            'Input Exposure': self.strings_exposure,
            'Input Ignore': self.strings_ignore,
            'Gradient Range': 256
        }
        configs_laser = {
            'Input Laser': self.strings_laser,
            'Gradient Range': 256
        }
        configs_grating_color = {
            'Input Grating Color': self.strings_grating_color,
            'Gradient Range': 256
        }
        self.map_timing = super().map_timing(configs_timing)
        self.map_laser_power = super().map_laser_power(configs_laser)
        self.delta_x = self.hologram_width / self.pixels_x
        self.delta_y = self.hologram_height / self.pixels_y
        dpi = self.image.modified_PIL.width / (39.37 * self.hologram_width)
        self.label_dpi.configure(text='Image Resolution (dpi): ' +
                                 str(int(dpi)))

    def run_time(self):
        """
        Generate a rough runtime estimation and display on window.
        """

        run_time = 0
        y_after_crop = self.image.modified_PIL.height
        x_after_crop = self.image.modified_PIL.width
        image_as_array = np.transpose(self.image.modified_array)
        #Loop through every potential grating on hologram
        for i in range(0, y_after_crop):
            visited_row = False
            farthest_x = 0
            #Calculate exposure time
            for j in range(0, x_after_crop):
                add = self.map_timing[image_as_array[j][i]]
                if add != 0:
                    visited_row = True
                    farthest_x = j
                    run_time += add
            #Calculate travel time in x direction
            if visited_row == True:
                run_time += ((farthest_x / x_after_crop) *
                             self.hologram_width / .001)
        #Calculate travel time in y direction
        run_time += self.hologram_height / .001
        #Print on Main Window.
        end_time = (
            datetime.now() +
            timedelta(seconds=run_time)).strftime('%H:%M:%S -- %d/%m/%Y')
        self.label_est_time.configure(text='End Time Estimate: ' + end_time)

    def generate_plot(self, item):
        """
        Display a plot of the mappings, if they have been produced.
        """

        try:
            data = {
                'Exposure Time (s)': item.map_timing,
                'Laser Power (mW)': item.map_laser_power
            }
            super().generate_plot(data)
        except Exception:
            pass

##############################################################################
#Run Experiment Driver Function
##############################################################################

    def run_experiment(self):
        """
        Run the experiment by calling methods to handle experiment.
        """

        #Update visuals on the main window.
        start = datetime.now().strftime('%H:%M:%S -- %d/%m/%Y')
        self.label_start_time.configure(text='Start Time: ' + start)
        self.listbox.selection_clear(0, tk.END)
        self.listbox.selection_set(0)
        self.root.update()
        #Establish and initialize the equipment for experiment.
        try:
            print("test")
            #Use simple threading to prevent laggy main window.
            x = threading.Thread(target=self.initialize_equipment)
            x.start()
            while x.is_alive():
                self.root.update()
                time.sleep(.25)
            x.join()
            #pdb.set_trace()
        except EquipmentError as e:
            super().error_window(e)
            super().close_ports(self.equipment)
            return
        except PermissionError as e:
            message = 'Could not establish connection with a serial port.'
            advice = 'Unplug the serial port from computer. Plug back in.'
            super().error_window(EquipmentError(message, e, advice))
            super().close_ports(self.equipment)
            return
        except Exception as e:
            message = 'Unknown error occured initializing equipment.'
            super().error_window(EquipmentError(message, e))
            super().close_ports(self.equipment)
            return
        #Conduct the movement and exposure process.
        try:
            #Use simple threading to prevent laggy main window.
            x = threading.Thread(target=self.movement)
            x.start()
            while x.is_alive():
                self.root.update()
                time.sleep(.25)
            x.join()
            #self.slm.close_window()
            #self.slm_thread.join()

        except UserInterruptError as e:
            super().close_ports(self.equipment)
            super().error_window(e)
            return
        except EquipmentError as e:
            super().close_ports(self.equipment)
            super().error_window(e)
            return
        except Exception as e:
            super().close_ports(self.equipment)
            message = 'Unknown error occured running experiment.'
            super().error_window(UnknownError(message, e))
            return
        #Finally, perform clean up processes.
        self.experiment_finish()

##############################################################################
#Run Experiment Worker Functions
##############################################################################

    def initialize_equipment(self):
        """
        Alter equipment status to initial conditions for experiment.
        """

        #Create the objects amd store in a list.
        self.equipment = []
        #pdb.set_trace()
        self.motor = Motor({
            **self.equipment_configs_motor,
            **{
                'Axes': (1, 2)
            }
        })
        #pdb.set_trace()
        self.equipment.append(self.motor)
        self.shutter = Shutter(self.equipment_configs_shutter)
        self.equipment.append(self.shutter)
        self.laser = Laser(self.equipment_configs_laser)
        self.equipment.append(self.laser)

        #Initialize to start positions.
        self.motor.move_home(1)
        self.motor.move_home(2)
        self.laser.turn_on_off(True)

    def create_SLM_window(self):
        self.slm = SLM_window(self.root)

    def movement(self):
        """
        Conduct the physical movement of machinery and such.
        """
        # Create SLM Window
        self.create_SLM_window()

        #Move through the image array, expose according to mappings.
        prev_pix = None
        prev_powr = None
        y_after_crop = self.image.modified_PIL.height
        x_after_crop = self.image.modified_PIL.width

        for item in self.item_list:
            item.image_as_array = np.transpose(item.image.modified_array)

        for i in range(0, y_after_crop):
            on_this_row = False
            for j in range(0, x_after_crop):
                self.check_pause_abort()
                cur_item = self.item_list[self.grating_map(j, i)]
                pix = cur_item.image_as_array[j][i]
                e_time = cur_item.map_timing[pix]
                if e_time < 0:
                    e_time = 0
                powr = cur_item.map_laser_power[pix]
                #Enter conditional if the current pixel should be exposed.
                if not super().compare_floats(e_time, 0):
                    self.slm.display(cur_item.grating.grating_tk)
                    self.update_progress(pix, e_time, powr, i, j)
                    #Change the laser's power if the pixel value has changed.
                    if prev_pix is not None and prev_powr is not None:
                        if not super().compare_floats(powr, prev_powr):
                            self.laser.change_power(powr)
                    #Move motors to the correct positions and open shutter.
                    if not on_this_row:
                        self.motor.move_absolute(2, i * self.delta_y * 1000)
                        on_this_row = True
                    self.motor.move_absolute(1, j * self.delta_x * 1000)
                    self.shutter.toggle(e_time)
                    #Update previous pixel info to current pixel info
                    prev_pix = pix
                    prev_powr = powr

    def check_pause_abort(self):
        """
        Handle a pause or an abort operation during movement.
        """

        selection = self.listbox.curselection()
        if 0 in selection:
            return
        if 1 in selection:
            message = 'User paused the experiment.'
            advice = 'Click Run in listbox to continue.'
            super().error_window(UserInterruptError(message, None, advice))
            while 1 in selection:
                selection = self.listbox.curselection()
                time.sleep(.5)
        if 2 in selection:
            message = 'User aborted the experiment.'
            advice = 'Click Run Experiment Button to restart.'
            super().error_window(UserInterruptError(message, None, advice))
            raise UserInterruptError(message, None, advice)

    def update_progress(self, pix: int, time: float, powr: float, i: int,
                        j: int):
        """
        Update the exposure information for the current pixel on main window.
        """

        self.label_position.configure(text='Location (x,y) : (' + str(j) +
                                      ',' + str(i) + ')')
        self.label_details.configure(text='Details (pxl,pwr,time) : (' +
                                     str(pix) + ',' + str(powr) + ',' +
                                     str(time) + ')')

    def experiment_finish(self):
        """
        Conduct final processes at end of experiment.
        """

        super().close_ports(self.equipment)
        end = datetime.now().strftime('%H:%M:%S -- %d/%m/%Y')
        self.label_end_time.configure(text='True Experiment End Time: ' + end)
        #screenshot = super().screenshot()
        #screenshot.save(self.file_experiment.replace('.txt','.png'))

##############################################################################
#Open Prior Experiments
##############################################################################

    def open_experiment(self, file_name: str = None):
        """
        Open an experiment from file and populate wigits with data.
        """

        #Determine which experiment to open and get that data.
        if file_name is None:
            file_name = filedialog.askopenfilename(
                title="Open Experiment",
                filetypes=(("txt files", "*.txt"), ("All files", "*.*")),
                initialdir='Experiments')
        try:
            datas = super().read_file(file_name)
        except NoFileError as e:
            super().error_window(e)
            return
        #Clear the input in main window.
        self.clear_experiment()
        #Populate the main window with data from file.
        self.populate_main(datas)
        #Overwrite the settings and serial files with data.
        self.overwrite_settings_serials(datas)

    def clear_experiment(self):
        """
        Clear all the input wigits on the main window before open experiment.
        """

        wigits = [
            self.entry_pixel_x, self.entry_pixel_y, self.entry_spot,
            self.entry_width, self.entry_height, self.entry_crop,
            self.entry_angle, self.entry_ymin, self.entry_ymax,
            self.entry_period, self.text_exposure, self.text_ignore,
            self.text_laser, self.text_grating_color
        ]
        self.clear_items()
        self.g_reverse.set('0')
        super().clear_wigits(wigits)

    def populate_main(self, datas: dict):
        """
        Fill wigits with datas from file.
        """

        #If data is not present, do not fill.
        if 'Hologram Width' in datas:
            self.entry_width.insert(1, datas['Hologram Width'])
        if 'Hologram Height' in datas:
            self.entry_height.insert(1, datas['Hologram Height'])
        if 'Spot Size' in datas:
            self.entry_spot.insert(1, datas['Spot Size'])
        if 'Pixels Horizontal' in datas:
            self.entry_pixel_x.insert(1, datas['Pixels Horizontal'])
        if 'Pixels Vertical' in datas:
            self.entry_pixel_y.insert(1, datas['Pixels Vertical'])
        if 'Cropping' in datas:
            self.entry_crop.insert(1, datas['Cropping'])

        for i in range(1, 5):

            if 'Strings Exposure %d' % (i) in datas:
                self.text_exposure.delete(1.0, tk.END)
                self.text_exposure.insert(1.0,
                                          datas['Strings Exposure %d' % (i)])
            if 'Strings Ignore %d' % (i) in datas:
                self.text_ignore.delete(1.0, tk.END)
                self.text_ignore.insert(1.0, datas['Strings Ignore %d' % (i)])
            if 'Strings Laser %d' % (i) in datas:
                self.text_laser.delete(1.0, tk.END)
                self.text_laser.insert(1.0, datas['Strings Laser %d' % (i)])
            if 'Strings Grating Color %d' % (i) in datas:
                self.text_grating_color.delete(1.0, tk.END)
                self.text_grating_color.insert(
                    1.0, datas['Strings Grating Color %d' % (i)])
            if 'Image File %d' % (i) in datas:
                self.image_select(datas['Image File %d' % (i)])
            if 'Grating File %d' % (i) in datas:
                self.grating_select(datas['Grating File %d' % (i)])
            if 'grating_type %d' % (i) in datas:
                self.type_var.set(datas['grating_type %d' % (i)])
            if 'rotation_angle %d' % (i) in datas:
                self.entry_angle.delete(0, tk.END)
                self.entry_angle.insert(0, datas['rotation_angle %d' % (i)])
            if 'y_min %d' % (i) in datas:
                self.entry_ymin.delete(0, tk.END)
                self.entry_ymin.insert(0, datas['y_min %d' % (i)])
            if 'y_max %d' % (i) in datas:
                self.entry_ymax.delete(0, tk.END)
                self.entry_ymax.insert(0, datas['y_max %d' % (i)])
            if 'period %d' % (i) in datas:
                self.entry_period.delete(0, tk.END)
                self.entry_period.insert(0, datas['period %d' % (i)])
            if 'reverse %d' % (i) in datas:
                self.g_reverse.set(datas['reverse %d' % (i)])
            self.add_item()

    def overwrite_settings_serials(self, datas):
        """
        Overwrite the equipment settings files in the case of loading experiment.
        """

        #Dictionaries for use within each special file.
        motor_settings = {}
        shutter_settings = {}
        laser_settings = {}
        motor_serials = {}
        shutter_serials = {}
        laser_serials = {}
        #Move through large data and organize according to headers.
        for key in datas.keys():
            KEY = key.upper()
            has_SERIAL = True if 'SERIAL' in KEY else False
            if 'MOTOR' in KEY:
                if has_SERIAL:
                    motor_serials[key] = datas[key]
                else:
                    motor_settings[key] = datas[key]
            if 'SHUTTER' in KEY:
                if has_SERIAL:
                    shutter_serials[key] = datas[key]
                else:
                    shutter_settings[key] = datas[key]
            if 'LASER' in KEY and 'STRINGS LASER' != KEY:
                if has_SERIAL:
                    laser_serials[key] = datas[key]
                else:
                    laser_settings[key] = datas[key]
        #Overwite files with new information.
        super().write_file('Equipment/Motor Settings.txt', motor_settings)
        super().write_file('Equipment/Shutter Settings.txt', shutter_settings)
        super().write_file('Equipment/Laser Settings.txt', laser_settings)
        super().write_file('Equipment/Motor Serial.txt', motor_serials)
        super().write_file('Equipment/Shutter Serial.txt', shutter_serials)
        super().write_file('Equipment/Laser Serial.txt', laser_serials)