def pc_calibration_measurement(self, calibration_settings): null_pulse = LightPulse(calibration_settings) self.add_to_queue(null_pulse.complete_waveform, calibration_settings) return self.single_measurement()
def load_settings(self, event): """ Loads settings from the forms """ # TODO: change so just fills form out # and then reads into data structure when press perform measurement self.view1.clear_experiment_form() self.measurement_handler.clear_queue() file_dir, file_name = self.view1.ask_user_for_filename( defaultDir=self.app_dir, message='Choose a file', wildcard='*.*', style=wx.OPEN) if file_dir is not None: # try: config_dict = load_metadata(file_name, file_dir) settings = self._parse_config(config_dict) for setting in settings["experiment_settings"]: self.measurement_handler.add_to_queue( LightPulse(setting).create_waveform(), setting) self.temperature_settings = settings["temp_settings"] # except Exception as e: # print("An Exception occurred: {0}".format(e)) self.view1.set_experiment_form(self.measurement_handler.as_list()) self.view1.set_temperature_form(self.temperature_settings.as_dict()) self.measurement_handler.clear_queue()
def upload(self, event): """ Bulk upload settings from a text file """ self.measurement_handler.clear_queue() file_dir, file_name = self.view1.ask_user_for_filename( defaultDir=self.app_dir, style=wx.OPEN, message='Choose a file', wildcard='*.*') if file_dir is not None: config_dict = load_metadata(file_name, file_dir) settings = self._parse_config(config_dict) for setting in settings["experiment_settings"]: self.measurement_handler.add_to_queue( LightPulse(setting).create_waveform(), setting) self.temperature_settings = settings["temp_settings"] self.view1.set_temperature_form( self.temperature_settings.as_dict()) self.view1.disable_all_settings_inputs() self.view1.show_info_modal( "{0} experiment settings uploaded successfully!".format( len(self.measurement_handler._queue))) self.uploaded = True
def perform_measurement(self, event): """ Perform the queued measurements """ try: if not self.uploaded: # try to retrieve settings from the various forms config_dict = {} config_dict["temperature_settings"] = ( self.view1.get_temperature_form()) config_dict["wafer_settings"] = self.view1.get_wafer_form() config_dict["experiment_settings"] = ( self.view1.get_experiment_form()) try: settings = self._parse_config(config_dict) except KeyError as e: self.view1.show_error_modal(str(e)) for setting in settings["experiment_settings"]: self.measurement_handler.add_to_queue( LightPulse(setting).create_waveform(), setting) self.wafer_settings = settings["wafer_settings"] self.temperature_settings = settings["temp_settings"] if self.data_dir is not None: save_metadata(self.wafer_settings.as_dict(), self.data_dir, self.wafer_settings.id) if self.measurement_handler.is_queue_empty(): raise (PVInputError("No measurements loaded.")) # Do the actual measurements # TODO: refactor in separate method dataset_list = [] total_measurements = 0 self.PlotModal = PlotModal(self.app) self.PlotModal.Show() while not self.measurement_handler.is_queue_empty(): single_dataset = self.measurement_handler.single_measurement() dataset_list.append(single_dataset) total_measurements = total_measurements + 1 ts = int(time.time()) dataset_name = (str(total_measurements) + self.wafer_settings.id + str(ts)) save_data(single_dataset, dataset_name, self.data_dir) # print single_dataset.shape self.PlotModal.plot_data([x for x in single_dataset[:, 1:].T]) except PVInputError as e: self.view1.show_error_modal(str(e))
def __init__(self, parent): # models self.metadata = ExperimentSettings() self.Data = ExperimentData(metadata=self.metadata) self.light_pulse = LightPulse(self.metadata) # setup file data self.dirname = os.getcwd() self.data_file = "untitled.dat" self.metadata_file = "untitled.inf" self.__InitUI(parent) self.__InitValidators()
class WaveformThreadTest(unittest.TestCase): # np.set_printoptions(threshold='nan') def setUp(self): self.settings = ExperimentSettings() self.lp = LightPulse(self.settings) def test_init(self, mock_waveform_thread): WaveformThread( waveform=self.lp.create_waveform(), Channel='ao1', Time=np.float64(1.011), input_voltage_range=10.0, output_voltage_range=self.settings.output_voltage_range, input_sample_rate=self.settings.sample_rate, output_sample_rate=self.settings.output_sample_rate )
def display(self, event): """ Plot waveforms in modal """ self.PlotModal = PlotModal(self.app) waveform_list = [] settings_dict = {} try: settings_dict[ "experiment_settings"] = self.view1.get_experiment_form() experiment_settings = self._parse_experiment_settings( settings_dict) for setting in experiment_settings: waveform_list.append(-LightPulse(setting).create_waveform()) self.PlotModal.Show() self.PlotModal.plot_data(waveform_list) except PVInputError as e: self.view1.show_error_modal(str(e))
def single_measurement(self): ''' This runs a single measurements: 1. Sets the femto gain 2. Generatres a waveform 3. Sends command to DAQ 4. Does next item ''' # get measurement from list measurement_settings = self._queue.popleft() print 'The measurement settings are', type(measurement_settings) print measurement_settings.ref_gain print measurement_settings.pl_gain # set preamp gains self.preamps.configure(measurement_settings) # gets the voltage pulse to control the waveform waveform_array = LightPulse(measurement_settings).create_waveform() # plt.figure() # plt.plot(waveform_array) # plt.show() # initalises the DAQ ready for a measurement thread = self.daq._int_thread(waveform_array, measurement_settings) # lets get the averaging averaging = measurement_settings.averaging # set the variables before they are created? thread_time = None measurement_data = [] # perform a measurement measurement_data, thread_time = self.daq.run_thread(thread) # make sure the length makes sense data_len = measurement_data.shape[0] / self.daq.NUM_CHANNELS # print type(data_len), data_len # print assert (data_len.is_integer(), "No of points per channel={0}".format(data_len)) data_len = int(data_len) # if more averages were requested if averaging > 1: for i in range(averaging - 1): thread_data, thread_time = self.daq.run_thread(thread) # stack the data measurement_data = np.vstack((thread_data, measurement_data)) # average the data, weighting it towards # running total measurement_data = np.average(measurement_data, axis=0, weights=(1, i + 1)) # convert into columns, putting the right data # in the right place measurement_data = measurement_data[:data_len * int(self.daq.NUM_CHANNELS)] measurement_data = measurement_data.reshape( (data_len, int(self.daq.NUM_CHANNELS)), order='F') assert measurement_data.shape[0] == thread_time.shape[ 0], 'data Length differ {0} {1}'.format(measurement_data.shape, thread_time.shape[0]) measurement_data = np.vstack((thread_time, measurement_data.T)).T measurement_data = bin_data(measurement_data, measurement_settings.binning) return measurement_data
class GUIController(FrameSkeleton): """ Controller to handle interface with wx UI Attributes ---------- MyFrame1: Fig1 Data LoadPath Path measurement_type """ def __init__(self, parent): # models self.metadata = ExperimentSettings() self.Data = ExperimentData(metadata=self.metadata) self.light_pulse = LightPulse(self.metadata) # setup file data self.dirname = os.getcwd() self.data_file = "untitled.dat" self.metadata_file = "untitled.inf" self.__InitUI(parent) self.__InitValidators() def __InitUI(self, parent): # initialize parent class FrameSkeleton.__init__(self, parent) # setup Matplotlib Canvas panel self.Fig1 = CanvasPanel(self.Figure1_Panel) # make status bars m_statusBar = wx.StatusBar(self) self.SetStatusBar(m_statusBar) # self.m_statusBar.SetStatusText("Ready to go!") self.data_panel = DataProcessingPanel(self.m_notebook1) self.m_notebook1.AddPage( self.data_panel, u"Data Processing", # True ) # Setup the Menu menu_bar = wx.MenuBar() # File Menu filem = wx.Menu() filem.Append(wx.ID_OPEN, "Open\tCtrl+O") filem.Append(wx.ID_ANY, "Run\tCtrl+R") filem.Append(wx.ID_ABOUT, "About") filem.Append(wx.ID_EXIT, "Exit\tCtrl+X") # TODO: add and bind event handlers menu_bar.Append(filem, "&File") self.SetMenuBar(menu_bar) # initialise view with model parameters self.m_voltageRange.AppendItems(INPUT_VOLTAGE_RANGE_STR) self.m_Waveform.AppendItems(WAVEFORMS) self.m_Output.AppendItems(OUTPUTS) self.setWaveformParameters() self.setCollectionParameters() def __InitValidators(self): # Waveform validators self.m_Intensity.SetValidator(NumRangeValidator(numeric_type='int')) self.m_Threshold.SetValidator(NumRangeValidator(numeric_type='int')) self.m_Period.SetValidator(NumRangeValidator(numeric_type='float')) self.m_Offset_Before.SetValidator(NumRangeValidator(numeric_type='float')) self.m_Offset_After.SetValidator(NumRangeValidator(numeric_type='float')) # Data collection validators self.m_samplingFreq.SetValidator(NumRangeValidator(numeric_type='float')) self.m_Averaging.SetValidator(NumRangeValidator(numeric_type='int')) self.m_Binning.SetValidator(NumRangeValidator(numeric_type='int')) def setPCCalibrationMean(self): self.m_pcCalibrationMean.SetValue('{0:3.3f}'.format( self.metadata.pc_calibration_mean )) def setPCCalibrationStd(self): self.m_pcCalibrationStd.SetValue('{0:3.3f}'.format( self.metadata.pc_calibration_std )) def setSampleDataPoints(self, sample_data_points): self.m_DataPoint.SetValue('{0:.2e}'.format(sample_data_points)) def setFrequency(self, frequence_val): self.m_Frequency.SetValue('{0:3.3f}'.format(frequence_val)) def setWaveformParameters(self): self.m_Intensity.SetValue(str(self.metadata.amplitude)) self.m_Period.SetValue(str(self.metadata.duration)) self.m_Offset_Before.SetValue(str(self.metadata.offset_before)) self.m_Offset_After.SetValue(str(self.metadata.offset_after)) self.setFrequency(self.metadata.get_frequency()) def setCollectionParameters(self): self.m_Binning.SetValue(str(self.metadata.binning)) self.m_Averaging.SetValue(str(self.metadata.averaging)) self.m_Threshold.SetValue(str(self.metadata.threshold)) self.m_samplingFreq.SetValue(str(self.metadata.sample_rate)) self.setSampleDataPoints(self.metadata.get_total_data_points()) ################################# # Event Handlers for Measurements def Perform_Measurement(self, event): print("GUIController: Perform_Measurement") # Using that instance we then run the lights, # and measure the outputs self.measurement_handler = MeasurementHandler() measurement_handler.add_to_queue( self.light_pulse.complete_waveform, self.metadata ) raw_data = self.measurement_handler.single_measurement() self.Data.updateRawData(raw_data) self.Data.Data = utils.bin_data(raw_data, self.metadata.binning) # We then plot the datas, this has to be changed if the plots want # to be updated on the fly. pub.sendMessage('update.plot') def fftHandler(self): channel = self.data_panel.m_fftChoice.GetStringSelection() freq_data = self.Data.fftOperator(channel, self.metadata.get_total_time()) self.PlotData(freq_data, title=['FFT of Raw data', 'Frequency (hz)', 'Voltage (V)']) def onWaveformParameters(self, event): self.metadata.A = float(self.m_Intensity.GetValue()) self.metadata.Duration = float(self.m_Period.GetValue()) self.metadata.Offset_Before = float(self.m_Offset_Before.GetValue()) self.metadata.Offset_After = float(self.m_Offset_After.GetValue()) self.metadata.Waveform = self.m_Waveform.GetStringSelection() pub.sendMessage('waveform.change') def onCollectionParameters(self, event): # TODO: refactor so it obeys DRY self.metadata.binning = int(self.m_Binning.GetValue()) self.metadata.averaging = int(self.m_Averaging.GetValue()) self.metadata.channel = self.m_Output.GetStringSelection() self.metadata.threshold = float(self.m_Threshold.GetValue()) self.metadata.sample_rate = float(self.m_samplingFreq.GetValue()) self.metadata.sample_data_points = self.metadata.get_total_data_points() pub.sendMessage('collection.change') ########################## # Plot Handlers # def plotHandler(self): self.PlotData(self.Data.Data) def PlotData(self, data, data_labels=['Reference', 'PC', 'PL'], title=['Raw Data', 'Time (s)', 'Voltage (V)'], e=None): self.Fig1.clear() labels = data_labels colours = ['b', 'r', 'g'] # this is done not to clog up the plot with many points if data.shape[0] > 1000: num = data.shape[0] // 1000 else: num = 1 # This plots the figure for i, label, colour in zip(data[:, 1:].T, labels, colours): self.Fig1.draw_points( data[::num, 0], i[::num], '.', Color=colour, Label=label ) self.Fig1.legend() self.Fig1.labels(title[0], title[1], title[2]) self.Fig1.update() if e is not None: e.skip() ################# # Helper methods: def defaultFileDialogOptions(self): """ Return a dictionary with file dialog options that can be used in both the save file dialog as well as in the open file dialog. """ return dict(message='Choose a file', defaultDir=self.dirname, wildcard='*.*') def askUserForFilename(self, **dialogOptions): dialog = wx.FileDialog(self, **dialogOptions) if dialog.ShowModal() == wx.ID_OK: userProvidedFilename = True self.data_file = dialog.GetFilename() self.dirname = dialog.GetDirectory() else: userProvidedFilename = False dialog.Destroy() return userProvidedFilename def ShowCalibrationModal(self): msg_text = ( "Please remove sample from measurement area\n" "Only one PC calibration is necessary per experimental session" ) wx.MessageBox(msg_text, 'Info', wx.OK | wx.ICON_INFORMATION) ########################## # App state Event Handlers # def onSave(self, event): """ Method to handle dialogue window and saving data to file """ dialog = wx.FileDialog( None, 'Save measurement data and metadata', self.dirname, '', r'DAT and INF files (*.dat;*.inf)|*.inf;*.dat', wx.FD_SAVE ) if dialog.ShowModal() == wx.ID_OK: dialog_path = dialog.GetPath() self.dirname = os.path.dirname(dialog_path) self.SaveName = os.path.splitext(os.path.basename(dialog_path))[0] experiment_settings = self.metadata.get_settings_as_dict() waveform_settings = self.light_pulse.get_settings_as_dict() metadata_dict = experiment_settings.copy() utils.save_data(self.Data.Data, self.SaveName, self.dirname) utils.save_metadata( metadata_dict, self.SaveName, self.dirname ) else: print('Canceled save') dialog.Destroy() event.Skip() def onSaveData(self, event): print("onSaveData") if self.askUserForFilename(style=wx.SAVE, **self.defaultFileDialogOptions()): utils.save_data(self.Data.Data, self.data_file, self.dirname) # print(filename) # fullpath = os.path.join(self.dirname, self.data_file) # self.Data.updateRawData(utils.load_data(fullpath)) def onLoad(self, event): """ Method to handle load metadata dialog window and update metadata state """ dialog = wx.FileDialog( None, 'Select a metadata file', self.dirname, '', r'*.inf', wx.FD_OPEN ) if dialog.ShowModal() == wx.ID_OK: metadata_dict = utils.load_metadata(dialog.GetPath()) metadata_stringified = dict( [a, str(x)] for a, x in metadata_dict.iteritems() ) print(metadata_stringified) # experimental data self.m_Output.SetStringSelection(metadata_stringified[u'Channel']) self.m_Averaging.SetValue(metadata_stringified[u'Averaging']) try: self.m_Binning.SetValue(metadata_stringified[u'Measurement_Binning']) except: self.m_Binning.SetValue(metadata_stringified[u'Binning']) self.m_Threshold.SetValue(metadata_stringified[u'Threshold_mA']) # waveform data self.m_Intensity.SetValue(metadata_stringified[u'Intensity_v']) self.m_Waveform.SetStringSelection(metadata_stringified[u'Waveform']) self.m_Offset_Before.SetValue(metadata_stringified[u'Offset_Before_ms']) self.m_Offset_After.SetValue(metadata_stringified[u'Offset_After_ms']) self.m_Period.SetValue(metadata_stringified[u'Peroid_s']) dialog.Destroy() event.Skip() def onLoadData(self, event): """ Handlers loading of new data set into Frame """ print("start data loading") if self.askUserForFilename(style=wx.OPEN, **self.defaultFileDialogOptions()): fullpath = os.path.join(self.dirname, self.data_file) self.Data.updateRawData(utils.load_data(fullpath)) print(self.Data.Data) pub.sendMessage('update.plot') self.onWaveformParameters(self, event) self.onCollectionParameters(self, event) def onExit(self, event): self.Close() def OnAbout(self, event): dialog = wx.MessageDialog( self, 'An PV experimental assistant in wxPython', 'About PVapp', wx.OK ) dialog.ShowModal() dialog.Destroy() def onStatusUpdate(self, status, is_error=False): self.SetStatusText(status, 0) if is_error: self.m_statusBar.SetBackgroundColour('RED') self.m_statusBar.SetStatusText(status) else: self.m_statusBar.SetStatusText(status)
def setUp(self): self.settings = ExperimentSettings() self.lp = LightPulse(self.settings)
class MeasurementHandlerTest(unittest.TestCase): # np.set_printoptions(threshold='nan') def setUp(self): self.settings = ExperimentSettings() self.lp = LightPulse(self.settings) def test__run_thread(self, mock_waveform_thread): mock_waveform_thread.return_value.time = 3 mock_waveform_thread.return_value.Read_Data = np.array([]) handler = MeasurementHandler() handler.add_to_queue(self.lp.create_waveform(), self.settings) # handler._run_thread() def test_single_measurement_no_averaging(self, mock_waveform_thread): handler = MeasurementHandler() handler.add_to_queue(self.lp.create_waveform(), self.settings) return_tuple = (np.array([1, 2, 3, 4, 5, 6]), np.array([0, 1])) with patch.object(handler, '_run_thread', return_value=return_tuple) as method: test_dataset = handler.single_measurement() self.assertEqual(1, method.call_count) np.testing.assert_array_equal( test_dataset, np.array([[0, 1, 3, 5], [1, 2, 4, 6]]) ) self.assertEqual(len(handler._queue), 0) def test_single_measurement_with_averaging(self, mock_waveform_thread): # setup settings = ExperimentSettings() settings.averaging = 2 handler = MeasurementHandler() return_tuple = [ (np.array([2, 3, 4, 5, 6, 7]), np.array([0, 1])), (np.array([3, 4, 5, 6, 7, 8]), np.array([0, 1])), ] handler.add_to_queue(self.lp.create_waveform(), settings) with patch.object(handler, '_run_thread', side_effect=return_tuple) as method: # perform test_dataset = handler.single_measurement() # assert self.assertEqual(2, method.call_count) np.testing.assert_array_equal( test_dataset, np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]) ) self.assertEqual(len(handler._queue), 0) def test_add_to_queue(self, mock_waveform_thread): handler = MeasurementHandler() handler.add_to_queue(self.lp.create_waveform(), self.settings) self.assertEqual(len(handler._queue), 1) @log_capture() def test_series_measurement_empty_queue(self, mock_waveform_thread, log_checker): handler = MeasurementHandler() observed_data_list = handler.series_measurement() self.assertEqual( len(observed_data_list), 0) log_checker.check( ('root', 'INFO', 'Total: 0 measurements performed'), ) @log_capture() def test_series_measurement_one_in_queue(self, mock_waveform_thread, log_checker): # setup handler = MeasurementHandler() print(handler._queue) handler.add_to_queue(self.lp.create_waveform(), self.settings) side_effect_array = [ np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]) ] with patch.object(handler, 'single_measurement', side_effect=side_effect_array) as method: # perform observed_data_list = handler.series_measurement() # assert for observed_data in observed_data_list: np.testing.assert_array_equal( observed_data, np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]) ) log_checker.check( ('root', 'INFO', 'Measurement #1 complete'), ('root', 'INFO', 'Total: 1 measurements performed'), ) @log_capture() def test_series_measurement_multiple_queue(self, mock_waveform_thread, log_checker): side_effect_array = [ np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]), np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]), np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]) ] handler = MeasurementHandler() handler.add_to_queue(self.lp.create_waveform(), self.settings) handler.add_to_queue(self.lp.create_waveform(), self.settings) handler.add_to_queue(self.lp.create_waveform(), self.settings) with patch.object(handler, 'single_measurement', side_effect=side_effect_array) as method: observed_data_list = handler.series_measurement() for observed_data in observed_data_list: np.testing.assert_array_equal( observed_data, np.array([[0, 2.5, 4.5, 6.5], [1, 3.5, 5.5, 7.5]]) ) log_checker.check( ('root', 'INFO', 'Measurement #1 complete'), ('root', 'INFO', 'Measurement #2 complete'), ('root', 'INFO', 'Measurement #3 complete'), ('root', 'INFO', 'Total: 3 measurements performed'), )
class LightPulseTest(unittest.TestCase): # np.set_printoptions(threshold='nan') def setUp(self): self.lp = LightPulse(ExperimentSettings()) # def test_init_ def test_can_create_pulse_array(self): pulse_array = self.lp.create_waveform() self.assertTrue(np.any(pulse_array)) # make value self.assertTrue(np.any(np.amax(pulse_array) < 150.0)) self.assertTrue(self.lp.A > 0) initial_offset = int(1.2) np.testing.assert_array_equal(np.zeros(initial_offset), pulse_array[:initial_offset]) final_offset = int(12.0) np.testing.assert_array_equal(np.zeros(final_offset), pulse_array[-final_offset:]) self.assertEqual(len(pulse_array[initial_offset:-final_offset]), 1200) def test_scale_to_threshold(self): # setup tests self.lp.Voltage_Threshold = 0.08152173913043478 voltage_waveform = 6 * np.ones(10) test_array = np.array([ 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174, 5.83695652174 ]) # scaled_array = self.lp.scale_to_threshold(voltage_waveform) # assertions np.testing.assert_almost_equal(scaled_array, test_array, decimal=11, verbose=True) def test_final_pulse_array_function(self): example_array = np.loadtxt( "test/data/sample_sin_waveformthread_write_data.csv") self.lp.Waveform = "Sin" pulse_array = self.lp.create_waveform() np.savetxt('test_sine_array', pulse_array) np.testing.assert_almost_equal(pulse_array, example_array) def test_sin_function(self): pulse_array = self.lp.Sin(np.linspace(0, 1, 1200), 0.5) t = np.linspace(0, 1, 1200) amp = 0.5 pi = np.pi np.testing.assert_array_equal(pulse_array, -(amp) * np.abs(np.sin(pi * t / t[-1]))) def test_square_function(self): pulse_array = self.lp.Square(np.linspace(0, 5, 5), -6) self.assertTrue(np.all(pulse_array == (6 * np.ones(5)))) # def test_cos_function(self): # pass def test_triangle_function(self): pulse_array = self.lp.Triangle(np.arange(5), -4) self.assertTrue(np.all(pulse_array == np.array([0, 2, 4, 2, 0])))