def __init__(self): try: # Initialize VISA resource manager, connect to Matisse and wavemeter, clear any errors. self._instrument = ResourceManager().open_resource( cfg.get(cfg.MATISSE_DEVICE_ID)) self.target_wavelength = None self._stabilization_thread = None self._lock_correction_thread = None self._plotting_processes = [] self.exit_flag = False self._scan_attempts = 0 self._force_large_scan = True self._restart_set_wavelength = False self.is_setting_wavelength = False self.is_scanning_bifi = False self.is_scanning_thin_etalon = False self.stabilization_auto_corrections = 0 self.query('ERROR:CLEAR') # start with a clean slate self.query('MOTORBIREFRINGENT:CLEAR') self.query('MOTORTHINETALON:CLEAR') self._wavemeter = WaveMaster(cfg.get(cfg.WAVEMETER_PORT)) except VisaIOError as ioerr: raise IOError( "Can't reach Matisse. Make sure it's on and connected via USB." ) from ioerr
def set_wavelength_dialog(self, checked): """ Open a dialog to set the wavelength of the Matisse. If the difference in wavleength is greater than or equal to the wavelength change threshold, display a warning to confirm the change. """ current_wavelength = self.matisse.target_wavelength if current_wavelength is None: current_wavelength = self.matisse.wavemeter_wavelength() target_wavelength, success = QInputDialog.getDouble( self.window, 'Set Wavelength', 'Wavelength (nm): ', current_wavelength, decimals=3, min=cfg.get(cfg.WAVELENGTH_LOWER_LIMIT), max=cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) if success: if abs(current_wavelength - target_wavelength ) >= ControlApplication.CONFIRM_WAVELENGTH_CHANGE_THRESHOLD: answer = QMessageBox.warning( self.window, 'Large Wavelength Change', f"The desired wavelength, {target_wavelength} nm, is more than " f"{ControlApplication.CONFIRM_WAVELENGTH_CHANGE_THRESHOLD} nm " 'away from the current wavelength. Are you sure?', QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.No) if answer == QMessageBox.No: return print(f"Setting wavelength to {target_wavelength} nm...") self.run_matisse_task(self.matisse.set_wavelength, target_wavelength)
def setup_form(self): form_layout = QFormLayout() data_file_selection_layout = QHBoxLayout() self.data_file_button = QPushButton('Select File') self.data_file_label = QLabel() data_file_selection_layout.addWidget(self.data_file_button) data_file_selection_layout.addWidget(self.data_file_label) form_layout.addRow('Existing Data: ', data_file_selection_layout) line = QFrame() line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) form_layout.addRow(line) self.exposure_time_field = QDoubleSpinBox() self.exposure_time_field.setMinimum(0) self.exposure_time_field.setDecimals(4) form_layout.addRow('Exposure time (s): ', self.exposure_time_field) self.layout.addLayout(form_layout) # We need to make sure the Andor libraries are loaded to access the spectrometer ple.PLE.load_andor_libs() self.center_wavelength_field = QDoubleSpinBox() self.center_wavelength_field.setMinimum(cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.center_wavelength_field.setMaximum(cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.center_wavelength_field.setValue(ple.shamrock.get_center_wavelength()) form_layout.addRow('Center wavelength (nm): ', self.center_wavelength_field) self.grating_grooves_field = QComboBox() for groove_num in ple.shamrock.gratings.keys(): self.grating_grooves_field.addItem(f"{groove_num}", ple.shamrock.gratings[groove_num]) self.grating_grooves_field.setCurrentText(str(ple.shamrock.get_grating_grooves())) form_layout.addRow('Grating grooves: ', self.grating_grooves_field)
def run(self): while True: if self.messages.qsize() == 0: try: bifi_pos = self.matisse.query('MOTBI:POS?', numeric_result=True) thin_eta_pos = self.matisse.query('MOTTE:POS?', numeric_result=True) refcell_pos, pz_eta_pos, slow_pz_pos = self.matisse.get_stabilizing_piezo_positions( ) is_stabilizing = self.matisse.is_stabilizing() is_scanning = self.matisse.is_scanning() is_locked = self.matisse.laser_locked() wavemeter_value = self.matisse.wavemeter_raw_value() bifi_pos_text = f"BiFi:{bifi_pos}" thin_eta_pos_text = f"Thin Eta:{thin_eta_pos}" pz_eta_pos_text = f"Pz Eta:{pz_eta_pos:.3f}" slow_pz_pos_text = f"Slow Pz:{slow_pz_pos:.3f}" refcell_pos_text = f"RefCell:{refcell_pos:.3f}" stabilizing_text = f"Stabilize:{green_text('ON') if is_stabilizing else red_text('OFF')}" scanning_text = f"Scanning:{green_text('ON') if is_scanning else red_text('OFF')}" locked_text = f"{green_text('LOCKED') if is_locked else red_text('NO LOCK')}" wavemeter_text = f"Wavemeter:{wavemeter_value}" limit_offset = cfg.get(cfg.COMPONENT_LIMIT_OFFSET) refcell_at_limit = not matisse.REFERENCE_CELL_LOWER_LIMIT + limit_offset < refcell_pos < matisse.REFERENCE_CELL_UPPER_LIMIT - limit_offset slow_pz_at_limit = not matisse.SLOW_PIEZO_LOWER_LIMIT + limit_offset < slow_pz_pos < matisse.SLOW_PIEZO_UPPER_LIMIT - limit_offset pz_eta_at_limit = not matisse.PIEZO_ETALON_LOWER_LIMIT + limit_offset < pz_eta_pos < matisse.PIEZO_ETALON_UPPER_LIMIT - limit_offset warn_offset = limit_offset * 2 refcell_near_limit = not matisse.REFERENCE_CELL_LOWER_LIMIT + warn_offset < refcell_pos < matisse.REFERENCE_CELL_UPPER_LIMIT - warn_offset slow_pz_near_limit = not matisse.SLOW_PIEZO_LOWER_LIMIT + warn_offset < slow_pz_pos < matisse.SLOW_PIEZO_UPPER_LIMIT - warn_offset pz_eta_near_limit = not matisse.PIEZO_ETALON_LOWER_LIMIT + warn_offset < pz_eta_pos < matisse.PIEZO_ETALON_UPPER_LIMIT - warn_offset if refcell_at_limit: refcell_pos_text = red_text(refcell_pos_text) elif refcell_near_limit: refcell_pos_text = orange_text(refcell_pos_text) if slow_pz_at_limit: slow_pz_pos_text = red_text(slow_pz_pos_text) elif slow_pz_near_limit: slow_pz_pos_text = orange_text(slow_pz_pos_text) if pz_eta_at_limit: pz_eta_pos_text = red_text(pz_eta_pos_text) elif pz_eta_near_limit: pz_eta_pos_text = orange_text(pz_eta_pos_text) status = f"{bifi_pos_text} | {thin_eta_pos_text} | {pz_eta_pos_text} | {slow_pz_pos_text} | {refcell_pos_text} | {stabilizing_text} | {scanning_text} | {locked_text} | {wavemeter_text}" except Exception: status = red_text( 'Error reading system status. Please restart if this issue persists.' ) self.status_read.emit(status) time.sleep(cfg.get(cfg.STATUS_MONITOR_DELAY)) else: break
def run(self): with ControlLoopsOn(self.matisse): self.timer.start() while True: if self.messages.qsize() == 0: if self.matisse.fast_piezo_locked(): self.timer.cancel() if self.matisse.is_any_limit_reached(): print( 'WARNING: A component has hit a limit while the laser is locked. ' 'Attempting automatic corrections.') if cfg.get(cfg.REPORT_EVENTS): current_wavelength = self.matisse.wavemeter_wavelength( ) log_event( EventType.LOCK_CORRECTION, self.matisse, current_wavelength, 'component hit a limit while laser was locked' ) self.matisse.reset_stabilization_piezos() else: self.restart_timer() if self.matisse.is_any_limit_reached(): print( 'WARNING: A component has hit a limit before the laser could lock. ' 'Stopping control loops. ' + LockCorrectionThread.UNABLE_TO_LOCK_MESSAGE) self.timer.cancel() break time.sleep(1) else: self.timer.cancel() break
def set_recommended_fast_piezo_setpoint(self): """ Analyze the data from the reference cell transmission spectrum, and set the fast piezo setpoint to a point about halfway between the min and max points on the spectrum. The recommended value is determined by averaging a number of scans given by cfg.FAST_PZ_SETPOINT_NUM_SCANS. If this doesn't help to lock the laser, use the 'RefCell Properties Measurement' feature in Matisse Commander to set the fast piezo setpoint instead. This stops auto-stabilization or any reference cell scans currently running and temporarily enables all component control loops. """ if self.is_stabilizing(): self.stabilize_off() self.stop_scan() with ControlLoopsOn(self): num_scans = cfg.get(cfg.FAST_PZ_SETPOINT_NUM_SCANS) total = 0 for i in range(0, num_scans): positions, values = self.get_reference_cell_transmission_spectrum( ) setpoint = (np.max(values) + np.min(values)) / 2 total += setpoint recommended_setpoint = total / num_scans print(f"Setting fast piezo setpoint to {recommended_setpoint}") self.query(f"FASTPIEZO:CONTROLSETPOINT {recommended_setpoint}")
def lock_at_wavelength(self, wavelength: float): """Try to lock the Matisse at a given wavelength, waiting to return until we're within a small tolerance.""" tolerance = 10**-cfg.get(cfg.WAVEMETER_PRECISION) self.matisse.set_wavelength(wavelength) while abs(wavelength - self.matisse.wavemeter_wavelength()) >= tolerance or \ (self.matisse.is_setting_wavelength or self.matisse.is_scanning_bifi or self.matisse.is_scanning_thin_etalon): if self.ple_exit_flag: break time.sleep(3)
def do_stabilization_correction(self, wavelength): """Reset the stabilization piezos and optionally log the correction event.""" print('WARNING: A component has hit a limit while adjusting the RefCell. Attempting automatic corrections.') self._matisse.stop_scan() if cfg.get(cfg.REPORT_EVENTS): log_event(EventType.STABILIZATION_CORRECTION, self._matisse, wavelength, 'component hit a limit while auto-stabilization was on') self._matisse.reset_stabilization_piezos() self._matisse.stabilization_auto_corrections += 1
def set_bifi_wavelength(self, value: float): """ Set the birefringent filter motor to the approximate position corresponding to the given wavelength. This position is determined by the Matisse. Parameters ---------- value : float the desired wavelength """ assert cfg.get(cfg.WAVELENGTH_LOWER_LIMIT) < value < cfg.get(cfg.WAVELENGTH_UPPER_LIMIT), \ 'Target wavelength out of range.' # Wait for motor to be ready to accept commands while not self.bifi_motor_status() == MOTOR_STATUS_IDLE: pass self.query(f"MOTBI:WAVELENGTH {value}") # Wait for motor to finish movement while not self.bifi_motor_status() == MOTOR_STATUS_IDLE: pass
def run(self): """ Try to keep the measured wavelength within the configured tolerance by scanning the reference cell. If a larger drift in wavelength occurs, we might have fallen into a dip on the power diode curve. To correct this, a small BiFi scan and a small thin etalon scan will be performed. Exit if anything is pushed to the message queue. """ while True: if self.messages.qsize() == 0: current_wavelength = self._matisse.wavemeter_wavelength() drift = round(current_wavelength - self._matisse.target_wavelength, cfg.get(cfg.WAVEMETER_PRECISION)) # TODO: This threshold is large, maybe add another config option for this condition if abs(drift) > cfg.get(cfg.LARGE_WAVELENGTH_DRIFT): # TODO: Consider logging this event to the event report print(f"WARNING: Wavelength drifted by {drift} nm during stabilization. Making corrections.") self._matisse.stop_scan() if self._matisse.is_lock_correction_on(): self._matisse.stop_laser_lock_correction() # TODO: Skip BiFi scan if drift is small enough, kind of like in Matisse.set_wavelength self._matisse.birefringent_filter_scan(scan_range=cfg.get(cfg.BIFI_SCAN_RANGE_SMALL)) self._matisse.thin_etalon_scan(scan_range=cfg.get(cfg.THIN_ETA_SCAN_RANGE_SMALL)) self._matisse.start_laser_lock_correction() elif abs(drift) > cfg.get(cfg.STABILIZATION_TOLERANCE): if drift > 0: # measured wavelength is too high print(f"Wavelength too high, decreasing. Drift is {drift} nm. Refcell is at {self._matisse.query('SCAN:NOW?', numeric_result=True)}") if not self._matisse.is_any_limit_reached(): if cfg.get(cfg.REPORT_EVENTS): log_event(EventType.WAVELENGTH_DRIFT, self._matisse, current_wavelength, f"wavelength drifted by {drift} nm") self._matisse.start_scan(matisse.SCAN_MODE_DOWN) else: self.do_stabilization_correction(current_wavelength) else: # measured wavelength is too low print(f"Wavelength too low, increasing. Drift is {drift} nm. Refcell is at {self._matisse.query('SCAN:NOW?', numeric_result=True)}") if not self._matisse.is_any_limit_reached(): if cfg.get(cfg.REPORT_EVENTS): log_event(EventType.WAVELENGTH_DRIFT, self._matisse, current_wavelength, f"wavelength drifted by {drift} nm") self._matisse.start_scan(matisse.SCAN_MODE_UP) else: self.do_stabilization_correction(current_wavelength) else: self._matisse.stop_scan() # print(f"Within tolerance. Drift is {drift}") time.sleep(cfg.get(cfg.STABILIZATION_DELAY)) else: self._matisse.stop_scan() break
def setup(self, exposure_time: float, acquisition_mode=ACQ_MODE_SINGLE, readout_mode=READ_MODE_FVB, temperature=-70, cool_down=True): """ Perform setup procedures on CCD, like cooling down to a given temperature and setting acquisition parameters. Parameters ---------- exposure_time the desired exposure time at which to configure the CCD acquisition_mode the desired acquisition mode at which to configure the CCD (default is accumulate) readout_mode the desired readout mode at which to configure the CCD (default is FVB) temperature the desired temperature in degrees centigrade at which to configure the CCD (default is -70) cool_down whether to cool down the CCD at all (sometimes we don't care, like when taking a single acquisition) """ self.exit_flag = False if cool_down: min_temp, max_temp = c_int(), c_int() self.lib.GetTemperatureRange(pointer(min_temp), pointer(max_temp)) min_temp, max_temp = min_temp.value, max_temp.value assert min_temp < temperature < max_temp, f"Temperature must be set between {min_temp} and {max_temp}" self.lib.SetTemperature(c_int(temperature)) self.lib.CoolerON() # Cooler stops when temp is within 3 degrees of target, so wait until it's close # CCD normally takes a few minutes to fully cool down while not self.temperature_ok: if self.exit_flag: return current_temp = self.get_temperature() print( f"Cooling CCD. Current temperature is {round(current_temp, 2)} °C" ) self.temperature_ok = current_temp < temperature + cfg.get( cfg.PLE_TEMPERATURE_TOLERANCE) time.sleep(10) print('Configuring acquisition parameters.') self.lib.SetAcquisitionMode(c_int(acquisition_mode)) self.lib.SetReadMode(c_int(readout_mode)) self.lib.SetVSSpeed(c_int(1)) self.lib.SetTriggerMode(c_int(TRIGGER_MODE_INTERNAL)) self.lib.SetExposureTime(c_float(exposure_time)) print('CCD ready for acquisition.')
def __init__(self): try: self.lib = load_lib(CCD.LIBRARY_NAME) self.lib.Initialize() self.lib.SetTemperature(c_int(cfg.get(cfg.PLE_TARGET_TEMPERATURE))) self.lib.CoolerON() self.temperature_ok = False self.exit_flag = False num_cameras = c_long() self.lib.GetAvailableCameras(pointer(num_cameras)) assert num_cameras.value > 0, 'No CCD camera found.' except OSError as err: raise RuntimeError('Unable to initialize Andor CCD API.') from err
def get_reference_cell_transmission_spectrum(self): """ Scan the reference cell from cfg.FAST_PZ_SETPOINT_SCAN_LOWER_LIMIT to cfg.FAST_PZ_SETPOINT_SCAN_UPPER_LIMIT, measuring the input to the fast piezo for each position. This creates a curve that represents the transmission spectrum of the reference cell. Returns ------- (ndarray, ndarray) the positions and input values measured during the scan """ positions = np.linspace(cfg.get(cfg.FAST_PZ_SETPOINT_SCAN_LOWER_LIMIT), cfg.get(cfg.FAST_PZ_SETPOINT_SCAN_UPPER_LIMIT), cfg.get(cfg.FAST_PZ_SETPOINT_NUM_POINTS)) values = np.array([]) old_refcell_pos = self.query(f"SCAN:NOW?", numeric_result=True) for pos in positions: self.query(f"SCAN:NOW {pos}") values = np.append( values, self.query('FASTPIEZO:INPUT?', numeric_result=True)) self.query(f"SCAN:NOW {old_refcell_pos}") return positions, values
def is_any_limit_reached(self): """ Returns ------- bool whether any of the stabilization piezos are very close to their limits """ refcell_pos, pz_eta_pos, slow_pz_pos = self.get_stabilizing_piezo_positions( ) offset = cfg.get(cfg.COMPONENT_LIMIT_OFFSET) return not (REFERENCE_CELL_LOWER_LIMIT + offset < refcell_pos < REFERENCE_CELL_UPPER_LIMIT - offset and SLOW_PIEZO_LOWER_LIMIT + offset < slow_pz_pos < SLOW_PIEZO_UPPER_LIMIT - offset and PIEZO_ETALON_LOWER_LIMIT + offset < pz_eta_pos < PIEZO_ETALON_UPPER_LIMIT - offset)
def setup_form(self): form_layout = QFormLayout() self.analysis_name_field = QLineEdit() form_layout.addRow('Analysis name: ', self.analysis_name_field) data_file_selection_layout = QHBoxLayout() self.data_file_button = QPushButton('Select File') self.data_file_label = QLabel() data_file_selection_layout.addWidget(self.data_file_button) data_file_selection_layout.addWidget(self.data_file_label) form_layout.addRow('PLE Data: ', data_file_selection_layout) self.integration_start_field = QDoubleSpinBox() self.integration_start_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.integration_start_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.integration_start_field.setDecimals( cfg.get(cfg.WAVEMETER_PRECISION)) self.integration_start_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) form_layout.addRow('Integration start (nm): ', self.integration_start_field) self.integration_end_field = QDoubleSpinBox() self.integration_end_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.integration_end_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.integration_end_field.setDecimals(cfg.get( cfg.WAVEMETER_PRECISION)) self.integration_end_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) form_layout.addRow('Integration end (nm): ', self.integration_end_field) bkgd_file_selection_layout = QHBoxLayout() self.bkgd_file_button = QPushButton('Select File') self.bkgd_file_label = QLabel() bkgd_file_selection_layout.addWidget(self.bkgd_file_button) bkgd_file_selection_layout.addWidget(self.bkgd_file_label) form_layout.addRow('Subtract Background: ', bkgd_file_selection_layout) self.layout.addLayout(form_layout)
def start_laser_lock_correction(self): """ Try to lock the laser, and make automatic corrections to the stabilization piezos if needed. If there is no target wavelength set, lock at the current wavelength. Starts a `matisse_controller.matisse.lock_correction_thread.LockCorrectionThread` as a daemon for this purpose. Call `Matisse.stop_laser_lock_correction` to disable lock. """ if self.is_lock_correction_on(): print('WARNING: Lock correction is already running.') else: print('Starting laser lock.') self._lock_correction_thread = LockCorrectionThread( self, cfg.get(cfg.LOCKING_TIMEOUT), queue.Queue(), daemon=True) if self.target_wavelength is None: self.target_wavelength = self.wavemeter_wavelength() self._lock_correction_thread.start()
def __init__(self, matisse, messages: Queue, *args, **kwargs): """ Parameters ---------- matisse : matisse_controller.matisse.matisse.Matisse an instance of Matisse to be provided to the StatusUpdateThread messages a message queue to be given to the StatusUpdateThread *args args to pass to `QLabel.__init__` **kwargs kwargs to pass to `QLabel.__init__` """ super().__init__(*args, **kwargs) self.messages = messages self.setFont( QFont('StyleNormal', cfg.get(cfg.STATUS_MONITOR_FONT_SIZE))) self.update_thread = StatusUpdateThread(matisse, messages, parent=self) self.update_thread.status_read.connect(self.setText) self.update_thread.start()
def reset_stabilization_piezos(self): """ Reset the slow piezo to the center, and the RefCell and piezo etalon according to the following rules: - If RefCell is at upper limit, piezo etalon is likely near lower limit - If wavelength is still too low, move RefCell down lower than usual and piezo etalon higher than usual - If RefCell is at lower limit, piezo etalon is likely near upper limit - If wavelength is still too high, move RefCell up higher than usual and piezo etalon lower than usual - Else, move RefCell and piezo etalon to their center positions. A target wavelength must already be set in order to run this method. """ current_refcell_pos, current_pz_eta_pos, current_slow_pz_pos = self.get_stabilizing_piezo_positions( ) current_wavelength = self.wavemeter_wavelength() offset = cfg.get(cfg.COMPONENT_LIMIT_OFFSET) if (current_refcell_pos > REFERENCE_CELL_UPPER_LIMIT - offset and current_wavelength < self.target_wavelength): self.query(f"SCAN:NOW {cfg.get(cfg.REFCELL_LOWER_CORRECTION_POS)}") self.query( f"PIEZOETALON:BASELINE {cfg.get(cfg.PIEZO_ETA_UPPER_CORRECTION_POS)}" ) elif (current_refcell_pos < REFERENCE_CELL_LOWER_LIMIT + offset and current_wavelength > self.target_wavelength): self.query(f"SCAN:NOW {cfg.get(cfg.REFCELL_UPPER_CORRECTION_POS)}") self.query( f"PIEZOETALON:BASELINE {cfg.get(cfg.PIEZO_ETA_LOWER_CORRECTION_POS)}" ) else: self.query(f"SCAN:NOW {cfg.get(cfg.REFCELL_MID_CORRECTION_POS)}") self.query( f"PIEZOETALON:BASELINE {cfg.get(cfg.PIEZO_ETA_MID_CORRECTION_POS)}" ) self.query( f"SLOWPIEZO:NOW {cfg.get(cfg.SLOW_PIEZO_MID_CORRECTION_POS)}")
def start_ple_scan(self, scan_name: str, scan_location: str, initial_wavelength: float, final_wavelength: float, step: float, center_wavelength: float, grating_grooves: int, *ccd_args, plot_analysis=False, integration_start=None, integration_end=None, **ccd_kwargs): """ Perform a PLE scan using the Andor Shamrock spectrometer and Newton CCD. Generates text files with data from each spectrum taken during the scan, and pickles the Python dictionary of all data into {scan_name}.pickle. Parameters ---------- scan_name a unique name to give the PLE measurement, which will be included in the name of all the data files scan_location the name of a folder to contain all relevant scan data initial_wavelength starting wavelength for the PLE scan final_wavelength ending wavelength for the PLE scan step the desired change in wavelength between each individual scan center_wavelength the wavelength at which to set the spectrometer grating_grooves the number of grooves to use for the spectrometer grating plot_analysis whether to plot the PLE analysis in real time integration_start : float the wavelength at which to start integration for real-time analysis plotting integration_end : float the wavelength at which to stop integration for real-time analysis plotting *ccd_args args to pass to `matisse_controller.shamrock_ple.ccd.CCD.setup` **ccd_kwargs kwargs to pass to `matisse_controller.shamrock_ple.ccd.CCD.setup` """ self.ple_exit_flag = False if not scan_name: print('WARNING: Name of PLE scan is required.') return if not scan_location: print('WARNING: Location of PLE scan is required.') return data_file_name = os.path.join(scan_location, f"{scan_name}.pickle") if os.path.exists(data_file_name): print( f"WARNING: A PLE scan has already been run for '{scan_name}'. Choose a new name and try again." ) return PLE.load_andor_libs() print( f"Setting spectrometer grating to {grating_grooves} grvs and center wavelength to {center_wavelength}..." ) shamrock.set_grating_grooves(grating_grooves) shamrock.set_center_wavelength(center_wavelength) if self.ple_exit_flag: return ccd.setup(*ccd_args, **ccd_kwargs) wavelengths = np.append( np.arange(initial_wavelength, final_wavelength, step), final_wavelength) wavelength_range = abs( round(final_wavelength - initial_wavelength, cfg.get(cfg.WAVEMETER_PRECISION))) counter = 1 file_name = '' pl_pipe_in, pl_pipe_out = Pipe() pl_plot_process = SpectrumPlotProcess(pipe=pl_pipe_out, daemon=True) self.spectrum_plot_processes.append(pl_plot_process) pl_plot_process.start() if plot_analysis: analysis_pipe_in, analysis_pipe_out = Pipe() analysis_plot_process = PLEAnalysisPlotProcess( pipe=analysis_pipe_out, daemon=True) self.analysis_plot_processes.append(analysis_plot_process) analysis_plot_process.start() data = { 'grating_grooves': grating_grooves, 'center_wavelength': center_wavelength } for wavelength in wavelengths: print(f"Starting acquisition {counter}/{len(wavelengths)}.") wavelength = round(float(wavelength), cfg.get(cfg.WAVEMETER_PRECISION)) self.lock_at_wavelength(wavelength) if self.ple_exit_flag: print('Received exit signal, saving PLE data.') break acquisition_data = ccd.take_acquisition( ) # FVB mode bins into each column, so this only grabs points along width file_name = os.path.join( scan_location, f"{str(counter).zfill(3)}_{scan_name}_{wavelength}nm" f"_StepSize_{step}nm_Range_{wavelength_range}nm.txt") np.savetxt(file_name, acquisition_data) acq_wavelengths = self.pixels_to_wavelengths( range(len(acquisition_data)), center_wavelength, grating_grooves) pl_pipe_in.send((acq_wavelengths, acquisition_data)) if plot_analysis: start_pixel, end_pixel = self.find_integration_endpoints( integration_start, integration_end, center_wavelength, grating_grooves) analysis_pipe_in.send( (wavelength, sum(acquisition_data[start_pixel:end_pixel]))) data[wavelength] = acquisition_data counter += 1 pl_pipe_in.send(None) if file_name: self.plot_single_acquisition(center_wavelength, grating_grooves, data_file=file_name) with open(data_file_name, 'wb') as data_file: pickle.dump(data, data_file, pickle.HIGHEST_PROTOCOL) print('Finished PLE scan.')
def set_current_values_from_config(self): self.matisse_device_id_field.setText(cfg.get(cfg.MATISSE_DEVICE_ID)) self.wavemeter_port_field.setText(cfg.get(cfg.WAVEMETER_PORT)) self.wavemeter_precision_field.setValue( cfg.get(cfg.WAVEMETER_PRECISION)) self.wavemeter_measurement_delay_field.setValue( cfg.get(cfg.WAVEMETER_MEASUREMENT_DELAY)) self.status_monitor_delay_field.setValue( cfg.get(cfg.STATUS_MONITOR_DELAY)) self.status_monitor_font_size_field.setValue( cfg.get(cfg.STATUS_MONITOR_FONT_SIZE)) self.component_limit_offset_field.setValue( cfg.get(cfg.COMPONENT_LIMIT_OFFSET)) self.wavelength_lower_limit_field.setValue( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.wavelength_upper_limit_field.setValue( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.bifi_reset_pos_field.setValue(cfg.get(cfg.BIFI_RESET_POS)) self.thin_eta_reset_pos_field.setValue(cfg.get(cfg.THIN_ETA_RESET_POS)) self.thin_eta_rand_range_field.setValue( cfg.get(cfg.THIN_ETA_RAND_RANGE)) self.report_events_field.setChecked(cfg.get(cfg.REPORT_EVENTS)) self.scan_limit_field.setValue(cfg.get(cfg.SCAN_LIMIT)) self.bifi_scan_range_field.setValue(cfg.get(cfg.BIFI_SCAN_RANGE)) self.bifi_small_scan_range_field.setValue( cfg.get(cfg.BIFI_SCAN_RANGE_SMALL)) self.bifi_scan_step_field.setValue(cfg.get(cfg.BIFI_SCAN_STEP)) self.bifi_scan_show_plots_field.setChecked( cfg.get(cfg.BIFI_SCAN_SHOW_PLOTS)) self.bifi_smoothing_window_field.setValue( cfg.get(cfg.BIFI_SMOOTHING_FILTER_WINDOW)) self.bifi_smoothing_polyorder_field.setValue( cfg.get(cfg.BIFI_SMOOTHING_FILTER_POLYORDER)) self.thin_eta_scan_range_field.setValue( cfg.get(cfg.THIN_ETA_SCAN_RANGE)) self.thin_eta_small_scan_range_field.setValue( cfg.get(cfg.THIN_ETA_SCAN_RANGE_SMALL)) self.thin_eta_scan_step_field.setValue(cfg.get(cfg.THIN_ETA_SCAN_STEP)) self.thin_eta_nudge_field.setValue(cfg.get(cfg.THIN_ETA_NUDGE)) self.thin_eta_scan_show_plots_field.setChecked( cfg.get(cfg.THIN_ETA_SHOW_PLOTS)) self.thin_eta_smoothing_window_field.setValue( cfg.get(cfg.THIN_ETA_SMOOTHING_FILTER_WINDOW)) self.thin_eta_smoothing_polyorder_field.setValue( cfg.get(cfg.THIN_ETA_SMOOTHING_FILTER_POLYORDER)) self.thin_eta_max_allowed_stddev_field.setValue( cfg.get(cfg.THIN_ETA_MAX_ALLOWED_STDDEV)) self.refcell_rising_speed_field.setValue( cfg.get(cfg.REFCELL_SCAN_RISING_SPEED)) self.refcell_falling_speed_field.setValue( cfg.get(cfg.REFCELL_SCAN_FALLING_SPEED)) self.large_wavelength_drift_field.setValue( cfg.get(cfg.LARGE_WAVELENGTH_DRIFT)) self.medium_wavelength_drift_field.setValue( cfg.get(cfg.MEDIUM_WAVELENGTH_DRIFT)) self.small_wavelength_drift_field.setValue( cfg.get(cfg.SMALL_WAVELENGTH_DRIFT)) self.locking_timeout_field.setValue(cfg.get(cfg.LOCKING_TIMEOUT)) self.fast_pz_setpoint_lower_limit_field.setValue( cfg.get(cfg.FAST_PZ_SETPOINT_SCAN_LOWER_LIMIT)) self.fast_pz_setpoint_upper_limit_field.setValue( cfg.get(cfg.FAST_PZ_SETPOINT_SCAN_UPPER_LIMIT)) self.fast_pz_setpoint_num_points_field.setValue( cfg.get(cfg.FAST_PZ_SETPOINT_NUM_POINTS)) self.fast_pz_setpoint_num_scans_field.setValue( cfg.get(cfg.FAST_PZ_SETPOINT_NUM_SCANS)) self.stabilization_rising_speed_field.setValue( cfg.get(cfg.STABILIZATION_RISING_SPEED)) self.stabilization_falling_speed_field.setValue( cfg.get(cfg.STABILIZATION_FALLING_SPEED)) self.stabilization_delay_field.setValue( cfg.get(cfg.STABILIZATION_DELAY)) self.stabilization_tolerance_field.setValue( cfg.get(cfg.STABILIZATION_TOLERANCE)) self.auto_correction_limit_field.setValue(cfg.get( cfg.CORRECTION_LIMIT)) self.pz_eta_upper_correction_pos_field.setValue( cfg.get(cfg.PIEZO_ETA_UPPER_CORRECTION_POS)) self.slow_pz_upper_correction_pos_field.setValue( cfg.get(cfg.SLOW_PIEZO_UPPER_CORRECTION_POS)) self.refcell_upper_correction_pos_field.setValue( cfg.get(cfg.REFCELL_UPPER_CORRECTION_POS)) self.pz_eta_mid_correction_pos_field.setValue( cfg.get(cfg.PIEZO_ETA_MID_CORRECTION_POS)) self.slow_pz_mid_correction_pos_field.setValue( cfg.get(cfg.SLOW_PIEZO_MID_CORRECTION_POS)) self.refcell_mid_correction_pos_field.setValue( cfg.get(cfg.REFCELL_MID_CORRECTION_POS)) self.pz_eta_lower_correction_pos_field.setValue( cfg.get(cfg.PIEZO_ETA_LOWER_CORRECTION_POS)) self.slow_pz_lower_correction_pos_field.setValue( cfg.get(cfg.SLOW_PIEZO_LOWER_CORRECTION_POS)) self.refcell_lower_correction_pos_field.setValue( cfg.get(cfg.REFCELL_LOWER_CORRECTION_POS)) self.target_temperature_field.setValue( cfg.get(cfg.PLE_TARGET_TEMPERATURE)) self.temperature_tolerance_field.setValue( cfg.get(cfg.PLE_TEMPERATURE_TOLERANCE))
def birefringent_filter_scan(self, scan_range: int = None, repeat=False): """ Initiate a scan of the birefringent filter, selecting the power maximum closest to the target wavelength. A configurable Savitzky-Golay filter is used to smooth the data for analysis. The position is not changed if the difference between the current position and the "best" position is less than 1/6 of the average separation between peaks in the power diode curve. Additionally, plot the power data and motor position selection if plotting is enabled for this scan. Parameters ---------- scan_range : int number of motor positions to scan left and right repeat : bool whether to repeat the scan until the wavelength difference is less than cfg.MEDIUM_WAVELENGTH_DRIFT """ self.is_scanning_bifi = True if self.exit_flag or self._scan_attempts > cfg.get( cfg.SCAN_LIMIT) or self._restart_set_wavelength: self.is_scanning_bifi = False return if self.target_wavelength is None: self.target_wavelength = self.wavemeter_wavelength() if scan_range is None: scan_range = cfg.get(cfg.BIFI_SCAN_RANGE) self._scan_attempts += 1 old_pos = int(self.query('MOTBI:POS?', numeric_result=True)) lower_end = old_pos - scan_range upper_end = old_pos + scan_range assert (0 < lower_end < BIREFRINGENT_FILTER_UPPER_LIMIT and 0 < upper_end < BIREFRINGENT_FILTER_UPPER_LIMIT and lower_end < upper_end), 'Conditions for BiFi scan invalid. Motor position must be between ' + \ f"{scan_range} and {BIREFRINGENT_FILTER_UPPER_LIMIT - scan_range}" positions = np.array( range(lower_end, upper_end, cfg.get(cfg.BIFI_SCAN_STEP))) voltages = np.array([]) print('Starting BiFi scan... ') for pos in positions: self.set_bifi_motor_pos(pos) voltages = np.append(voltages, self.query('DPOW:DC?', numeric_result=True)) self.set_bifi_motor_pos( old_pos ) # return back to where we started, just in case something goes wrong print('Done.') print('Analyzing scan data... ') # Smooth out the data and find extrema smoothed_data = savgol_filter( voltages, window_length=cfg.get(cfg.BIFI_SMOOTHING_FILTER_WINDOW), polyorder=cfg.get(cfg.BIFI_SMOOTHING_FILTER_POLYORDER)) maxima = argrelextrema(smoothed_data, np.greater, order=5) # Find the position of the extremum closest to the target wavelength wavelength_differences = np.array([]) for pos in positions[maxima]: self.set_bifi_motor_pos(pos) time.sleep(cfg.get(cfg.WAVEMETER_MEASUREMENT_DELAY)) wavelength_differences = np.append( wavelength_differences, abs(self.wavemeter_wavelength() - self.target_wavelength)) best_pos = positions[maxima][np.argmin(wavelength_differences)] # By default, let's assume we're using the new position. using_new_pos = True if len(positions[maxima]) > 1: difference_threshold = np.mean(np.diff(positions[maxima])) / 6 if abs(old_pos - best_pos) > difference_threshold: self.set_bifi_motor_pos(best_pos) else: print( 'Current BiFi motor position is close enough, leaving it alone.' ) self.set_bifi_motor_pos(old_pos) using_new_pos = False else: self.set_bifi_motor_pos(best_pos) print('Done.') self.is_scanning_bifi = False if cfg.get(cfg.BIFI_SCAN_SHOW_PLOTS): # TODO: Label wavelength at each peak plot_process = BirefringentFilterScanPlotProcess(positions, voltages, smoothed_data, maxima, old_pos, best_pos, using_new_pos, daemon=True) self._plotting_processes.append(plot_process) plot_process.start() if repeat: new_diff = np.min(wavelength_differences) if abs(new_diff) > cfg.get(cfg.MEDIUM_WAVELENGTH_DRIFT): print( 'Wavelength still too far away from target value. Starting another scan.' ) self.birefringent_filter_scan(scan_range, repeat=True)
def thin_etalon_scan(self, scan_range: int = None, repeat=False): """ Initiate a scan of the thin etalon, selecting the reflex minimum closest to the target wavelength. A configurable Savitzky-Golay filter is used to smooth the data for analysis. The position is not changed if the difference between the current position and the "best" position is less than 1/6 of the average separation between valleys in the reflex curve. If the thin etalon moves too far to one side and we end up in a valley of the power diode curve, the wavelength will make large jumps, so a small birefringent scan is performed to correct this. If the thin etalon moves into a region with too much noise (as determined by a normalized RMS deviation), quit early and perform a large scan next time set_wavelength is called. Nudges the motor position a little bit away from the minimum to ensure good locking later. Additionally, plot the reflex data and motor position selection. Parameters ---------- scan_range : int number of motor positions to scan left and right repeat : bool whether to repeat the scan until the wavelength difference is less than cfg.SMALL_WAVELENGTH_DRIFT """ self.is_scanning_thin_etalon = True if self.exit_flag or self._scan_attempts > cfg.get( cfg.SCAN_LIMIT) or self._restart_set_wavelength: self.is_scanning_thin_etalon = False return if self.target_wavelength is None: self.target_wavelength = self.wavemeter_wavelength() if scan_range is None: scan_range = cfg.get(cfg.THIN_ETA_SCAN_RANGE) self._scan_attempts += 1 old_pos = int(self.query('MOTTE:POS?', numeric_result=True)) lower_end, upper_end = self.limits_for_thin_etalon_scan( old_pos, scan_range) positions = np.array( range(lower_end, upper_end, cfg.get(cfg.THIN_ETA_SCAN_STEP))) voltages = np.array([]) print('Starting thin etalon scan... ') for pos in positions: self.set_thin_etalon_motor_pos(pos) voltages = np.append(voltages, self.query('TE:DC?', numeric_result=True)) self.set_thin_etalon_motor_pos( old_pos ) # return back to where we started, just in case something goes wrong print('Done.') print('Analyzing scan data... ') # Smooth out the data and find extrema smoothed_data = savgol_filter( voltages, window_length=cfg.get(cfg.THIN_ETA_SMOOTHING_FILTER_WINDOW), polyorder=cfg.get(cfg.THIN_ETA_SMOOTHING_FILTER_POLYORDER)) normalized_std_dev = np.sqrt( np.sum(((smoothed_data - voltages) / smoothed_data)**2)) print( f"Normalized standard deviation from smoothed data: {normalized_std_dev}" ) # Example good value: 1.5, example bad value: 2.5 if normalized_std_dev > cfg.get(cfg.THIN_ETA_MAX_ALLOWED_STDDEV): print( 'Abnormal deviation from smoothed curve detected, the scan region might just contain noise.' ) self._restart_set_wavelength = True self._force_large_scan = True self.is_scanning_thin_etalon = False return minima = argrelextrema(smoothed_data, np.less, order=5) # Find the position of the extremum closest to the target wavelength wavelength_differences = np.array([]) for pos in positions[minima]: self.set_thin_etalon_motor_pos(pos) time.sleep(cfg.get(cfg.WAVEMETER_MEASUREMENT_DELAY)) wavelength_differences = np.append( wavelength_differences, abs(self.wavemeter_wavelength() - self.target_wavelength)) best_minimum_index = np.argmin(wavelength_differences) best_pos = positions[minima][best_minimum_index] + cfg.get( cfg.THIN_ETA_NUDGE) # By default, let's assume we're using the new position. using_new_pos = True if len(positions[minima]) > 1: difference_threshold = np.mean(np.diff(positions[minima])) / 6 if abs(old_pos - best_pos) > difference_threshold: self.set_thin_etalon_motor_pos(best_pos) else: print( 'Current thin etalon motor position is close enough, leaving it alone.' ) self.set_thin_etalon_motor_pos(old_pos) using_new_pos = False else: self.set_thin_etalon_motor_pos(best_pos) print('Done.') self.is_scanning_thin_etalon = False adjacent_differences = np.diff(wavelength_differences) left_too_large = (best_minimum_index >= 1 and adjacent_differences[best_minimum_index - 1] > cfg.get(cfg.LARGE_WAVELENGTH_DRIFT)) right_too_large = (best_minimum_index < len(wavelength_differences) - 1 and adjacent_differences[best_minimum_index] > cfg.get(cfg.LARGE_WAVELENGTH_DRIFT)) if left_too_large or right_too_large: print( 'Large jump in wavelength detected, correcting birefringent filter position.' ) self.birefringent_filter_scan(cfg.get(cfg.BIFI_SCAN_RANGE_SMALL), repeat=False) print('Returning to thin etalon scan.') if cfg.get(cfg.THIN_ETA_SHOW_PLOTS): plot_process = ThinEtalonScanPlotProcess(positions, voltages, smoothed_data, minima, old_pos, best_pos, using_new_pos, daemon=True) self._plotting_processes.append(plot_process) plot_process.start() if repeat: new_diff = np.min(wavelength_differences) if new_diff > cfg.get(cfg.SMALL_WAVELENGTH_DRIFT): print( 'Wavelength still too far away from target value. Starting another scan.' ) self.thin_etalon_scan(scan_range, repeat=True)
def set_wavelength(self, wavelength: float): """ Configure the Matisse to output a given wavelength. If the laser is locked and/or stabilizing, pause those operations for the duration of the method. First I'll check the difference between the current wavelength and the target wavelength. - If this is the first time this is being run, do a large birefringent scan regardless of the difference. - If it's greater than cfg.LARGE_WAVELENGTH_DRIFT, do a large birefringent scan to choose a better peak. - If it's between about cfg.MEDIUM_WAVELENGTH_DRIFT and cfg.LARGE_WAVELENGTH_DRIFT, do a small birefringent scan to keep it on the peak. - If it's between cfg.SMALL_WAVELENGTH_DRIFT nm and cfg.MEDIUM_WAVELENGTH_DRIFT, skip the first birefringent scan and go right to the thin etalon scan. - If it's less than cfg.SMALL_WAVELENGTH_DRIFT, skip all BiFi and TE scans, and just do a RefCell scan. This is generally the process I'll follow: 1. Decide whether to skip any scans, as described above. 2. Set approx. wavelength using BiFi. This is supposed to be good to about +-1 nm but it's usually very far off. 3. Scan the BiFi back and forth and measure the total laser power at each point. 4. Find all local maxima, move the BiFi to the maximum that's closest to the desired wavelength. 5. Move the thin etalon motor directly to a position close to the target wavelength. 6. Scan the thin etalon back and forth and measure the thin etalon reflex at each point. 7. Find all local minima. Move the TE to the minimum that's closest to the desired wavelength. 8. Shift the thin etalon a over bit by cfg.THIN_ETA_NUDGE. We want to be on the "flank" of the chosen parabola. 9. Do a small BiFi scan to make sure we're still on the location with maximum power. If the distance to the new motor location is very small, just leave the motor where it is. 10. Do a small thin etalon scan to make sure we're still on the flank of the right parabola. 11. Attempt to lock the laser, setting the fast piezo setpoint if needed. 12. Enable RefCell stabilization, which scans the device up or down until the desired wavelength is reached. If more than cfg.SCAN_LIMIT scan attempts pass before stabilizing, restart the whole process over again. If, during stabilization, more than cfg.CORRECTION_LIMIT corrections are made, start with a large birefringent scan the next time this method is run. A scan may decide it needs to start the process over again for some other reason, like the thin etalon moving to a location with mostly noise. Parameters ---------- wavelength : float the desired wavelength """ self.is_setting_wavelength = True assert cfg.get(cfg.WAVELENGTH_LOWER_LIMIT) < wavelength < cfg.get(cfg.WAVELENGTH_UPPER_LIMIT), \ 'Target wavelength out of range.' self.target_wavelength = wavelength if self.is_lock_correction_on(): self.stop_laser_lock_correction() # Disable all control loops. This is important if the laser is locked but lock correction isn't on. self.set_fast_piezo_control(False) self.set_piezo_etalon_control(False) self.set_thin_etalon_control(False) self.set_slow_piezo_control(False) if self.is_stabilizing(): self.stabilize_off() while True: self._scan_attempts = 0 diff = abs(wavelength - self.wavemeter_wavelength()) if diff > cfg.get( cfg.LARGE_WAVELENGTH_DRIFT) or self._force_large_scan: # Notice we randomize the position of the thin etalon a little bit to avoid returning to exactly the # same state each time. This is to further avoid the possibility of getting stuck in an endless loop. rand_offset = np.random.randint( -cfg.get(cfg.THIN_ETA_RAND_RANGE), cfg.get(cfg.THIN_ETA_RAND_RANGE) + 1) self.query( f"MOTTE:POS {cfg.get(cfg.THIN_ETA_RESET_POS) + rand_offset}" ) self.reset_stabilization_piezos() # Normal BiFi scan print(f"Setting BiFi to ~{wavelength} nm... ") self.set_bifi_wavelength(wavelength) time.sleep(cfg.get(cfg.WAVEMETER_MEASUREMENT_DELAY)) print( f"Done. Wavelength is now {self.wavemeter_wavelength()} nm. " "(This is often very wrong, don't worry)") self.birefringent_filter_scan(repeat=True) self.thin_etalon_scan(repeat=True) self.birefringent_filter_scan(scan_range=cfg.get( cfg.BIFI_SCAN_RANGE_SMALL), repeat=True) self.thin_etalon_scan(scan_range=cfg.get( cfg.THIN_ETA_SCAN_RANGE_SMALL), repeat=True) elif cfg.get(cfg.MEDIUM_WAVELENGTH_DRIFT) < diff <= cfg.get( cfg.LARGE_WAVELENGTH_DRIFT): # Small BiFi scan self.birefringent_filter_scan(scan_range=cfg.get( cfg.BIFI_SCAN_RANGE_SMALL), repeat=True) self.thin_etalon_scan(repeat=True) self.birefringent_filter_scan(scan_range=cfg.get( cfg.BIFI_SCAN_RANGE_SMALL), repeat=True) self.thin_etalon_scan(scan_range=cfg.get( cfg.THIN_ETA_SCAN_RANGE_SMALL), repeat=True) elif cfg.get(cfg.SMALL_WAVELENGTH_DRIFT) < diff <= cfg.get( cfg.MEDIUM_WAVELENGTH_DRIFT): # No BiFi scan, TE scan only self.thin_etalon_scan(repeat=True) self.birefringent_filter_scan(scan_range=cfg.get( cfg.BIFI_SCAN_RANGE_SMALL), repeat=True) self.thin_etalon_scan(scan_range=cfg.get( cfg.THIN_ETA_SCAN_RANGE_SMALL), repeat=True) else: # No BiFi, no TE. Scan device only. pass # Restart/exit conditions if self.exit_flag: self.is_setting_wavelength = False return if self._restart_set_wavelength: self._restart_set_wavelength = False print('Restarting wavelength-setting process.') continue elif self._scan_attempts > cfg.get(cfg.SCAN_LIMIT): print( 'WARNING: Number of scan attempts exceeded. Starting wavelength-setting process over again.' ) log_event( EventType.SCAN_LIMIT_EXCEEDED, self, self.wavemeter_wavelength(), f"too many scans, exceeded limit of {cfg.get(cfg.SCAN_LIMIT)}, restarting set_wavelength" ) self._force_large_scan = True continue elif self.stabilization_auto_corrections > cfg.get( cfg.CORRECTION_LIMIT): print( 'WARNING: Number of stabilization auto-corrections exceeded. Starting wavelength-setting process ' 'over again.') log_event( EventType.STABILIZATION_LIMIT_EXCEEDED, self, self.wavemeter_wavelength(), f"too many stabilization corrections, exceeded limit of {cfg.get(cfg.CORRECTION_LIMIT)}, " 'restarting set_wavelength') self.stabilization_auto_corrections = 0 self._force_large_scan = True continue else: self._force_large_scan = False break self.start_laser_lock_correction() print('Attempting to lock laser...') while not self.laser_locked(): if self.exit_flag: self.is_setting_wavelength = False return if not self.is_lock_correction_on(): print('Lock failed, trying again.') self.set_recommended_fast_piezo_setpoint() self.start_laser_lock_correction() time.sleep(1) self.stabilize_on() self.is_setting_wavelength = False
def setup_form(self): form_layout = QFormLayout() self.scan_name_field = QLineEdit() form_layout.addRow('Scan name: ', self.scan_name_field) scan_location_layout = QHBoxLayout() self.scan_location_button = QPushButton('Select Folder') self.scan_location_label = QLabel() scan_location_layout.addWidget(self.scan_location_button) scan_location_layout.addWidget(self.scan_location_label) form_layout.addRow('Scan Location: ', scan_location_layout) self.wavelength_start_field = QDoubleSpinBox() self.wavelength_start_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.wavelength_start_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.wavelength_start_field.setDecimals( cfg.get(cfg.WAVEMETER_PRECISION)) self.wavelength_start_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) form_layout.addRow('Wavelength start (nm): ', self.wavelength_start_field) self.wavelength_end_field = QDoubleSpinBox() self.wavelength_end_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.wavelength_end_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.wavelength_end_field.setDecimals(cfg.get(cfg.WAVEMETER_PRECISION)) self.wavelength_end_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) form_layout.addRow('Wavelength end (nm): ', self.wavelength_end_field) self.wavelength_step_field = QDoubleSpinBox() self.wavelength_step_field.setMinimum(0) self.wavelength_step_field.setDecimals(cfg.get( cfg.WAVEMETER_PRECISION)) self.wavelength_step_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) form_layout.addRow('Wavelength step (nm): ', self.wavelength_step_field) self.exposure_time_field = QDoubleSpinBox() self.exposure_time_field.setMinimum(0) self.exposure_time_field.setDecimals(4) form_layout.addRow('Exposure time (s): ', self.exposure_time_field) self.layout.addLayout(form_layout) # We need to make sure the Andor libraries are loaded to access the spectrometer ple.PLE.load_andor_libs() self.center_wavelength_field = QDoubleSpinBox() self.center_wavelength_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.center_wavelength_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.center_wavelength_field.setValue( ple.shamrock.get_center_wavelength()) form_layout.addRow('Center wavelength (nm): ', self.center_wavelength_field) self.grating_grooves_field = QComboBox() for groove_num in ple.shamrock.gratings.keys(): self.grating_grooves_field.addItem( f"{groove_num}", ple.shamrock.gratings[groove_num]) self.grating_grooves_field.setCurrentText( str(ple.shamrock.get_grating_grooves())) form_layout.addRow('Grating grooves: ', self.grating_grooves_field) line = QFrame() line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) form_layout.addRow(line) self.plot_analysis_field = QCheckBox() form_layout.addRow('Plot analysis in real time? ', self.plot_analysis_field) self.integration_start_field = QDoubleSpinBox() self.integration_start_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.integration_start_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.integration_start_field.setDecimals( cfg.get(cfg.WAVEMETER_PRECISION)) self.integration_start_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) self.integration_start_field.setEnabled(False) form_layout.addRow('Integration start (nm): ', self.integration_start_field) self.integration_end_field = QDoubleSpinBox() self.integration_end_field.setMinimum( cfg.get(cfg.WAVELENGTH_LOWER_LIMIT)) self.integration_end_field.setMaximum( cfg.get(cfg.WAVELENGTH_UPPER_LIMIT)) self.integration_end_field.setDecimals(cfg.get( cfg.WAVEMETER_PRECISION)) self.integration_end_field.setSingleStep( 10**-cfg.get(cfg.WAVEMETER_PRECISION)) self.integration_end_field.setEnabled(False) form_layout.addRow('Integration end (nm): ', self.integration_end_field)