From 173e44c797aed2d3385338ec40912d686c885124 Mon Sep 17 00:00:00 2001 From: Wenduo Zhou <zhouw@ornl.gov> Date: Fri, 9 Oct 2015 07:12:43 -0400 Subject: [PATCH] Refs #12122. Added python classes. --- .../HFIR_4Circle_Reduction/NTableWidget.py | 253 ++++ .../fourcircle_utility.py | 188 +++ scripts/HFIR_4Circle_Reduction/guiutility.py | 169 +++ scripts/HFIR_4Circle_Reduction/hfctables.py | 364 +++++ .../HFIR_4Circle_Reduction/mplgraphicsview.py | 1255 +++++++++++++++++ 5 files changed, 2229 insertions(+) create mode 100644 scripts/HFIR_4Circle_Reduction/NTableWidget.py create mode 100644 scripts/HFIR_4Circle_Reduction/fourcircle_utility.py create mode 100644 scripts/HFIR_4Circle_Reduction/guiutility.py create mode 100644 scripts/HFIR_4Circle_Reduction/hfctables.py create mode 100644 scripts/HFIR_4Circle_Reduction/mplgraphicsview.py diff --git a/scripts/HFIR_4Circle_Reduction/NTableWidget.py b/scripts/HFIR_4Circle_Reduction/NTableWidget.py new file mode 100644 index 00000000000..d7a2ed43bb8 --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/NTableWidget.py @@ -0,0 +1,253 @@ +#pylint: disable=C0103,R0904 +# N(DAV)TableWidget +# + +from PyQt4 import QtGui, QtCore + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + + +class NTableWidget(QtGui.QTableWidget): + """ + NdavTableWidget inherits from QTableWidget by extending the features + for easy application. + """ + def __init__(self, parent): + """ + + :param parent: + :return: + """ + QtGui.QTableWidget.__init__(self, parent) + + self._myParent = parent + + self._myHeaderList = None + self._myColumnTypeList = None + + return + + def append_row(self, row_value_list, type_list=None): + """ + + :param row_value_list: + :return: + """ + # Check input + assert isinstance(row_value_list, list) + if type_list is not None: + assert isinstance(type_list, list) + assert len(row_value_list) == len(type_list) + else: + type_list = self._myColumnTypeList + if len(row_value_list) != self.columnCount(): + ret_msg = 'Input number of values (%d) is different from ' \ + 'column number (%d).' % (len(row_value_list), self.columnCount()) + return False, ret_msg + else: + ret_msg = '' + + # Insert new row + row_number = self.rowCount() + self.insertRow(row_number) + + # Set values + for i_col in xrange(min(len(row_value_list), self.columnCount())): + item = QtGui.QTableWidgetItem() + item.setText(_fromUtf8(str(row_value_list[i_col]))) + item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable) + if type_list[i_col] == 'checkbox': + self.set_check_box(row_number, i_col, False) + else: + self.setItem(row_number, i_col, item) + # END-FOR(i_col) + + return True, ret_msg + + def get_selected_rows(self): + """ + + :return: + """ + rows_list = list() + index_status = self._myColumnTypeList.index('checkbox') + for i_row in xrange(self.rowCount()): + is_checked = self.get_row_value(i_row)[index_status] + if is_checked: + rows_list.append(i_row) + + return rows_list + + def get_cell_value(self, row_index, col_index): + """ + + :param row_index: + :param col_index: + :return: + """ + c_type = self._myColumnTypeList[col_index] + + return_value = None + if c_type == 'checkbox': + # Check box + cell_i_j = self.cellWidget(row_index, col_index) + assert isinstance(cell_i_j, QtGui.QCheckBox) + return_value = cell_i_j.isChecked() + else: + # Regular cell + item_i_j = self.item(row_index, col_index) + assert isinstance(item_i_j, QtGui.QTableWidgetItem) + value = str(item_i_j.text()) + if c_type == 'int': + return_value = int(value) + elif c_type == 'float': + return_value = float(value) + + return return_value + + def get_row_value(self, row_index): + """ + :param row_index: + :return: list of objects + """ + if row_index < 0 or row_index >= self.rowCount(): + raise IndexError('Index of row (%d) is out of range.' % row_index) + + ret_list = list() + for i_col in xrange(len(self._myColumnTypeList)): + c_type = self._myColumnTypeList[i_col] + + if c_type == 'checkbox': + # Check box + cell_i_j = self.cellWidget(row_index, i_col) + assert isinstance(cell_i_j, QtGui.QCheckBox) + is_checked = cell_i_j.isChecked() + ret_list.append(is_checked) + else: + # Regular cell + item_i_j = self.item(row_index, i_col) + assert isinstance(item_i_j, QtGui.QTableWidgetItem) + value = str(item_i_j.text()) + if c_type == 'int': + value = int(value) + elif c_type == 'float': + value = float(value) + + ret_list.append(value) + # END-IF-ELSE + # END-FOR + + return ret_list + + def init_setup(self, column_tup_list): + """ Initial setup + :param column_tup_list: list of 2-tuple as string (column name) and string (data type) + :return: + """ + print '[DB] Init set up table with %d columns!' % len(column_tup_list) + + # Define column headings + num_cols = len(column_tup_list) + + # Class variables + self._myHeaderList = list() + self._myColumnTypeList = list() + + for c_tup in column_tup_list: + c_name = c_tup[0] + c_type = c_tup[1] + self._myHeaderList.append(c_name) + self._myColumnTypeList.append(c_type) + + self.setColumnCount(num_cols) + self.setHorizontalHeaderLabels(self._myHeaderList) + + return + + def init_size(self, num_rows, num_cols): + """ + + :return: + """ + self.setColumnCount(num_cols) + self.setRowCount(num_rows) + + return + + def set_check_box(self, row, col, state): + """ function to add a new select checkbox to a cell in a table row + won't add a new checkbox if one already exists + """ + # Check input + assert isinstance(state, bool) + + # Check if cellWidget exists + if self.cellWidget(row,col): + # existing: just set the value + self.cellWidget(row, col).setChecked(state) + else: + # case to add checkbox + checkbox = QtGui.QCheckBox() + checkbox.setText('') + checkbox.setChecked(state) + + # Adding a widget which will be inserted into the table cell + # then centering the checkbox within this widget which in turn, + # centers it within the table column :-) + self.setCellWidget(row, col, checkbox) + # END-IF-ELSE + + return + + def set_value_cell(self, row, col, value=''): + """ + Set value to a cell with integer, float or string + :param row: + :param col: + :param value: + :return: + """ + # Check + if row < 0 or row >= self.rowCount() or col < 0 or col >= self.columnCount(): + raise IndexError('Input row number or column number is out of range.') + + # Init cell + cell_item = QtGui.QTableWidgetItem() + cell_item.setText(_fromUtf8(str(value))) + cell_item.setFlags(cell_item.flags() & ~QtCore.Qt.ItemIsEditable) + + self.setItem(row, col, cell_item) + + return + + def update_cell_value(self, row, col, value): + """ + + :param row: + :param col: + :param value: + :return: + """ + cell_item = self.item(row, col) + cell_widget = self.cellWidget(row, col) + + if cell_item is not None and cell_widget is None: + # TableWidgetItem + assert isinstance(cell_item, QtGui.QTableWidgetItem) + if isinstance(value, float): + cell_item.setText(_fromUtf8('%.7f' % value)) + else: + cell_item.setText(_fromUtf8(str(value))) + elif cell_item is None and cell_widget is not None: + # TableCellWidget + if isinstance(cell_item, QtGui.QCheckBox) is True: + cell_item.setChecked(value) + else: + raise TypeError('Cell of type %s is not supported.' % str(type(cell_item))) + else: + raise TypeError('Table cell (%d, %d) is in an unsupported situation!' % (row, col)) + + return diff --git a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py new file mode 100644 index 00000000000..35dc1fe387e --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py @@ -0,0 +1,188 @@ +#pylint: disable=W0633,too-many-branches +__author__ = 'wzz' + +import os +import urllib2 +import socket + + +def check_url(url, read_lines=False): + """ Check whether a URL is valid + :param url: + :return: boolean, error message + """ + lines = None + try: + # Access URL + url_stream = urllib2.urlopen(url, timeout=2) + + # Read lines + if read_lines is True: + lines = url_stream.readlines() + except urllib2.URLError as url_error: + url_stream = url_error + except socket.timeout: + return False, 'Time out. Try again!' + + # Return result + if url_stream.code in (200, 401): + url_good = True + else: + url_good = False + + # Close connect + url_stream.close() + + # Return + if read_lines is True: + return url_good, lines + if url_good is False: + error_message = 'Unable to access %s. Check internet access. Code %d' % (url, url_stream.code) + else: + error_message = '' + + return url_good, error_message + + +def get_scans_list(server_url, exp_no, return_list=False): + """ Get list of scans under one experiment + :param server_url: + :param exp_no: + :return: message + """ + if server_url.endswith('/') is False: + server_url = '%s/' % server_url + data_dir_url = '%sexp%d/Datafiles' % (server_url, exp_no) + + does_exist, raw_lines = check_url(data_dir_url, read_lines=True) + if does_exist is False: + return "Experiment %d's URL %s cannot be found." % (exp_no, data_dir_url) + + # Scan through the index page + scan_list = [] + header = 'HB3A_exp%04d_scan' % exp_no + for line in raw_lines: + if line.count(header) > 0: + # try to find file HB3A_exp0123_scan6789.dat + term = line.split(header)[1].split('.dat')[0] + scan = int(term) + # check + if '%04d' % scan == term: + scan_list.append(scan) + # END_FOR + scan_list = sorted(scan_list) + if return_list is True: + return scan_list + + message = 'Experiment %d: Scan from %d to %d' % (exp_no, scan_list[0], scan_list[-1]) + + return message + + +def get_scans_list_local_disk(local_dir, exp_no): + """ Get scans from a specified directory on local disk + :param local_dir: + :param exp_no: + :return: + """ + scan_list = [] + + file_names = os.listdir(local_dir) + header = 'HB3A_exp%04d_scan' % exp_no + for name in file_names: + if name.count(header) > 0: + scan = int(name.split(header)[1].split('.dat')[0]) + scan_list.append(scan) + + scan_list = sorted(scan_list) + + if len(scan_list) == 0: + message = 'Experiment %d: No scan can be found.' % exp_no + else: + message = 'Experiment %d: Scan from %d to %d ' % (exp_no, scan_list[0], scan_list[-1]) + num_skip_scans = scan_list[-1] - scan_list[0] + 1 - len(scan_list) + if num_skip_scans > 0: + message += 'with %d ' % num_skip_scans + else: + message += 'without ' + message += 'missing scans.' + + return message + + +def parse_int_array(int_array_str): + """ Validate whether the string can be divided into integer strings. + Allowed: a, b, c-d, e, f + :param int_array_str: + :return: + """ + int_array_str = str(int_array_str) + if int_array_str == "": + return True, [] + + # Split by "," + term_level_0 = int_array_str.split(",") + integer_list = [] + + # For each term + err_msg = "" + ret_status = True + + for level0_term in term_level_0: + level0_term = level0_term.strip() + + # split upon dash - + num_dashes = level0_term.count("-") + if num_dashes == 0: + # one integer + value_str = level0_term + try: + int_value = int(value_str) + if str(int_value) != value_str: + ret_status = False + err_msg = "Contains non-integer string %s." % value_str + except ValueError: + ret_status = False + err_msg = "String %s is not an integer." % (value_str) + else: + integer_list.append(int_value) + + elif num_dashes == 1: + # Integer range + two_terms = level0_term.split("-") + temp_list = [] + for i in xrange(2): + value_str = two_terms[i] + try: + int_value = int(value_str) + if str(int_value) != value_str: + ret_status = False + err_msg = "Contains non-integer string %s." % (value_str) + except ValueError: + ret_status = False + err_msg = "String %s is not an integer." % (value_str) + else: + temp_list.append(int_value) + + # break loop + if ret_status is False: + break + # END_FOR(i) + integer_list.extend(range(temp_list[0], temp_list[1]+1)) + + else: + # Undefined situation + ret_status = False + err_msg = "Term %s contains more than 1 dash." % level0_term + # END-IF-ELSE + + # break loop if something is wrong + if ret_status is False: + break + # END-FOR(level0_term) + + # Return with false + if ret_status is False: + return False, err_msg + + return True, integer_list diff --git a/scripts/HFIR_4Circle_Reduction/guiutility.py b/scripts/HFIR_4Circle_Reduction/guiutility.py new file mode 100644 index 00000000000..386141d4e44 --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/guiutility.py @@ -0,0 +1,169 @@ +# +# GUI Utility Methods +# +from PyQt4 import QtGui + + +def parse_float_array(array_str): + """ Parse a string to an array of float + :param array_str: + :return: boolean, list of floats/error message + """ + print array_str + assert isinstance(array_str, str) + array_str = array_str.replace(',', ' ') + array_str = array_str.replace('\n', ' ') + array_str = array_str.replace('\t ', ' ') + array_str = array_str.strip() + print '[DB] After processing: ', array_str + + float_str_list = array_str.split() + float_list = list() + for float_str in float_str_list: + try: + value = float(float_str) + except ValueError as value_error: + return False, 'Unable to parse %s due to %s.' % (float_str, str(value_error)) + else: + float_list.append(value) + # END-FOR + + return True, float_list + + +def parse_integer_list(array_str): + """ Parse a string to an array of integer separated by ',' + also, the format as 'a-b' is supported too + :param array_str: + :return: boolean, list of floats/error message + """ + assert isinstance(array_str, str) + array_str = array_str.replace(' ', '') + array_str = array_str.replace('\n', '') + array_str = array_str.replace('\t ', '') + + int_str_list = array_str.split(',') + int_list = list() + for int_str in int_str_list: + + try: + int_value = int(int_str) + int_list.append(int_value) + except ValueError: + num_dash = int_str.count('-') + if num_dash == 1: + terms = int_str.split('-') + try: + start_value = int(terms[0]) + end_value = int(terms[1]) + except ValueError: + raise 'Unable to parse %s due to value error' % int_str + elif num_dash == 2 and int_str.startswith('-'): + terms = int_str[1:].split('-') + try: + start_value = int(terms[0])*-1 + end_value = int(terms[1]) + except ValueError: + raise 'Unable to parse %s due to value error' % int_str + elif num_dash == 3: + terms = int_str.split('-') + try: + start_value = -1*int(terms[1]) + end_value = -1*int(terms[3]) + except ValueError: + raise 'Unable to parse %s due to value error' % int_str + except IndexError: + raise 'Unable to parse %s due to value error' % int_str + else: + raise 'Unable to parse %s due to value error' % int_str + + int_list.extend(xrange(start_value, end_value+1)) + # END-FOR + + return int_list + + +def parse_float_editors(line_edits): + """ + :param line_edit_list: + :return: (True, list of floats); (False, error message) + """ + # Set flag + return_single_value = False + + if isinstance(line_edits, QtGui.QLineEdit) is True: + line_edit_list = [line_edits] + return_single_value = True + elif isinstance(line_edits, list) is True: + line_edit_list = line_edits + else: + raise RuntimeError('Input is not LineEdit or list of LineEdit.') + + error_message = '' + float_list = [] + + for line_edit in line_edit_list: + assert isinstance(line_edit, QtGui.QLineEdit) + try: + str_value = str(line_edit.text()).strip() + float_value = float(str_value) + except ValueError as value_err: + error_message += 'Unable to parse to integer. %s\n' % (str(value_err)) + else: + float_list.append(float_value) + # END-TRY + # END-FOR + + if len(error_message) > 0: + return False, error_message + elif return_single_value is True: + return True, float_list[0] + + return True, float_list + + +def parse_integers_editors(line_edits): + """ + :param line_edit_list: + :return: (True, list of integers); (False, error message) + """ + # Set flag + return_single_value = False + + if isinstance(line_edits, QtGui.QLineEdit) is True: + line_edit_list = [line_edits] + return_single_value = True + elif isinstance(line_edits, list) is True: + line_edit_list = line_edits + else: + raise RuntimeError('Input is not LineEdit or list of LineEdit.') + + error_message = '' + integer_list = [] + + for line_edit in line_edit_list: + assert isinstance(line_edit, QtGui.QLineEdit) + try: + str_value = str(line_edit.text()).strip() + int_value = int(str_value) + except ValueError as value_err: + error_message += 'Unable to parse to integer. %s\n' % (str(value_err)) + else: + if str_value != '%d' % int_value: + error_message += 'Value %s is not a proper integer.\n' % str_value + else: + integer_list.append(int_value) + # END-TRY + # END-FOR + + if len(error_message) > 0: + return False, error_message + elif return_single_value is True: + return True, integer_list[0] + + return True, integer_list + + +if __name__ == '__main__': + int_list = parse_integer_list('123, -234, 330-350, 400, -2-4') + print int_list diff --git a/scripts/HFIR_4Circle_Reduction/hfctables.py b/scripts/HFIR_4Circle_Reduction/hfctables.py new file mode 100644 index 00000000000..cb1b45624fc --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/hfctables.py @@ -0,0 +1,364 @@ +#pylint: disable=W0403,C0103,R0901,R0904 +import numpy +import NTableWidget as tableBase + +# UB peak information table +Peak_Integration_Table_Setup = [('Scan', 'int'), + ('Pt', 'int'), + ('H', 'float'), + ('K', 'float'), + ('L', 'float'), + ('Q_x', 'float'), + ('Q_y', 'float'), + ('Q_z', 'float'), + ('Intensity', 'float')] + + +class IntegratePeaksTableWidget(tableBase.NTableWidget): + """ + Extended table widget for peak integration + """ + def __init__(self, parent): + """ + :param parent: + """ + tableBase.NTableWidget.__init__(self, parent) + + return + + def setup(self): + """ + Init setup + :return: + """ + self.init_setup(Peak_Integration_Table_Setup) + + return + + +class UBMatrixTable(tableBase.NTableWidget): + """ + Extended table for UB matrix + """ + def __init__(self, parent): + """ + + :param parent: + :return: + """ + tableBase.NTableWidget.__init__(self, parent) + + # Matrix + self._matrix = numpy.ndarray((3, 3), float) + for i in xrange(3): + for j in xrange(3): + self._matrix[i][j] = 0. + + return + + def _set_to_table(self): + """ + TODO/DOC + :return: + """ + for i_row in xrange(3): + for j_col in xrange(3): + self.update_cell_value(i_row, j_col, self._matrix[i_row][j_col]) + + return + + def get_matrix(self): + """ + Get the copy of the matrix + :return: + """ + print '[DB] MatrixTable: _Matrix = ', self._matrix + return self._matrix.copy() + + def set_from_list(self, element_array): + """ + TODO/DOC + :param element_array: + :return: + """ + # Check + assert isinstance(element_array, list) + assert len(element_array) == 9 + + # Set value + i_array = 0 + for i in xrange(3): + for j in xrange(3): + self._matrix[i][j] = element_array[i_array] + i_array += 1 + + # Set to table + self._set_to_table() + + return + + def set_from_matrix(self, matrix): + """ + TODO - DOC + :param matrix: + :return: + """ + # Check + assert isinstance(matrix, numpy.ndarray) + assert matrix.shape == (3, 3) + + for i in xrange(3): + for j in xrange(3): + self._matrix[i][j] = matrix[i][j] + + self._set_to_table() + + return + + def setup(self): + """ + Init setup + :return: + """ + # self.init_size(3, 3) + + for i in xrange(3): + for j in xrange(3): + self.set_value_cell(i, j) + + self._set_to_table() + + return + + +# UB peak information table +UB_Peak_Table_Setup = [('Scan', 'int'), + ('Pt', 'int'), + ('H', 'float'), + ('K', 'float'), + ('L', 'float'), + ('Q_x', 'float'), + ('Q_y', 'float'), + ('Q_z', 'float'), + ('Use', 'checkbox'), + ('m1', 'float'), + ('Error', 'float')] + + +class UBMatrixPeakTable(tableBase.NTableWidget): + """ + Extended table for peaks used to calculate UB matrix + """ + def __init__(self, parent): + """ + + :param parent: + :return: + """ + tableBase.NTableWidget.__init__(self, parent) + + return + + def get_exp_info(self, row_index): + """ + Get experiment information from a row + :return: scan number, pt number + """ + assert isinstance(row_index, int) + + scan_number = self.get_cell_value(row_index, 0) + assert isinstance(scan_number, int) + pt_number = self.get_cell_value(row_index, 1) + assert isinstance(pt_number, int) + + return scan_number, pt_number + + def get_hkl(self, row_index): + """ + Get reflection's miller index + :param row_index: + :return: + """ + assert isinstance(row_index, int) + + m_h = self.get_cell_value(row_index, 2) + m_k = self.get_cell_value(row_index, 3) + m_l = self.get_cell_value(row_index, 4) + + assert isinstance(m_h, float) + assert isinstance(m_k, float) + assert isinstance(m_l, float) + + return m_h, m_k, m_l + + def is_selected(self, row_index): + """ + + :return: + """ + if row_index < 0 or row_index >= self.rowCount(): + raise IndexError('Input row number %d is out of range [0, %d)' % (row_index, self.rowCount())) + + col_index = UB_Peak_Table_Setup.index(('Use', 'checkbox')) + + return self.get_cell_value(row_index, col_index) + + def setup(self): + """ + Init setup + :return: + """ + self.init_setup(UB_Peak_Table_Setup) + + return + + def set_hkl(self, i_row, hkl, error=None): + """ + Set HKL to table + :param irow: + :param hkl: + """ + # Check + assert isinstance(i_row, int) + assert isinstance(hkl, list) + + i_col_h = UB_Peak_Table_Setup.index(('H', 'float')) + i_col_k = UB_Peak_Table_Setup.index(('K', 'float')) + i_col_l = UB_Peak_Table_Setup.index(('L', 'float')) + + self.update_cell_value(i_row, i_col_h, hkl[0]) + self.update_cell_value(i_row, i_col_k, hkl[1]) + self.update_cell_value(i_row, i_col_l, hkl[2]) + + if error is not None: + i_col_error = UB_Peak_Table_Setup.index(('Error', 'float')) + self.update_cell_value(i_row, i_col_error, error) + + return + +# Processing status table +Process_Table_Setup = [('Scan', 'int'), + ('Number Pt', 'int'), + ('Status', 'str'), + ('Merged Workspace', 'str'), + ('Group Name', 'str'), + ('Select', 'checkbox')] + + +class ProcessTableWidget(tableBase.NTableWidget): + """ + Extended table for peaks used to calculate UB matrix + """ + def __init__(self, parent): + """ + + :param parent: + :return: + """ + tableBase.NTableWidget.__init__(self, parent) + + return + + def append_scans(self, scans): + """ Append rows + :param scans: + :return: + """ + # Check + assert isinstance(scans, list) + + # Append rows + for scan in scans: + row_value_list = [scan, 0, 'In Queue', '', '', False] + status, err = self.append_row(row_value_list) + if status is False: + raise RuntimeError(err) + + return + + def setup(self): + """ + Init setup + :return: + """ + self.init_setup(Process_Table_Setup) + + return + + def set_scan_pt(self, scan_no, pt_list): + """ + :param scan_no: + :param pt_list: + :return: + """ + # Check + assert isinstance(scan_no, int) + + num_rows = self.rowCount() + set_done = False + for i_row in xrange(num_rows): + tmp_scan_no = self.get_cell_value(i_row, 0) + if scan_no == tmp_scan_no: + self.update_cell_value(i_row, 1, len(pt_list)) + set_done = True + break + # END-FOR + + if set_done is False: + return 'Unable to find scan %d in table.' % scan_no + + return '' + + def set_status(self, scan_no, status): + """ + TODO/Doc + :param status: + :return: + """ + # Check + assert isinstance(scan_no, int) + + num_rows = self.rowCount() + set_done = False + for i_row in xrange(num_rows): + tmp_scan_no = self.get_cell_value(i_row, 0) + if scan_no == tmp_scan_no: + self.update_cell_value(i_row, 2, status) + set_done = True + break + # END-FOR + + if set_done is False: + return 'Unable to find scan %d in table.' % scan_no + + return '' + + def set_ws_names(self, scan_num, merged_md_name, ws_group_name): + """ + TODO/DOC + :param merged_md_name: + :param ws_group_name: + :return: + """ + # Check + assert isinstance(scan_num, int) + assert isinstance(merged_md_name, str) or merged_md_name is None + assert isinstance(ws_group_name, str) or ws_group_name is None + + num_rows = self.rowCount() + set_done = False + for i_row in xrange(num_rows): + tmp_scan_no = self.get_cell_value(i_row, 0) + if scan_num == tmp_scan_no: + if merged_md_name is not None: + self.update_cell_value(i_row, 3, merged_md_name) + if ws_group_name is not None: + self.update_cell_value(i_row, 4, ws_group_name) + set_done = True + break + # END-FOR + + if set_done is False: + return 'Unable to find scan %d in table.' % scan_num + + return diff --git a/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py new file mode 100644 index 00000000000..152ffc29c47 --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py @@ -0,0 +1,1255 @@ +#pylint: disable=invalid-name,too-many-public-methods,too-many-arguments,non-parent-init-called,R0902,too-many-branches,C0302 +import os +import numpy as np + +from PyQt4 import QtGui + +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar2 +from matplotlib.figure import Figure +import matplotlib.image + +MplLineStyles = ['-' , '--' , '-.' , ':' , 'None' , ' ' , ''] +MplLineMarkers = [ + ". (point )", + "* (star )", + "x (x )", + "o (circle )", + "s (square )", + "D (diamond )", + ", (pixel )", + "v (triangle_down )", + "^ (triangle_up )", + "< (triangle_left )", + "> (triangle_right)", + "1 (tri_down )", + "2 (tri_up )", + "3 (tri_left )", + "4 (tri_right )", + "8 (octagon )", + "p (pentagon )", + "h (hexagon1 )", + "H (hexagon2 )", + "+ (plus )", + "d (thin_diamond )", + "| (vline )", + "_ (hline )", + "None (nothing )"] + +# Note: in colors, "white" is removed +MplBasicColors = [ + "black", + "red", + "blue", + "green", + "cyan", + "magenta", + "yellow"] + + +class IndicatorManager(object): + """ Manager for all indicator lines + """ + def __init__(self): + """ + + :return: + """ + # Auto color index + self._colorIndex = 0 + # Auto line ID + self._autoLineID = 1 + + self._lineManager = dict() + self._canvasLineKeyDict = dict() + self._indicatorTypeDict = dict() # value: 0 (horizontal), 1 (vertical), 2 (2-way) + + return + + def add_2way_indicator(self, x, x_min, x_max, y, y_min, y_max, color): + """ + + :param x: + :param x_min: + :param x_max: + :param y: + :param y_min: + :param y_max: + :param color: + :return: + """ + # Set up indicator ID + this_id = str(self._autoLineID) + self._autoLineID += 1 + + # Set up vectors + vec_x_horizontal = np.array([x_min, x_max]) + vec_y_horizontal = np.array([y, y]) + + vec_x_vertical = np.array([x, x]) + vec_y_vertical = np.array([y_min, y_max]) + + # + self._lineManager[this_id] = [vec_x_horizontal, vec_y_horizontal, vec_x_vertical, vec_y_vertical, color] + self._indicatorTypeDict[this_id] = 2 + + return this_id + + def add_horizontal_indicator(self, y, x_min, x_max, color): + """ + Add a horizontal indicator + :param y: + :param x_min: + :param x_max: + :param color: + :return: + """ + # Get ID + this_id = str(self._autoLineID) + self._autoLineID += 1 + + # + vec_x = np.array([x_min, x_max]) + vec_y = np.array([y, y]) + + # + self._lineManager[this_id] = [vec_x, vec_y, color] + self._indicatorTypeDict[this_id] = 0 + + return this_id + + def add_vertical_indicator(self, x, y_min, y_max, color): + """ + Add a vertical indicator to data structure + :return: indicator ID + """ + # Get ID + this_id = str(self._autoLineID) + self._autoLineID += 1 + + # + vec_x = np.array([x, x]) + vec_y = np.array([y_min, y_max]) + + # + self._lineManager[this_id] = [vec_x, vec_y, color] + self._indicatorTypeDict[this_id] = 1 + + return this_id + + def get_canvas_line_index(self, my_id): + """ + + :param my_id: + :return: + """ + assert isinstance(my_id, str) + + if my_id not in self._canvasLineKeyDict: + raise RuntimeError('Indicator ID %s cannot be found. Current keys are %s.' % ( + my_id, str(sorted(self._canvasLineKeyDict.keys())) + )) + return self._canvasLineKeyDict[my_id] + + def get_line_type(self, my_id): + """ + + :param my_id: + :return: + """ + return self._indicatorTypeDict[my_id] + + def get_2way_data(self, line_id): + """ + + :param line_id: + :return: + """ + assert self._indicatorTypeDict.has_key(line_id) + assert self._indicatorTypeDict[line_id] == 2 + + vec_set = [self._lineManager[line_id][0:2], self._lineManager[line_id][2:4]] + + return vec_set + + def get_data(self, line_id): + """ + Get line's vector x and vector y + :param line_id: + :return: + """ + return self._lineManager[line_id][0], self._lineManager[line_id][1] + + def get_line_style(self, line_id=None): + """ + + :param line_id: + :return: + """ + assert isinstance(line_id, None) + return '--' + + def get_live_indicator_ids(self): + """ + + :return: + """ + return sorted(self._lineManager.keys()) + + def get_marker(self): + """ + Get the marker a line + :param line_id: + :return: + """ + return 'o' + + def get_next_color(self): + """ + Get next color by auto color index + :return: string as color + """ + next_color = MplBasicColors[self._colorIndex] + + # Advance and possibly reset color scheme + self._colorIndex += 1 + if self._colorIndex == len(MplBasicColors): + self._colorIndex = 0 + + return next_color + + def set_canvas_line_index(self, my_id, canvas_line_index): + """ + + :param my_id: + :param canvas_line_index: + :return: + """ + self._canvasLineKeyDict[my_id] = canvas_line_index + + def shift(self, my_id, dx, dy): + """ + + :param my_id: + :param dx: + :param dy: + :return: + """ + print self._lineManager[my_id][0] + + if self._indicatorTypeDict[my_id] == 0: + # horizontal + self._lineManager[my_id][1] += dy + + elif self._indicatorTypeDict[my_id] == 1: + # vertical + self._lineManager[my_id][0] += dx + + elif self._indicatorTypeDict[my_id] == 2: + # 2-way + self._lineManager[my_id][2] += dx + self._lineManager[my_id][1] += dy + + else: + raise RuntimeError('Unsupported indicator of type %d' % self._indicatorTypeDict[my_id]) + + return + + def update_indicators_range(self, x_range, y_range): + """ + Update indicator's range + :param x_range: + :param y_range: + :return: + """ + for i_id in self._lineManager.keys(): + # NEXT - Need a new flag for direction of the indicating line, vertical or horizontal + if True: + self._lineManager[i_id][1][0] = y_range[0] + self._lineManager[i_id][1][-1] = y_range[1] + else: + self._lineManager[i_id][0][0] = x_range[0] + self._lineManager[i_id][0][-1] = x_range[1] + + return + + +class MplGraphicsView(QtGui.QWidget): + """ A combined graphics view including matplotlib canvas and + a navigation tool bar + + Note: Merged with HFIR_Powder_Reduction.MplFigureCAnvas + """ + def __init__(self, parent): + """ Initialization + """ + # Initialize parent + QtGui.QWidget.__init__(self, parent) + + # set up canvas + self._myCanvas = Qt4MplCanvas(self) + self._myToolBar = MyNavigationToolbar(self, self._myCanvas) + + # set up layout + self._vBox = QtGui.QVBoxLayout(self) + self._vBox.addWidget(self._myCanvas) + self._vBox.addWidget(self._myToolBar) + + # auto line's maker+color list + self._myLineMarkerColorList = [] + self._myLineMarkerColorIndex = 0 + self.setAutoLineMarkerColorCombo() + + # Declaration of class variables + self._indicatorKey = None + + # Indicator manager + self._myIndicatorsManager = IndicatorManager() + + return + + def add_line_set(self, vec_set, color, marker, line_style, line_width): + """ Add a set of line and manage together + :param vec_set: + :param color: + :param marker: + :param line_style: + :param line_width: + :return: + """ + key_list = list() + for vec_x, vec_y in vec_set: + temp_key = self._myCanvas.add_plot_1d(vec_x, vec_y, color=color, marker=marker, + line_style=line_style, line_width=line_width) + assert isinstance(temp_key, int) + assert temp_key >= 0 + key_list.append(temp_key) + + return key_list + + def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label="", x_label=None, y_label=None, + marker=None, line_style=None, line_width=1): + """ Add a new plot + """ + line_key = self._myCanvas.add_plot_1d(vec_x, vec_y, y_err, color, label, x_label, y_label, marker, line_style, + line_width) + + return line_key + + def add_plot_1d_right(self, vec_x, vec_y, color=None, label='', marker=None, line_style=None, line_width=1): + """ + Add 1 line (1-d plot) to right axis + :param vec_x: + :param vec_y: + :param color: + :param label: + :param marker: + :param line_style: + :param line_width: + :return: + """ + line_key = self._myCanvas.add_1d_plot_right(vec_x, vec_y, label=label, + color=color, marker=marker, + linestyle=line_style, linewidth=line_width) + + return line_key + + def add_2way_indicator(self, x=None, y=None, color=None, master_line=None): + """ Add a 2-way indicator following an existing line? + :param x: + :param y: + :param color: + :return: + """ + if master_line is not None: + raise RuntimeError('Implement how to use master_line ASAP.') + + x_min, x_max = self._myCanvas.getXLimit() + if x is None: + x = (x_min + x_max) * 0.5 + else: + assert isinstance(x, float) + + y_min, y_max = self._myCanvas.getYLimit() + if y is None: + y = (y_min + y_max) * 0.5 + else: + assert isinstance(y, float) + + if color is None: + color = self._myIndicatorsManager.get_next_color() + else: + assert isinstance(color, str) + + my_id = self._myIndicatorsManager.add_2way_indicator(x, x_min, x_max, + y, y_min, y_max, + color) + vec_set = self._myIndicatorsManager.get_2way_data(my_id) + + canvas_line_index = self.add_line_set(vec_set, color=color, + marker=self._myIndicatorsManager.get_marker(), + line_style=self._myIndicatorsManager.get_line_style(), + line_width=1) + self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index) + + return my_id + + def add_horizontal_indicator(self, y=None, color=None): + """ Add an indicator line + """ + # Default + if y is None: + y_min, y_max = self._myCanvas.getYLimit() + y = (y_min + y_max) * 0.5 + else: + assert isinstance(y, float) + + x_min, x_max = self._myCanvas.getXLimit() + + # For color + if color is None: + color = self._myIndicatorsManager.get_next_color() + else: + assert isinstance(color, str) + + # Form + my_id = self._myIndicatorsManager.add_horizontal_indicator(y, x_min, x_max, color) + vec_x, vec_y = self._myIndicatorsManager.get_data(my_id) + + canvas_line_index = self._myCanvas.add_plot_1d(vec_x=vec_x, vec_y=vec_y, + color=color, marker=self._myIndicatorsManager.get_marker(), + line_style=self._myIndicatorsManager.get_line_style(), + line_width=1) + + self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index) + + return my_id + + def add_vertical_indicator(self, x=None, color=None): + """ + Add a vertical indicator line + :param x: None as the automatic mode using default from middle of canvas + :param color: None as the automatic mode using default + :return: indicator ID + """ + # For indicator line's position + if x is None: + x_min, x_max = self._myCanvas.getXLimit() + x = (x_min + x_max) * 0.5 + else: + assert isinstance(x, float) + + y_min, y_max = self._myCanvas.getYLimit() + + # For color + if color is None: + color = self._myIndicatorsManager.get_next_color() + else: + assert isinstance(color, str) + + # Form + my_id = self._myIndicatorsManager.add_vertical_indicator(x, y_min, y_max, color) + vec_x, vec_y = self._myIndicatorsManager.get_data(my_id) + + canvas_line_index = self._myCanvas.add_plot_1d(vec_x=vec_x, vec_y=vec_y, + color=color, marker=self._myIndicatorsManager.get_marker(), + line_style=self._myIndicatorsManager.get_line_style(), + line_width=1) + + self._myIndicatorsManager.set_canvas_line_index(my_id, canvas_line_index) + + return my_id + + def add_plot_2d(self, array2d, x_min, x_max, y_min, y_max, hold_prev_image=True, y_tick_label=None): + """ + Add a 2D image to canvas + :param array2d: numpy 2D array + :param x_min: + :param x_max: + :param y_min: + :param y_max: + :param hold_prev_image: + :param y_tick_label: + :return: + """ + self._myCanvas.addPlot2D(array2d, x_min, x_max, y_min, y_max, hold_prev_image, y_tick_label) + + return + + + def addImage(self, imagefilename): + """ Add an image by file + """ + # check + if os.path.exists(imagefilename) is False: + raise NotImplementedError("Image file %s does not exist." % (imagefilename)) + + self._myCanvas.addImage(imagefilename) + + return + + def clear_all_lines(self): + """ + """ + self._myCanvas.clear_all_1d_plots() + + def clear_canvas(self): + """ Clear canvas + """ + return self._myCanvas.clear_canvas() + + def draw(self): + """ Draw to commit the change + """ + return self._myCanvas.draw() + + def evt_view_updated(self): + """ Event handling as canvas size updated + :return: + """ + # update the indicator + new_x_range = self.getXLimit() + new_y_range = self.getYLimit() + + self._myIndicatorsManager.update_indicators_range(new_x_range, new_y_range) + for indicator_key in self._myIndicatorsManager.get_live_indicator_ids(): + canvas_line_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key) + data_x, data_y = self._myIndicatorsManager.get_data(indicator_key) + self.updateLine(canvas_line_id, data_x, data_y) + # END-FOR + + return + + def getPlot(self): + """ + """ + return self._myCanvas.getPlot() + + def getLastPlotIndexKey(self): + """ Get ... + """ + return self._myCanvas.getLastPlotIndexKey() + + def getXLimit(self): + """ Get limit of Y-axis + """ + return self._myCanvas.getXLimit() + + def getYLimit(self): + """ Get limit of Y-axis + """ + return self._myCanvas.getYLimit() + + def move_indicator(self, line_id, dx, dy): + """ + Move the indicator line in horizontal + :param line_id: + :param dx: + :return: + """ + # Shift value + self._myIndicatorsManager.shift(line_id, dx=dx, dy=dy) + + # apply to plot on canvas + if self._myIndicatorsManager.get_line_type(line_id) < 2: + # horizontal or vertical + canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(line_id) + vec_x, vec_y = self._myIndicatorsManager.get_data(line_id) + self._myCanvas.updateLine(ikey=canvas_line_index, vecx=vec_x, vecy=vec_y) + else: + # 2-way + canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(line_id) + h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(line_id) + + self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=h_vec_set[0], vecy=h_vec_set[1]) + self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=v_vec_set[0], vecy=v_vec_set[1]) + + return + + def remove_indicator(self, indicator_key): + """ Remove indicator line + :param indicator_key: + :return: + """ + # + plot_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key) + self._myCanvas.remove_plot_1d(plot_id) + + return + + def removePlot(self, ikey): + """ + """ + return self._myCanvas.remove_plot_1d(ikey) + + def updateLine(self, ikey, vecx, vecy, linestyle=None, linecolor=None, marker=None, markercolor=None): + """ + """ + return self._myCanvas.updateLine(ikey, vecx, vecy, linestyle, linecolor, marker, markercolor) + + def update_indicator(self, i_key, color): + """ + Update indicator with new color + :param i_key: + :param vec_x: + :param vec_y: + :param color: + :return: + """ + if self._myIndicatorsManager.get_line_type(i_key) < 2: + # horizontal or vertical + canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(i_key) + self._myCanvas.updateLine(ikey=canvas_line_index, vecx=None, vecy=None, linecolor=color) + else: + # 2-way + canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(i_key) + # h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(i_key) + + self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=None, vecy=None, linecolor=color) + self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=None, vecy=None, linecolor=color) + + return + + def get_indicator_position(self, indicator_key): + """ Get position (x or y) of the indicator + :return: + """ + # NEXT - Consider a better and more consistent return + vec_x, vec_y = self._myIndicatorsManager.get_data(indicator_key) + if vec_x[0] == vec_x[1]: + return vec_x[0] + + return vec_y[0] + + def getLineStyleList(self): + """ + """ + return MplLineStyles + + def getLineMarkerList(self): + """ + """ + return MplLineMarkers + + def getLineBasicColorList(self): + """ + """ + return MplBasicColors + + def getDefaultColorMarkerComboList(self): + """ Get a list of line/marker color and marker style combination + as default to add more and more line to plot + """ + return self._myCanvas.getDefaultColorMarkerComboList() + + def getNextLineMarkerColorCombo(self): + """ As auto line's marker and color combo list is used, + get the NEXT marker/color combo + """ + # get from list + marker, color = self._myLineMarkerColorList[self._myLineMarkerColorIndex] + # process marker if it has information + if marker.count(' (') > 0: + marker = marker.split(' (')[0] + print "[DB] Print line %d: marker = %s, color = %s" % (self._myLineMarkerColorIndex, marker, color) + + # update the index + self._myLineMarkerColorIndex += 1 + if self._myLineMarkerColorIndex == len(self._myLineMarkerColorList): + self._myLineMarkerColorIndex = 0 + + return marker, color + + def resetLineColorStyle(self): + """ Reset the auto index for line's color and style + """ + self._myLineMarkerColorIndex = 0 + return + + # NEXT-Urgent! - Find out difference between setXYLimit() and setXYLimits() + def setXYLimit(self, xmin, xmax, ymin, ymax): + """ Set X-Y limit automatically + """ + self._myCanvas.axes.set_xlim([xmin, xmax]) + self._myCanvas.axes.set_ylim([ymin, ymax]) + + self._myCanvas.draw() + + return + + def setXYLimits(self, xmin=None, xmax=None, ymin=None, ymax=None): + """ + """ + return self._myCanvas.setXYLimit(xmin, xmax, ymin, ymax) + + def setAutoLineMarkerColorCombo(self): + """ + """ + self._myLineMarkerColorList = [] + for marker in MplLineMarkers: + for color in MplBasicColors: + self._myLineMarkerColorList.append( (marker, color) ) + + return + + def setLineMarkerColorIndex(self, newindex): + """ + """ + self._myLineMarkerColorIndex = newindex + + return + + +class Qt4MplCanvas(FigureCanvas): + """ A customized Qt widget for matplotlib figure. + It can be used to replace GraphicsView of QtGui + """ + def __init__(self, parent): + """ Initialization + """ + # from mpl_toolkits.axes_grid1 import host_subplot + # import mpl_toolkits.axisartist as AA + # import matplotlib.pyplot as plt + + # Instantiating matplotlib Figure + self.fig = Figure() + self.fig.patch.set_facecolor('white') + + if True: + self.axes = self.fig.add_subplot(111) # return: matplotlib.axes.AxesSubplot + self.axes2 = None + else: + self.axes = self.fig.add_host_subplot(111) + + # Initialize parent class and set parent + FigureCanvas.__init__(self, self.fig) + self.setParent(parent) + + # Set size policy to be able to expanding and resizable with frame + FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + + # Variables to manage all lines/subplot + self._lineDict = {} + self._lineIndex = 0 + + # legend and color bar + self._colorBar = None + + return + + def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label="", x_label=None, y_label=None, + marker=None, line_style=None, line_width=1): + """ + + :param vec_x: numpy array X + :param vec_y: numpy array Y + :param y_err: + :param color: + :param label: + :param x_label: + :param y_label: + :param marker: + :param line_style: + :param line_width: + :return: new key + """ + # Check input + if isinstance(vec_x, np.ndarray) is False or isinstance(vec_y, np.ndarray) is False: + raise NotImplementedError('Input vec_x or vec_y for addPlot() must be numpy.array.') + plot_error = y_err is not None + if plot_error is True: + if isinstance(y_err, np.ndarray) is False: + raise NotImplementedError('Input y_err must be either None or numpy.array.') + + if len(vec_x) != len(vec_y): + raise NotImplementedError('Input vec_x and vec_y must have same size.') + if plot_error is True and len(y_err) != len(vec_x): + raise NotImplementedError('Input vec_x, vec_y and y_error must have same size.') + + # Hold previous data + self.axes.hold(True) + + # process inputs and defaults + if color is None: + color = (0,1,0,1) + if marker is None: + marker = 'o' + if line_style is None: + line_style = '-' + + # color must be RGBA (4-tuple) + if plot_error is False: + print "[DB] line_style = ", line_style, "line_width = ", line_width, "marker = ", marker, "color = ", color + r = self.axes.plot(vec_x, vec_y, color=color, marker=marker, linestyle=line_style, + label=label, linewidth=line_width) + # return: list of matplotlib.lines.Line2D object + else: + r = self.axes.errorbar(vec_x, vec_y, yerr=y_err, color=color, marker=marker, linestyle=line_style, + label=label, linewidth=line_width) + + self.axes.set_aspect('auto') + + # set x-axis and y-axis label + if x_label is not None: + self.axes.set_xlabel(x_label, fontsize=20) + if y_label is not None: + self.axes.set_ylabel(y_label, fontsize=20) + + # set/update legend + self._setupLegend() + + # Register + line_key = self._lineIndex + if len(r) == 1: + self._lineDict[line_key] = r[0] + self._lineIndex += 1 + else: + print "Impoooooooooooooooosible! Return from plot is a %d-tuple. " % (len(r)) + + # Flush/commit + self.draw() + + return line_key + + def add_1d_plot_right(self, x, y, color=None, label="", x_label=None, ylabel=None, marker=None, linestyle=None, + linewidth=1): + """ Add a line (1-d plot) at right axis + """ + if self.axes2 is None: + self.axes2 = self.axes.twinx() + # print self.par1, type(self.par1) + + # Hold previous data + self.axes2.hold(True) + + # Default + if color is None: + color = (0, 1, 0, 1) + if marker is None: + marker = 'o' + if linestyle is None: + linestyle = '-' + + # Special default + if len(label) == 0: + label = 'right' + color = 'red' + + # color must be RGBA (4-tuple) + r = self.axes2.plot(x, y, color=color, marker=marker, linestyle=linestyle, + label=label, linewidth=linewidth) + # return: list of matplotlib.lines.Line2D object + + self.axes2.set_aspect('auto') + + # set x-axis and y-axis label + if x_label is not None: + self.axes2.set_xlabel(x_label, fontsize=20) + if ylabel is not None: + self.axes2.set_ylabel(ylabel, fontsize=20) + + # set/update legend + self._setupLegend() + + # Register + line_key = -1 + if len(r) == 1: + line_key = self._lineIndex + self._lineDict[line_key] = r[0] + self._lineIndex += 1 + else: + print "Impoooooooooooooooosible!" + + # Flush/commit + self.draw() + + return line_key + + + def addPlot2D(self, array2d, xmin, xmax, ymin, ymax, holdprev, yticklabels=None): + """ Add a 2D plot + + Arguments: + - yticklabels :: list of string for y ticks + """ + # Release the current image + self.axes.hold(holdprev) + + # Do plot + # y ticks will be shown on line 1, 4, 23, 24 and 30 + # yticks = [1, 4, 23, 24, 30] + # self.axes.set_yticks(yticks) + + # show image + imgplot = self.axes.imshow(array2d, extent=[xmin,xmax,ymin,ymax], interpolation='none') + # set y ticks as an option: + if yticklabels is not None: + # it will always label the first N ticks even image is zoomed in + print "--------> [FixMe]: Set up the Y-axis ticks is erroreous" + #self.axes.set_yticklabels(yticklabels) + + # explicitly set aspect ratio of the image + self.axes.set_aspect('auto') + + # Set color bar. plt.colorbar() does not work! + if self._colorBar is None: + # set color map type + imgplot.set_cmap('spectral') + self._colorBar = self.fig.colorbar(imgplot) + else: + self._colorBar.update_bruteforce(imgplot) + + # Flush... + self._flush() + + return + + def addImage(self, imagefilename): + """ Add an image by file + """ + #import matplotlib.image as mpimg + + # set aspect to auto mode + self.axes.set_aspect('auto') + + img = matplotlib.image.imread(str(imagefilename)) + # lum_img = img[:,:,0] + # FUTURE : refactor for image size, interpolation and origin + imgplot = self.axes.imshow(img, extent=[0, 1000, 800, 0], interpolation='none', origin='lower') + + # Set color bar. plt.colorbar() does not work! + if self._colorBar is None: + # set color map type + imgplot.set_cmap('spectral') + self._colorBar = self.fig.colorbar(imgplot) + else: + self._colorBar.update_bruteforce(imgplot) + + self._flush() + + return + + def clear_all_1d_plots(self): + """ Remove all lines from the canvas + """ + for ikey in self._lineDict.keys(): + plot = self._lineDict[ikey] + if plot is None: + continue + if isinstance(plot, tuple) is False: + try: + self.axes.lines.remove(plot) + except ValueError as e: + print "[Error] Plot %s is not in axes.lines which has %d lines. Error mesage: %s" % ( + str(plot), len(self.axes.lines), str(e)) + self._lineDict[ikey] = None + else: + # error bar + plot[0].remove() + for line in plot[1]: + line.remove() + for line in plot[2]: + line.remove() + self._lineDict[ikey] = None + # ENDIF(plot) + # ENDFOR + + self._setupLegend() + + self.draw() + + return + + def clear_canvas(self): + """ Clear data including lines and image from canvas + """ + # clear the image for next operation + self.axes.hold(False) + + # Clear all lines + self.clear_all_1d_plots() + + # clear image + self.axes.cla() + # Try to clear the color bar + if len(self.fig.axes) > 1: + self.fig.delaxes(self.fig.axes[1]) + self._colorBar = None + # This clears the space claimed by color bar but destroys sub_plot too. + self.fig.clear() + # Re-create subplot + self.axes = self.fig.add_subplot(111) + + # flush/commit + self._flush() + + return + + + def getLastPlotIndexKey(self): + """ Get the index/key of the last added line + """ + return self._lineIndex-1 + + + def getPlot(self): + """ reture figure's axes to expose the matplotlib figure to PyQt client + """ + return self.axes + + def getXLimit(self): + """ Get limit of Y-axis + """ + return self.axes.get_xlim() + + def getYLimit(self): + """ Get limit of Y-axis + """ + return self.axes.get_ylim() + + def setXYLimit(self, xmin, xmax, ymin, ymax): + """ + """ + # for X + xlims = self.axes.get_xlim() + xlims = list(xlims) + if xmin is not None: + xlims[0] = xmin + if xmax is not None: + xlims[1] = xmax + self.axes.set_xlim(xlims) + + # for Y + ylims = self.axes.get_ylim() + ylims = list(ylims) + if ymin is not None: + ylims[0] = ymin + if ymax is not None: + ylims[1] = ymax + self.axes.set_ylim(ylims) + + # try draw + self.draw() + + return + + def remove_plot_1d(self, plot_key): + """ Remove the line with its index as key + :param plot_key: + :return: + """ + # self._lineDict[ikey].remove() + print 'Remove line... ', + + # Get all lines in list + lines = self.axes.lines + assert isinstance(lines, list) + + print 'Number of lines = %d, List: %s' % (len(lines), str(lines)) + print 'Line to remove: key = %s, Line Dict has key = %s' % (str(plot_key), str(self._lineDict.has_key(plot_key))) + + if plot_key in self._lineDict: + self.axes.lines.remove(self._lineDict[plot_key]) + self._lineDict[plot_key] = None + else: + raise RuntimeError('Line with ID %s is not recorded.' % plot_key) + + # Draw + self.draw() + + return + + def updateLine(self, ikey, vecx, vecy, linestyle=None, linecolor=None, marker=None, markercolor=None): + """ + """ + line = self._lineDict[ikey] + + if vecx is not None and vecy is not None: + line.set_xdata(vecx) + line.set_ydata(vecy) + + if linecolor is not None: + line.set_color(linecolor) + + if linestyle is not None: + line.set_linestyle(linestyle) + + if marker is not None: + line.set_marker(marker) + + if markercolor is not None: + line.set_markerfacecolor(markercolor) + + oldlabel = line.get_label() + line.set_label(oldlabel) + + self.axes.legend() + + # commit + self.draw() + + return + + def getLineStyleList(self): + """ + """ + return MplLineStyles + + + def getLineMarkerList(self): + """ + """ + return MplLineMarkers + + def getLineBasicColorList(self): + """ + """ + return MplBasicColors + + def getDefaultColorMarkerComboList(self): + """ Get a list of line/marker color and marker style combination + as default to add more and more line to plot + """ + combolist = [] + nummarkers = len(MplLineMarkers) + numcolors = len(MplBasicColors) + + for i in xrange(nummarkers): + marker = MplLineMarkers[i] + for j in xrange(numcolors): + color = MplBasicColors[j] + combolist.append( (marker, color) ) + # ENDFOR (j) + # ENDFOR(i) + + return combolist + + def _flush(self): + """ A dirty hack to flush the image + """ + w, h = self.get_width_height() + self.resize(w+1,h) + self.resize(w,h) + + return + + def _setupLegend(self, location='best'): + """ Set up legend + self.axes.legend() + Handler is a Line2D object. Lable maps to the line object + """ + loclist = [ + "best", + "upper right", + "upper left", + "lower left", + "lower right", + "right", + "center left", + "center right", + "lower center", + "upper center", + "center"] + + # Check legend location valid or not + if location not in loclist: + location = 'best' + + handles, labels = self.axes.get_legend_handles_labels() + self.axes.legend(handles, labels, loc=location) + # print handles + # print labels + #self.axes.legend(self._myLegendHandlers, self._myLegentLabels) + + return + +# END-OF-CLASS (MplGraphicsView) + + +class MyNavigationToolbar(NavigationToolbar2): + """ A customized navigation tool bar attached to canvas + Note: + * home, left, right: will not disable zoom/pan mode + * zoom and pan: will turn on/off both's mode + + Other methods + * drag_pan(self, event): event handling method for dragging canvas in pan-mode + """ + NAVIGATION_MODE_NONE = 0 + NAVIGATION_MODE_PAN = 1 + NAVIGATION_MODE_ZOOM = 2 + + def __init__(self, parent, canvas): + """ Initialization + """ + NavigationToolbar2.__init__(self, canvas, canvas) + + self._myParent = parent + self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + + return + + def get_mode(self): + """ + :return: integer as none/pan/zoom mode + """ + return self._navigationMode + + # Overriding base's methods + def draw(self): + """ + Canvas is drawn called by pan(), zoom() + :return: + """ + NavigationToolbar2.draw(self) + + self._myParent.evt_view_updated() + + return + + def pan(self, *args): + """ + + :param args: + :return: + """ + NavigationToolbar2.pan(self, args) + + if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_PAN: + # out of pan mode + self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + else: + # into pan mode + self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_PAN + + return + + def zoom(self, *args): + """ + Turn on/off zoom (zoom button) + :param args: + :return: + """ + NavigationToolbar2.zoom(self, args) + + if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM: + # out of zoom mode + self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + else: + # into zoom mode + self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_ZOOM + + return + + def _update_view(self): + """ + view update called by home(), back() and forward() + :return: + """ + NavigationToolbar2._update_view(self) + + self._myParent.evt_view_updated() + + return + -- GitLab