def __init__(self): super(GainOffset, self).__init__() #log.debug("creation") from barbecue.ui.GainOffset_layout import Ui_MainWindow self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setGeometry(450, 350, 1080, 600) self.replace_widgets() self.last_model = None self.set_app_defaults() self.setup_signals() # It's necessary to create the dialog objects ahead of time so # the test scripts can click their buttons self.file_dialog = QtGui.QFileDialog() self.show()
class GainOffset(QtGui.QMainWindow): """ The main interface for the GainOffset application. Can be created from unittest or a main() for full test coverage. """ def __init__(self): super(GainOffset, self).__init__() #log.debug("creation") from barbecue.ui.GainOffset_layout import Ui_MainWindow self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setGeometry(450, 350, 1080, 600) self.replace_widgets() self.last_model = None self.set_app_defaults() self.setup_signals() # It's necessary to create the dialog objects ahead of time so # the test scripts can click their buttons self.file_dialog = QtGui.QFileDialog() self.show() def replace_widgets(self): """ From: http://stackoverflow.com/questions/4625102/\ how-to-replace-a-widget-with-another-using-qt Replace the current placeholders from qt designer with the custom widgets. """ # Create the new widget self.ui.image_dialog = NoButtonImageDialog() # Remove the placeholder widget from the layout liph = self.ui.labelImagePlaceholder vli = self.ui.verticalLayout vli.removeWidget(liph) liph.close() # Add the new widget to the layout vli.insertWidget(0, self.ui.image_dialog) vli.update() def set_app_defaults(self): """ Setup preliminary values of all widgets. """ # Hide the progress bar for better startup looks self.ui.progressBar.setVisible(False) self.ui.spinBoxGainStart.setMinimum(0) self.ui.spinBoxGainStart.setMaximum(254) self.ui.spinBoxGainStart.setValue(0) self.ui.spinBoxGainEnd.setMinimum(1) self.ui.spinBoxGainEnd.setMaximum(255) self.ui.spinBoxGainEnd.setValue(255) self.ui.spinBoxOffsetStart.setMinimum(0) self.ui.spinBoxOffsetStart.setMaximum(254) self.ui.spinBoxOffsetStart.setValue(0) self.ui.spinBoxOffsetEnd.setMinimum(1) self.ui.spinBoxOffsetEnd.setMaximum(255) self.ui.spinBoxOffsetEnd.setValue(255) self.ui.spinBoxLineTime.setMinimum(25) self.ui.spinBoxLineTime.setMaximum(1000) self.ui.spinBoxLineTime.setValue(100) self.ui.spinBoxIntegrationTime.setMinimum(2) self.ui.spinBoxIntegrationTime.setMaximum(98) self.ui.spinBoxIntegrationTime.setValue(98) # Create a datamodel, add it to the widget self.datamod = QtGui.QStandardItemModel() self.model_header = QtCore.QStringList() self.model_header.append('Offset') self.model_header.append('Gain') self.datamod.setHorizontalHeaderLabels(self.model_header) self.ui.treeView.setModel(self.datamod) # Trigger an update of the text self.update_summary() # Create the timer so the test controller can have access to the # various parts of the gain/offset loop control self.process_timer = QtCore.QTimer() self.process_timer.setSingleShot(True) self.process_timer.timeout.connect(self.loop_process) # Progress indicators for data saving self.save_timer = QtCore.QTimer() self.save_timer.setSingleShot(True) self.save_timer.timeout.connect(self.loop_save) self.load_timer = QtCore.QTimer() self.load_timer.setSingleShot(True) self.load_timer.timeout.connect(self.loop_load) # When the save file action is activated, it needs to wait for # the filename selection dialog to close first. Signal is # connected with lambda function to filename below self.save_wait_timer = QtCore.QTimer() self.save_wait_timer.setSingleShot(True) self.load_wait_timer = QtCore.QTimer() self.load_wait_timer.setSingleShot(True) # Load / save / process parameters self.load_position = 0 self.orig_gain_start = 0 self.linetime = self.ui.spinBoxLineTime.value() self.integration = self.ui.spinBoxIntegrationTime.value() def setup_signals(self): """ Configure widget signals. """ spgs = self.ui.spinBoxGainStart spgs.valueChanged.connect(self.move_gain_range) spgs.valueChanged.connect(self.update_summary) spos = self.ui.spinBoxOffsetStart spos.valueChanged.connect(self.move_offset_range) spos.valueChanged.connect(self.update_summary) splt = self.ui.spinBoxLineTime splt.valueChanged.connect(self.move_linetime) #self.ui.toolButtonStart.clicked.connect(self.start_process) self.ui.toolButtonStart.clicked.connect(self.setup_process) self.ui.toolButtonStop.clicked.connect(self.stop_process) # If start of end ranges change, update the summary text spge = self.ui.spinBoxGainEnd spge.valueChanged.connect(self.update_summary) spoe = self.ui.spinBoxOffsetEnd spoe.valueChanged.connect(self.update_summary) # Open/Save results self.ui.actionOpen.triggered.connect(self.open_process) self.ui.actionSave.triggered.connect(self.save_process) # Tree list widget updates image on click self.ui.treeView.clicked.connect(self.update_image) def update_image(self): """ Based on the tree view selected datamodel item, build an image in the dialog. """ idx = self.ui.treeView.selectedIndexes() item = self.datamod.item(idx[0].row(), idx[0].column()) self.results_to_image(item) def results_to_image(self, item): """ given an item from the datamodel that has results of gain 0-255, construct a numpy array and assign that to the image. """ src_data = [] for gain_row in item.results: src_data.append(gain_row.data) #log.info("How many rows: %s" % len(src_data)) img_data = range(len(src_data)) position = 0 while position < len(img_data): img_data[position] = src_data[position] position += 1 new_data = numpy.array(img_data).astype(float) local_plot = self.ui.image_dialog.get_plot() # Apparently get_default_item is not supported by the python xy # implementation of guiqwt. #plot.get_default_item().set_data(new_data) first = local_plot.get_items()[1] first.set_data(new_data) local_plot.replot() def open_process(self): """ Get a filename to load. """ file_name = self.file_dialog.getOpenFileName() # Trigger the timer after the dialog has had a chance to close self.load_wait_timer.timeout.connect(lambda: self.load_file(file_name)) self.load_wait_timer.start(500) def load_file(self, file_name): """ Set the progress bar indicators and convert a csv file to datamodel. """ total = self.get_line_total(file_name) msg = "Loading %s combinations from %s" % (total, file_name) self.ui.labelProcessing.setText(msg) self.ui.progressBar.total = total self.ui.progressBar.setValue(0) self.op_count = 0 csv_header = ["Offset", "Gain", "Line Time", "Integration Time"] self.csv_file = csv.DictReader(open(file_name, 'rb'), csv_header, 'Data') # Read past the header: self.csv_file.next() self.last_model = None self.loop_load() def loop_load(self): """ Iterate through the file data to be loaded inside a timer, update the interface progress. """ line_fail = 0 try: line = self.csv_file.next() #log.info("File header: %s" % line['Offset']) except StopIteration: line_fail = 1 log.warn("Problem reading file") self.load_position += 1 self.update_progress_bar() if line_fail: log.info("Load complete, add final") last_entry = self.last_model.results[0] offs_it = QtGui.QStandardItem(str(last_entry.offset)) offs_it.results = self.last_model.results gain_it = QtGui.QStandardItem("Gain 0-255") self.datamod.appendRow([offs_it, gain_it]) return new_data = [] for item in line["Data"][0:2047]: new_data.append(float(item)) new_result = model.Result(line["Gain"], line["Offset"], line["Line Time"], line["Integration Time"], new_data ) # If it's the very first pass, just assign it if self.last_model == None: self.last_model = model.Model() # compare current line offset to last offset, if different, add last # item, start a new item new_offset = new_result.offset if len(self.last_model.results) == 0: old_offset = new_offset else: old_offset = self.last_model.results[0].offset if new_offset != old_offset: log.info("Offset change, add to model") offs_it = QtGui.QStandardItem(str(old_offset)) offs_it.results = self.last_model.results gain_it = QtGui.QStandardItem("Gain 0-255") self.datamod.appendRow([offs_it, gain_it]) self.last_model = model.Model() self.last_model.results.append(new_result) if self.load_position < self.ui.progressBar.total: self.load_timer.start(0) def get_line_total(self, file_name): """ Make a pass through the file, read the total number of lines. """ lfile = open(file_name) lcount = 0 for _ in lfile.readlines(): lcount += 1 lfile.close() return lcount def save_process(self): """ Select a filename to save the current results. """ file_name = self.file_dialog.getSaveFileName() # Trigger the timer after the dialog has had a chance to close self.save_wait_timer.timeout.connect(lambda: self.save_file(file_name)) self.save_wait_timer.start(300) def save_file(self, file_name): """ Write the current contents of the datamodel displayed in the tree widget to disk. """ total = self.datamod.rowCount() * 255 msg = "Saving %s combinations to %s" % (total, file_name) self.ui.labelProcessing.setText(msg) self.ui.progressBar.total = total self.ui.progressBar.setValue(0) self.op_count = 0 self.csv_file = open(file_name, "wb") self.write_header(self.csv_file) self.save_position = 0 self.loop_save() def loop_save(self): """ Iterate through the data to be saved in a timer to enable load inhibits and progress bar updates. """ item = self.datamod.item(self.save_position, 0) log.info("Write item: %s", item) self.write_results(self.csv_file, item) self.save_position += 1 if self.save_position < self.datamod.rowCount(): self.save_timer.start(0) def write_results(self, csv_file, item): """ Print the contents of the datamodel item to disk. """ for result in item.results: csv_file.write("%s," % result.offset) csv_file.write("%s," % result.gain) csv_file.write("%s," % result.linetime) csv_file.write("%s," % result.integration) for pixel in result.data: csv_file.write("%s," % pixel) csv_file.write("\n") self.update_progress_bar() def write_header(self, csv_file): """ write the csv file format header to the passed in file. """ head_str = "Offset,Gain,Line Time,Integration Time,Data\n" csv_file.write(head_str) def update_summary(self): """ Create a summary text showing how many iterations will be processed. """ #log.info("update summary") offset_range = self.ui.spinBoxOffsetEnd.value() - \ self.ui.spinBoxOffsetStart.value() + 1 gain_range = self.ui.spinBoxGainEnd.value() - \ self.ui.spinBoxGainStart.value() + 1 total = offset_range * gain_range msg = "System will process %s combinations." % total self.ui.labelProcessing.setText(msg) # Store the current total combinations for use by the progress # bar update function self.ui.progressBar.total = total self.ui.progressBar.setValue(0) def setup_process(self): """ Configure the test iteration based on set parameters, trigger the loop timer to start the process. """ log.info("Start setup") self.ui.progressBar.setVisible(True) self.orig_gain_start = self.ui.spinBoxGainStart.value() self.orig_gain_end = self.ui.spinBoxGainEnd.value() self.orig_offset_start = self.ui.spinBoxOffsetStart.value() self.orig_offset_end = self.ui.spinBoxOffsetEnd.value() self.offset = self.orig_offset_start self.linetime = self.ui.spinBoxLineTime.value() self.integration = self.ui.spinBoxIntegrationTime.value() self.op_count = 0 self.acquire_model = model.Model() self.acquire_model.assign("single") self.process_timer.start(0) def loop_process(self): """ Once the timer has been activated, loop through the test iteration structure until all offset/gain options have been processed. """ gain = self.orig_gain_start while gain <= self.orig_gain_end: #log.debug("Gain: %s" % gain) result = self.acquire_model.scan(gain, self.offset, self.linetime, self.integration ) if not result: log.critical("Problem scan %s, %s", gain, self.offset) self.update_progress_bar() gain += 1 offs_it = QtGui.QStandardItem(str(self.offset)) offs_it.results = self.acquire_model.results # Reset the results list for next pass self.acquire_model.results = [] #log.info("Store results: %s" % len(offs_it.results)) gain_it = QtGui.QStandardItem("Gain 0-255") self.datamod.appendRow([offs_it, gain_it]) self.offset += 1 if self.offset <= self.orig_offset_end: if not self.process_timer.isActive(): #log.info("Start timer: %s" % self.offset) self.process_timer.start(10) else: log.info("end offset loop") def stop_process(self): """ set the global variable to inhibit a running process, reset gui items. """ self.process_timer.stop() self.save_timer.stop() def update_progress_bar(self): """ Given a op_count value, assign the progress bar to the percentage of total operations. """ self.op_count += 1 self.op_count = self.op_count * 1.0 tot = self.ui.progressBar.total * 1.0 perc = (self.op_count / tot) * 100.0 self.ui.progressBar.setValue(perc) def move_linetime(self): """ Change the integration time range to make sure the user cannot set it more than 2 - line time. """ lt_value = self.ui.spinBoxLineTime.value() self.ui.spinBoxIntegrationTime.setMaximum(lt_value - 2) def move_gain_range(self): """ Make sure the end value is always at least 1 more than the start value. """ gs_value = self.ui.spinBoxGainStart.value() self.ui.spinBoxGainEnd.setMinimum(gs_value + 1) def move_offset_range(self): """ Make sure the end value is always at least 1 more than the start value. """ os_value = self.ui.spinBoxOffsetStart.value() self.ui.spinBoxOffsetEnd.setMinimum(os_value + 1)