Beispiel #1
0
class DeviceWidget(QLabel):
    """
    Colored Symbol for Lightpath Display

    See :func:`.symbol_for_device` for more information on how the proper
    symbol for the provided device is determined.

    Parameters
    ----------
    device: ophyd.Device
        Object that will have a drawing created for it.
    """
    clicked = pyqtSignal()

    def __init__(self, device, parent=None):
        super().__init__(parent=parent)
        # Grab the symbol of the device
        # NOTE: The symbol will not actually be created until setColor is
        # called. We want to avoid unnecessary widget creation and there is no
        # point in drawing a widget until we know more about the state of the
        # device
        self.symbol = symbol_for_device(device)
        # Default UI settings for conformity
        self.setMinimumSize(10, 10)
        self.setMaximumSize(50, 50)

    def setColor(self, color):
        """
        Set the color of the QIcon contained in the widget
        """
        try:
            icon = qta.icon(self.symbol, color=color)
        # Capture any errors loading icons
        except Exception:
            logger.exception("Unable to load icon %r", self.symbol)
            return
        # Set the proper pixmap
        self.setPixmap(icon.pixmap(self.width(), self.height()))

    def mousePressEvent(self, evt):
        """Catch mousePressEvent to emit "`clicked`" pyqtSignal"""
        # Push MouseEvent through
        super().mousePressEvent(evt)
        # Emit click
        self.clicked.emit()
Beispiel #2
0
class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
    """
    Customized 2D detector view

    """
    class MousePress(object):
        RELEASED = 0
        LEFT = 1
        RIGHT = 3

    newROIDefinedSignal = pyqtSignal(int, int, int,
                                     int)  # return coordinate of the

    def __init__(self, parent):
        """

        :param parent:
        :return:
        """
        mpl2dgraphicsview.Mpl2dGraphicsView.__init__(self, parent)

        # connect the mouse motion to interact with the canvas
        self._myCanvas.mpl_connect('button_press_event',
                                   self.on_mouse_press_event)
        self._myCanvas.mpl_connect('button_release_event',
                                   self.on_mouse_release_event)
        self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion)

        # class variables
        self._myPolygon = None  # matplotlib.patches.Polygon

        # class status variables
        self._roiSelectMode = False
        # region of interest. None or 2 tuple of 2-tuple for upper left corner and lower right corner
        # mouse positions as start and end
        self._roiStart = None
        self._roiEnd = None

        # mouse
        self._mousePressed = Detector2DView.MousePress.RELEASED

        # mouse position and resolution
        self._currX = 0.
        self._currY = 0.
        self._resolutionX = 0.005
        self._resolutionY = 0.005

        # parent window
        self._myParentWindow = None

        return

    def clear_canvas(self):
        """
        clear canvas (override base class)
        :return:
        """
        # clear the current record
        self._myPolygon = None

        # reset mouse selection ROI
        # set
        self._roiStart = None
        self._roiEnd = None

        # call base class
        super(Detector2DView, self).clear_canvas()

        return

    def enter_roi_mode(self, roi_state):
        """
        Enter or leave the region of interest (ROI) selection mode
        :return:
        """
        assert isinstance(roi_state, bool), 'ROI mode state {} must be a boolean but not a {}.' \
                                            ''.format(roi_state, type(roi_state))

        # set
        self._roiSelectMode = roi_state

        if roi_state:
            # new in add-ROI mode
            self.remove_roi()
        else:
            # reset roi start and roi end
            self._roiStart = None
            self._roiEnd = None

        return

    def integrate_roi_linear(self, exp_number, scan_number, pt_number,
                             output_dir):
        """
        integrate the 2D data inside region of interest along both axis-0 and axis-1 individually.
        and the result (as 1D data) will be saved to ascii file.
        the X values will be the corresponding pixel index either along axis-0 or axis-1
        :return:
        """
        def save_to_file(base_file_name, axis, array1d, start_index):
            """
            save the result (1D data) to an ASCII file
            :param base_file_name:
            :param axis:
            :param array1d:
            :param start_index:
            :return:
            """
            file_name = '{0}_axis_{1}.dat'.format(base_file_name, axis)

            wbuf = ''
            vec_x = np.arange(len(array1d)) + start_index
            for x, d in zip(vec_x, array1d):
                wbuf += '{0} \t{1}\n'.format(x, d)

            ofile = open(file_name, 'w')
            ofile.write(wbuf)
            ofile.close()

            return

        matrix = self.array2d
        assert isinstance(
            matrix,
            np.ndarray), 'A matrix must be an ndarray but not {0}.'.format(
                type(matrix))

        # get region of interest
        if self._roiStart is None:
            self._roiStart = (0, 0)
        if self._roiEnd is None:
            self._roiEnd = matrix.shape

        ll_row = min(self._roiStart[0], self._roiEnd[0])
        ll_col = min(self._roiStart[1], self._roiEnd[1])

        ur_row = max(self._roiStart[0], self._roiEnd[0])
        ur_col = max(self._roiStart[1], self._roiEnd[1])

        #roi_matrix = matrix[ll_col:ur_col, ll_row:ur_row]
        #sum_0 = roi_matrix.sum(0)
        #sum_1 = roi_matrix.sum(1)
        roi_matrix = matrix[ll_col:ur_col, ll_row:ur_row]
        sum_0 = roi_matrix.sum(0)
        sum_1 = roi_matrix.sum(1)

        # write to file
        base_name = os.path.join(
            output_dir, 'Exp{0}_Scan{1}_Pt{2}'.format(exp_number, scan_number,
                                                      pt_number))
        save_to_file(base_name, 0, sum_0, ll_row)
        save_to_file(base_name, 1, sum_1, ll_col)

        message = 'Integrated values are saved to {0}...'.format(base_name)

        return message

    @property
    def is_roi_selection_drawn(self):
        """
        whether ROI is drawn
        :return:
        """
        is_drawn = not (self._myPolygon is None)

        return is_drawn

    def get_roi(self):
        """
        :return: A list for polygon0
        """
        assert self._roiStart is not None
        assert self._roiEnd is not None

        # rio start is upper left, roi end is lower right
        lower_left_x = min(self._roiStart[0], self._roiEnd[0])
        lower_left_y = min(self._roiStart[1], self._roiEnd[1])
        lower_left = lower_left_x, lower_left_y

        # ROI upper right
        upper_right_x = max(self._roiStart[0], self._roiEnd[0])
        upper_right_y = max(self._roiStart[1], self._roiEnd[1])
        upper_right = upper_right_x, upper_right_y

        return lower_left, upper_right

    def plot_detector_counts(self, raw_det_data, title=None):
        """
        plot detector counts as 2D plot
        :param raw_det_data:
        :return:
        """
        x_min = 0
        x_max = raw_det_data.shape[0]
        y_min = 0
        y_max = raw_det_data.shape[1]

        count_plot = self.add_plot_2d(raw_det_data,
                                      x_min=x_min,
                                      x_max=x_max,
                                      y_min=y_min,
                                      y_max=y_max,
                                      hold_prev_image=False)
        if title is None:
            title = 'No Title'
        self.set_title(title)

        if self._myPolygon is not None:
            print('[DB...BAT...] Add PATCH')
            self._myCanvas.add_patch(self._myPolygon)
        else:
            print('[DB...BAT...] NO PATCH')

        print('[DB...BAT...AFTER]  ROI Rect: {0}.  2D plot: {1}'.format(
            self._myPolygon, count_plot))

        return

    def plot_roi(self):
        """ Plot region of interest (as rectangular) to the canvas from the region set from
        :return:
        """
        # check
        assert self._roiStart is not None, 'Starting point of region-of-interest cannot be None'
        assert self._roiEnd is not None, 'Ending point of region-of-interest cannot be None'

        # create a vertex list of a rectangular
        vertex_array = np.ndarray(shape=(4, 2))
        # upper left corner
        vertex_array[0][0] = self._roiStart[0]
        vertex_array[0][1] = self._roiStart[1]

        # lower right corner
        vertex_array[2][0] = self._roiEnd[0]
        vertex_array[2][1] = self._roiEnd[1]

        # upper right corner
        vertex_array[1][0] = self._roiEnd[0]
        vertex_array[1][1] = self._roiStart[1]

        # lower left corner
        vertex_array[3][0] = self._roiStart[0]
        vertex_array[3][1] = self._roiEnd[1]

        # register
        if self._myPolygon is not None:
            self._myPolygon.remove()
            self._myPolygon = None
        self._myPolygon = self._myCanvas.plot_polygon(vertex_array,
                                                      fill=False,
                                                      color='w')

        return

    def remove_roi(self):
        """
        Remove the rectangular for region of interest
        :return:
        """
        print('[DB...BAT] Try to remove ROI {0}'.format(self._myPolygon))
        if self._myPolygon is not None:
            # polygon is of type matplotlib.patches.Polygon
            self._myPolygon.remove()
            self._myPolygon = None

            # FUTURE-TO-DO: this should be replaced by some update() method of canvas
            self._myCanvas._flush()

            self._roiStart = None
            self._roiEnd = None

        else:
            print('[NOTICE] Polygon is None.  Nothing to remove')

        return

    def on_mouse_motion(self, event):
        """
        Event handing as mouse is moving
        :param event:
        :return:
        """
        # skip if the mouse cursor is still outside of the canvas
        if event.xdata is None or event.ydata is None:
            return

        # check: _currX and _currY must be specified
        assert self._currX is not None and self._currY is not None

        # operation if the displacement is too small
        if abs(event.xdata - self._currX) < self.resolutionX() and abs(
                event.ydata - self._currY) < self.resolutionY():
            return

        if self._mousePressed == Detector2DView.MousePress.RELEASED:
            # No operation if mouse is not pressed
            pass

        elif self._mousePressed == Detector2DView.MousePress.RIGHT:
            # No operation if mouse' right button is pressed
            pass

        elif self._mousePressed == Detector2DView.MousePress.LEFT:
            if self._roiSelectMode is True:
                # in ROI selection mode, update the size
                self.update_roi_poly(event.xdata, event.ydata)

        # update current mouse' position
        self._currX = event.xdata
        self._currY = event.ydata

        return

    def on_mouse_press_event(self, event):
        """

        :param event:
        :return:
        """
        # return if the cursor position is out of canvas
        if event.xdata is None or event.ydata is None:
            return

        # update mouse' position
        self._currX = event.xdata
        self._currY = event.ydata

        # update mouse' pressed state
        if event.button == 1:
            self._mousePressed = Detector2DView.MousePress.LEFT
        elif event.button == 3:
            self._mousePressed = Detector2DView.MousePress.RIGHT

        # do something?
        if self._roiSelectMode is True and self._mousePressed == Detector2DView.MousePress.LEFT:
            # start to select a region
            self._roiStart = (self._currX, self._currY)

        return

    def on_mouse_release_event(self, event):
        """

        :param event:
        :return:
        """
        # return without any operation if mouse cursor is out side of canvas
        if event.xdata is None or event.ydata is None:
            return

        # update mouse' position
        self._currX = event.xdata
        self._currY = event.ydata

        # update button
        prev_mouse_pressed = self._mousePressed
        self._mousePressed = Detector2DView.MousePress.RELEASED

        # do something
        if self._roiSelectMode and prev_mouse_pressed == Detector2DView.MousePress.LEFT:
            # end the ROI selection mode
            self.update_roi_poly(self._currX, self._currY)

            # send a signal to parent such that a rew ROI is defined
            self.newROIDefinedSignal.emit(self._roiStart[0], self._roiStart[1],
                                          self._roiEnd[0], self._roiEnd[1])

        # END-IF

        return

    def resolutionX(self):
        """

        :return:
        """
        return (self.x_max - self.x_min) * self._resolutionX

    def resolutionY(self):
        """

        :return:
        """
        return (self.y_max - self.y_min) * self._resolutionY

    def set_parent_window(self, parent_window):
        """
        Set the parent window for synchronizing the operation
        :param parent_window:
        :return:
        """
        assert parent_window is not None, 'Parent window cannot be None'

        self._myParentWindow = parent_window

        self.newROIDefinedSignal.connect(self._myParentWindow.evt_new_roi)

        return

    def set_roi(self, lower_left_corner, upper_right_corner, plot=True):
        """
        set ROI to class variables
        :param lower_left_corner:
        :param upper_right_corner:
        :param plot: if True, then plot ROI
        :return:
        """
        # check inputs
        assert len(lower_left_corner) == 2, 'Lower left corner row/col coordinate {0} must have 2 items.' \
                                            ''.format(lower_left_corner)
        assert len(upper_right_corner) == 2, 'Upper right corner row/col coordinate {0} must have 2 items.' \
                                             ''.format(upper_right_corner)

        # set lower left corner and upper right corner
        self._roiStart = lower_left_corner
        self._roiEnd = upper_right_corner

        # plot
        if plot:
            self.plot_roi()

        return

    def update_roi_poly(self, cursor_x, cursor_y):
        """Update region of interest.  It is to
        (1) remove the original polygon
        (2) draw a new polygon
        :return:
        """
        # check
        assert isinstance(
            cursor_x,
            float), 'Cursor x coordination {0} must be a float.'.format(
                cursor_x)
        assert isinstance(
            cursor_y,
            float), 'Cursor y coordination {0} must be a float.'.format(
                cursor_y)

        # remove the original polygon
        if self._myPolygon is not None:
            self._myPolygon.remove()
            self._myPolygon = None
            # self.canvas._flush()

        # set RIO end
        self._roiEnd = [cursor_x, cursor_y]

        # plot the new polygon
        self.plot_roi()

        # # update: no need to do this!
        # if self._myPolygon is not None:
        #     self._myParentWindow.do_apply_roi()

        return
class IntegrateSinglePtIntensityWindow(QMainWindow):
    """
    Main window widget to set up parameters to optimize
    """
    # establish signal for communicating from App2 to App1 - must be defined before the constructor
    scanIntegratedSignal = pyqtSignal(dict, name='SinglePtIntegrated')

    def __init__(self, parent=None):
        """
        Initialization
        :param parent:
        :return:
        """
        # init
        super(IntegrateSinglePtIntensityWindow, self).__init__(parent)

        assert parent is not None, 'Parent window cannot be None to set'
        self._parent_window = parent
        self._controller = parent.controller

        # connect signal handler
        self.scanIntegratedSignal.connect(
            self._parent_window.process_single_pt_scan_intensity)

        # init UI
        ui_path = "SinglePtIntegrationWindow.ui"
        self.ui = load_ui(__file__, ui_path, baseinstance=self)
        self._promote_widgets()

        # initialize widgets
        self.ui.tableView_summary.setup()
        self.ui.graphicsView_integration1DView.set_parent_window(self)

        # define event handlers for widgets
        self.ui.pushButton_integrteDetectorCounts.clicked.connect(
            self.do_integrate_detector_counts)
        self.ui.pushButton_load2thetaSigmaFile.clicked.connect(
            self.menu_load_gauss_sigma_file)

        self.ui.pushButton_exportIntensityToFile.clicked.connect(
            self.do_save_intensity)
        self.ui.pushButton_exportIntensityToTable.clicked.connect(
            self.do_export_intensity_to_parent)
        # self.ui.pushButton_refreshROI.clicked.connect(self.do_refresh_roi)
        self.ui.pushButton_retrieveFWHM.clicked.connect(self.do_retrieve_fwhm)
        self.ui.pushButton_integratePeaks.clicked.connect(
            self.do_integrate_single_pt)
        self.ui.pushButton_plot.clicked.connect(self.do_plot_integrated_pt)
        self.ui.pushButton_exportToMovie.clicked.connect(
            self.do_export_to_movie)

        # TODO - 20180809 - Implement the following...calling change_scan_number
        self.ui.pushButton_rewindPlot.clicked.connect(
            self.do_plot_previous_scan)
        self.ui.pushButton_forwardPlot.clicked.connect(self.do_plot_next_scan)
        self.ui.actionDefine_2theta_FWHM_Function.triggered.connect(
            self.do_define_2theta_fwhm_function)

        # menu bar
        self.ui.menuQuit.triggered.connect(self.do_close)
        self.ui.actionSelect_All.triggered.connect(self.menu_table_select_all)
        self.ui.actionDe_select_All.triggered.connect(
            self.menu_table_select_none)
        self.ui.actionLoad_Gaussian_Sigma_File.triggered.connect(
            self.menu_load_gauss_sigma_file)
        self.ui.actionLoad_Peak_Info_File.triggered.connect(
            self.do_load_peak_integration_table)
        self.ui.actionRefresh_ROI_List.triggered.connect(self.do_refresh_roi)

        # class variable
        self._working_dir = self._controller.get_working_directory()
        self._exp_number = None
        self._roiMutex = False

        # other things to do
        self.do_refresh_roi()

        return

    def _promote_widgets(self):
        graphicsView_integration1DView_layout = QVBoxLayout()
        self.ui.frame_graphicsView_integration1DView.setLayout(
            graphicsView_integration1DView_layout)
        self.ui.graphicsView_integration1DView = SinglePtIntegrationView(self)
        graphicsView_integration1DView_layout.addWidget(
            self.ui.graphicsView_integration1DView)

        tableView_summary_layout = QVBoxLayout()
        self.ui.frame_tableView_summary.setLayout(tableView_summary_layout)
        self.ui.tableView_summary = SinglePtIntegrationTable(self)
        tableView_summary_layout.addWidget(self.ui.tableView_summary)

        return

    def do_close(self):
        """
        Quit the window
        :return:
        """
        self.close()

        return

    def do_define_2theta_fwhm_function(self):
        """
        pop out a dialog for user to input the 2theta-FWHM formula
        :return:
        """
        formula = guiutility.get_value_from_dialog(
            parent=self,
            title='Input 2theta-FWHM function',
            details='Example: y = 4.0 * x**2 - 1.2 * x + 1./x]=\n'
            'where y is FWHM and x is 2theta',
            label_name='Equation: ')

        if formula is None:
            # return if user cancels operation
            return

        print('[DB...BAT] User input 2theta formula: {}'.format(formula))
        state, error_message = self._controller.check_2theta_fwhm_formula(
            formula)
        if not state:
            guiutility.show_message(self,
                                    message=error_message,
                                    message_type='error')
            return

        return

    def do_export_intensity_to_parent(self):
        """
        export the integrated intensity to parent window's peak processing table
        :return:
        """
        # collect all scan/pt from table. value including intensity and ROI
        intensity_dict = self.ui.tableView_summary.get_peak_intensities()

        # add to table including calculate peak center in Q-space
        self.scanIntegratedSignal.emit(intensity_dict)

        return

    # TESTME -  20180727 - Complete it!
    def do_export_to_movie(self):
        """
        export the complete list of single-pt experiment to a movie
        :return:
        """
        # find out the directory to save the PNG files for making a move
        movie_dir = self._controller.get_working_directory()
        roi_name = str(self.ui.comboBox_roiList.currentText())
        direction = str(
            self.ui.comboBox_integrateDirection.currentText()).lower()
        movie_dir = os.path.join(movie_dir,
                                 '{}_{}'.format(roi_name, direction))
        os.mkdir(movie_dir)

        # go through each line to plot and save the data
        num_rows = self.ui.tableView_summary.rowCount()
        file_list_str = ''
        for i_row in range(num_rows):
            # get run number and set to plot
            scan_number = self.ui.tableView_summary.get_scan_number(i_row)
            self.ui.lineEdit_Scan.setText('{}'.format(scan_number))
            png_file_name = os.path.join(
                movie_dir,
                'Scan{0:04d}_ROI{1}_{2}.png'.format(scan_number, roi_name,
                                                    direction))
            self.do_plot_integrated_pt(show_plot=False,
                                       save_plot_to=png_file_name)
            file_list_str += '{}\n'.format(png_file_name)
        # END-IF

        # write out
        png_list_file = open(os.path.join(movie_dir, 'MoviePNGList.txt'), 'w')
        png_list_file.write(file_list_str)
        png_list_file.close()

        # prompt how to make a movie
        command_linux = 'ffmpeg -framerate 8 -pattern_type glob -i "*.png" -r 30 test.mp4'
        guiutility.show_message(self, command_linux)

        return

    def do_integrate_detector_counts(self):
        """
        sum over the (selected) scan's detector counts by ROI
        :return:
        """
        # get ROI
        roi_name = str(self.ui.comboBox_roiList.currentText())
        if roi_name is None or roi_name == '':
            guiutility.show_message(
                'A region-of-interest must be chosen in order to integrate detector counts.'
            )
            return

        # integration direction and fit
        direction = str(
            self.ui.comboBox_integrateDirection.currentText()).lower()
        fit_gaussian = self.ui.checkBox_fitPeaks.isChecked()

        num_rows = self.ui.tableView_summary.rowCount()
        print('[DB...BAT] Number of rows = {}'.format(num_rows))
        scan_number_list = list()
        for row_number in range(num_rows):
            # integrate counts on detector
            scan_number = self.ui.tableView_summary.get_scan_number(row_number)
            scan_number_list.append(scan_number)
        # END-FOR
        print('[DB...BAT] Scan numbers: {}'.format(scan_number_list))

        peak_height_dict = self._controller.integrate_single_pt_scans_detectors_counts(
            self._exp_number, scan_number_list, roi_name, direction,
            fit_gaussian)

        # set the value to the row  to table
        for row_number in range(self.ui.tableView_summary.rowCount()):
            scan_number = self.ui.tableView_summary.get_scan_number(row_number)
            pt_number = 1
            peak_height = peak_height_dict[scan_number]
            self.ui.tableView_summary.set_peak_height(scan_number, pt_number,
                                                      peak_height, roi_name)
        # END-FOR (row_number)

        return

    def do_integrate_single_pt(self):
        """
        integrate the 2D data inside region of interest along both axis-0 and axis-1 individually.
        and the result (as 1D data) will be saved to ascii file.
        the X values will be the corresponding pixel index either along axis-0 or axis-1
        :return:
        """
        # get ROI
        roi_name = str(self.ui.comboBox_roiList.currentText())
        if roi_name is None or roi_name == '':
            guiutility.show_message(
                'A region-of-interest must be chosen in order to integrate detector counts.'
            )
            return

        for row_number in range(self.ui.tableView_summary.rowCount()):
            # integrate counts on detector
            scan_number = self.ui.tableView_summary.get_scan_number(row_number)
            pt_number = self.ui.tableView_summary.get_pt_number(row_number)

            # calculate peak intensity
            ref_fwhm = self.ui.tableView_summary.get_fwhm(row_number)

            intensity = self._controller.calculate_intensity_single_pt(
                self._exp_number,
                scan_number,
                pt_number,
                roi_name,
                ref_fwhm=ref_fwhm,
                is_fwhm=False)

            # add to table
            self.ui.tableView_summary.set_intensity(scan_number, pt_number,
                                                    intensity)
        # END-FOR

        return

    # TESTME - Load a previously save integrated peaks file
    # Question: What kind of peak integrtion table??? Need to find out and well documented!
    def do_load_peak_integration_table(self):
        """
        load peak integration table CSV file saved from peak integration table
        :return:
        """
        # get table file name
        table_file = QFileDialog.getOpenFileName(self,
                                                 'Peak Integration Table',
                                                 self._working_dir)
        if not table_file:
            return
        if isinstance(table_file, tuple):
            table_file = table_file[0]
        if not os.path.exists(table_file):
            return

        # load
        status, error_msg = self._controller.load_peak_integration_table(
            table_file)
        if not status:
            raise RuntimeError(error_msg)

    def do_plot_integrated_pt(self, show_plot=True, save_plot_to=None):
        """ plot integrated Pt with model and raw data
        1. selection include: 2-theta FWHM Model, Summed Single Pt. Counts (horizontal),
        Summed Single Pt. Counts (vertical) from comboBox_plotType
        :return:
        """
        plot_type = str(self.ui.comboBox_plotType.currentText())

        # reset the canvas
        self.ui.graphicsView_integration1DView.clear_all_lines()

        if plot_type == '2-theta FWHM Model':
            self.plot_2theta_fwhm_model()
        else:
            # plot summed single pt scan
            self.plot_summed_single_pt_scan_counts(
                is_vertical_summed=plot_type.lower().count('vertical'),
                figure_file=save_plot_to)

        return

    def do_plot_previous_scan(self):
        """ plot previous scan if not in 2theta FWHM model
        :return:
        """
        plot_type = str(self.ui.comboBox_plotType.currentText())
        if plot_type == '2-theta FWHM Model':
            return

        scan_number_str = str(self.ui.lineEdit_Scan.text()).strip()
        if scan_number_str == '':
            return

        scan_number = int(scan_number_str)
        row_number = self.ui.tableView_summary.get_row_number_by_scan(
            scan_number)
        if row_number == 0:
            row_number = self.ui.tableView_summary.rowCount() - 1
        scan_number = self.ui.tableView_summary.get_scan_number(row_number)
        self.ui.lineEdit_Scan.setText(scan_number)

        self.do_plot_integrated_pt()

        return

    def do_plot_next_scan(self):
        """  plot next scan if not in 2theta FWHM model
        :return:
        """
        plot_type = str(self.ui.comboBox_plotType.currentText())
        if plot_type == '2-theta FWHM Model':
            return

        scan_number_str = str(self.ui.lineEdit_Scan.text()).strip()
        if scan_number_str == '':
            return

        scan_number = int(scan_number_str)
        row_number = self.ui.tableView_summary.get_row_number_by_scan(
            scan_number)
        if row_number == self.ui.tableView_summary.rowCount() - 1:
            row_number = 0
        scan_number = self.ui.tableView_summary.get_scan_number(row_number)
        self.ui.lineEdit_Scan.setText(scan_number)

        self.do_plot_integrated_pt()

        return

    def plot_summed_single_pt_scan_counts(self,
                                          is_vertical_summed,
                                          figure_file=None,
                                          pop_error=False):
        """
        plot single pt scanned counts
        :param is_vertical_summed:
        :param figure_file:
        :param pop_error:
        :return:
        """
        # get scan number
        scan_num_str = str(self.ui.lineEdit_Scan.text()).strip()
        if len(scan_num_str) == 0:
            scan_number = self.ui.tableView_summary.get_scan_number(0)
            self.ui.lineEdit_Scan.setText('{}'.format(scan_number))
        else:
            scan_number = int(scan_num_str)
        roi_name = str(self.ui.comboBox_roiList.currentText())
        if is_vertical_summed:
            direction = 'vertical'
        else:
            direction = 'horizontal'

        # get data: pt number is always 1 as it is a single Pt. measurement
        model_y = None
        if self.ui.checkBox_fitPeaks.isChecked():
            try:
                vec_x, model_y = self._controller.get_single_scan_pt_model(
                    self._exp_number,
                    scan_number,
                    pt_number=1,
                    roi_name=roi_name,
                    integration_direction=direction)
            except RuntimeError as run_err:
                err_msg = 'Unable to get single-pt scan model for {} {} {} due to {}' \
                          ''.format(self._exp_number, scan_number, roi_name, run_err)
                if pop_error:
                    raise RuntimeError(err_msg)
                else:
                    print(err_msg)
            # END-TRY-EXCEPT
        # END-IF

        # get original data
        vec_x, vec_y = self._controller.get_single_scan_pt_summed(
            self._exp_number,
            scan_number,
            pt_number=1,
            roi_name=roi_name,
            integration_direction=direction)

        # plot
        self.ui.graphicsView_integration1DView.add_observed_data(
            vec_x, vec_y, label='Summed (raw) counts', update_plot=False)
        if model_y is not None:
            self.ui.graphicsView_integration1DView.add_fit_data(
                vec_x, model_y, label='Gaussian model', update_plot=True)

        # title
        self.ui.graphicsView_integration1DView.set_title(
            'Scan {} Pt {} {} Integration.'
            ''.format(scan_number, 1, direction))

        # save plot?
        if figure_file is not None:
            self.ui.graphicsView_integration1DView.canvas.save_figure(
                figure_file)

        return

    def plot_2theta_fwhm_model(self):
        """ plot the loaded 2theta-FWHM model
        :return:
        """
        # TODO - 20180815 - Need to parse the range from self.ui.lineEdit_Scan
        # default
        two_theta_range = 10, 2.0, 110  # start, resolution, stop

        vec_2theta, vec_fwhm, vec_model = self._controller.get_2theta_fwhm_data(
            two_theta_range[0], two_theta_range[1], two_theta_range[2])

        self.ui.graphicsView_integration1DView.plot_2theta_model(
            vec_2theta, vec_fwhm, vec_model)

        return

    def do_refresh_roi(self):
        """
        refresh ROI list from parent
        :return:
        """
        roi_list = self._controller.get_region_of_interest_list()

        # add ROI
        self._roiMutex = True

        self.ui.comboBox_roiList.clear()
        for roi_name in sorted(roi_list):
            self.ui.comboBox_roiList.addItem(roi_name)

        self._roiMutex = False

        return

    def do_save_intensity(self):
        """
        save intensity to file
        :return:
        """
        # get output file
        out_file_name = QFileDialog.getSaveFileName(
            self, 'File to save integrated intensities', self._working_dir)
        if not out_file_name:
            return
        if isinstance(out_file_name, tuple):
            out_file_name = out_file_name[0]

        self.ui.tableView_summary.save_intensities_to_file(out_file_name)

    def do_retrieve_fwhm(self):
        """
        Get FWHM from integrated 'STRONG' peaks according to 2theta value
        :return:
        """
        row_number = self.ui.tableView_summary.rowCount()
        error_messages = ''
        for row_index in range(row_number):
            # check whether FWHM value is set up
            fwhm_i = self.ui.tableView_summary.get_fwhm(row_index)
            if fwhm_i is not None and fwhm_i > 1.E-10:
                continue

            # use interpolation to curve
            two_theta = self.ui.tableView_summary.get_two_theta(row_index)
            try:
                gauss_sigma = self._controller.calculate_peak_integration_sigma(
                    two_theta)
                scan_number = self.ui.tableView_summary.get_scan_number(
                    row_index)
                pt_number = 1
                roi_name = self.ui.tableView_summary.get_region_of_interest_name(
                    row_index)
                self.ui.tableView_summary.set_gaussian_sigma(
                    row_index, gauss_sigma)
                self._controller.set_single_measure_peak_width(
                    self._exp_number,
                    scan_number,
                    pt_number,
                    roi_name,
                    gauss_sigma,
                    is_fhwm=False)

            except RuntimeError as err:
                # error!
                error_messages += 'Unable to calculate sigma of row {0} due to {1}\n'.format(
                    row_index, err)
                continue
            # END-IF-ELSE

        # show error message if necessary
        if len(error_messages) > 0:
            guiutility.show_message(self, error_messages)

    def menu_load_gauss_sigma_file(self):
        """
        load a Gaussian sigma curve for interpolation or matching
        :return:
        """
        # get the column ascii file name
        file_filter = 'Data Files (*.dat);;All Files (*.*)'
        twotheta_sigma_file_name = QFileDialog.getOpenFileName(
            self, self._working_dir, '2theta Gaussian-Sigma File', file_filter)
        if not twotheta_sigma_file_name:
            return
        if isinstance(twotheta_sigma_file_name, tuple):
            twotheta_sigma_file_name = twotheta_sigma_file_name[0]

        # set the file to controller
        try:
            vec_x, vec_y = self._controller.import_2theta_gauss_sigma_file(
                twotheta_sigma_file_name)
            self.ui.graphicsView_integration1DView.plot_2theta_model(
                vec_x, vec_y)
        except RuntimeError as run_err:
            guiutility.show_message(self, str(run_err))

    def menu_table_select_all(self):
        """
        select all rows in table
        :return:
        """
        self.ui.tableView_summary.select_all_rows(True)

    def menu_table_select_none(self):
        """
        de-select all rows in the able
        :return:
        """
        self.ui.tableView_summary.select_all_rows(False)

    def add_scans(self, scan_pt_list):
        """
        add scans' information to table, i.e., add line
        :param scan_pt_list:
        :return:
        """
        # check input
        assert isinstance(scan_pt_list, list), 'Scan-Pt-Infos {} must be a list but not a {}.' \
                                               ''.format(scan_pt_list, type(scan_pt_list))

        # sort the scans
        scan_pt_list = sorted(scan_pt_list)

        for scan_pt_info in scan_pt_list:
            scan_number, pt_number, hkl, two_theta = scan_pt_info
            self.ui.tableView_summary.add_scan_pt(scan_number, pt_number, hkl,
                                                  two_theta)
        # END-FOR

        return

    def add_scan(self, scan_number, pt_number, hkl_str, two_theta):
        """
        add single scan
        :param scan_number:
        :param pt_number:
        :param hkl_str:
        :param two_theta:
        :return:
        """
        self.ui.tableView_summary.add_scan_pt(scan_number, pt_number, hkl_str,
                                              two_theta)

    def change_scan_number(self, increment):
        """
        change the scan number in the
        :param increment:
        :return:
        """
        # FIXME - 20180809 - This behaviors weird... Need debugging output - TODO
        # get the list of scan number from the table, in future, a real-time updated list shall be used.
        run_number_list = list()
        for irow in range(self.ui.tableView_summary.rowCount()):
            run_number_list.append(
                self.ui.tableView_summary.get_scan_number(irow))
        curr_scan = int(self.ui.lineEdit_Scan.text())
        try:
            curr_scan_index = run_number_list.index(curr_scan)
        except IndexError:
            curr_scan_index = 0

        next_scan_index = curr_scan_index + increment
        next_scan_index = (next_scan_index +
                           len(run_number_list)) % len(run_number_list)

        # set
        self.ui.lineEdit_Scan.setText('{}'.format(
            run_number_list[next_scan_index]))

        return

    def set_experiment(self, exp_number):
        """ set experiment number to this window for convenience
        :param exp_number:
        :return:
        """
        assert isinstance(exp_number, int) and exp_number > 0, 'Experiment number {} (of type {} now) must be a ' \
                                                               'positive integer'.format(exp_number, type(exp_number))
        self._exp_number = exp_number

        return
class OptimizeLatticeWindow(QMainWindow):
    """
    Main window widget to set up parameters to optimize
    """

    # establish signal for communicating from App2 to App1 - must be defined before the constructor
    mySignal = pyqtSignal(int)

    def __init__(self, parent=None):
        """
        Initialization
        :param parent:
        :return:
        """
        # init
        QMainWindow.__init__(self, parent)

        ui_path = "OptimizeLattice.ui"
        self.ui = load_ui(__file__, ui_path, baseinstance=self)

        # initialize widgets
        self.ui.comboBox_unitCellTypes.addItems([
            'Cubic', 'Tetragonal', 'Orthorhombic', 'Hexagonal', 'Rhombohedral',
            'Monoclinic', 'Triclinic'
        ])

        self.ui.comboBox_ubSource.addItems(
            ['Tab - Calculate UB Matrix', 'Tab - Accepted UB Matrix'])

        self.ui.lineEdit_tolerance.setText('0.12')

        # define event handling
        self.ui.pushButton_Ok.clicked.connect(self.do_ok)
        self.ui.pushButton_cancel.clicked.connect(self.do_quit)

        if parent is not None:
            # connect to the method to refine UB matrix by constraining lattice parameters
            self.mySignal.connect(parent.refine_ub_lattice)

        # flag to trace back its previous step
        self._prevIndexByFFT = False

        return

    def do_ok(self):
        """
        User decide to go on and then send a signal to parent
        :return:
        """

        tolerance = self.get_tolerance()
        if tolerance is None:
            raise RuntimeError('Tolerance cannot be left blank!')

        # set up a hand-shaking signal
        signal_value = 1000
        self.mySignal.emit(signal_value)

        # quit
        self.do_quit()

        return

    def do_quit(self):
        """
        Quit the window
        :return:
        """
        self.close()

        return

    def get_unit_cell_type(self):
        """
        Get the tolerance
        :return:
        """
        unit_cell_type = str(self.ui.comboBox_unitCellTypes.currentText())

        return unit_cell_type

    def get_tolerance(self):
        """
        Get the tolerance for refining UB matrix with unit cell type.
        :return:
        """
        tol_str = str(self.ui.lineEdit_tolerance.text()).strip()

        if len(tol_str) == 0:
            # blank: return None
            tol = None
        else:
            tol = float(tol_str)

        return tol

    def get_ub_source(self):
        """
        Get the index of the tab where the UB matrix comes from
        :return:
        """
        source = str(self.ui.comboBox_ubSource.currentText())

        if source == 'Tab - Calculate UB Matrix':
            tab_index = 3
        else:
            tab_index = 4

        return tab_index

    def set_prev_ub_refine_method(self, use_fft=False):
        """

        :param use_fft:
        :return:
        """
        self._prevIndexByFFT = use_fft

        return
Beispiel #5
0
class MergePeaksThread(QThread):
    """A thread to integrate peaks

    """
    # signal to report state: (1) scan, (2) message
    mergeMsgSignal = pyqtSignal(int, str)
    saveMsgSignal = pyqtSignal(int, str)

    def __init__(self, main_window, exp_number, scan_number_list,
                 md_file_list):
        """Initialization

        :param main_window:
        :param exp_number:
        :param scan_number_list: list of tuples for scan as (scan number, pt number list, state as merged)
        :param md_file_list:
        """
        # check
        assert main_window is not None, 'Main window cannot be None'
        assert isinstance(exp_number,
                          int), 'Experiment number must be an integer.'
        assert isinstance(scan_number_list, list), 'Scan (info) tuple list {0} must be a list but not {1}.' \
                                                   ''.format(scan_number_list, type(scan_number_list))
        assert isinstance(md_file_list, list) or md_file_list is None, 'Output MDWorkspace file name list {0} ' \
                                                                       'must be either a list or None but not {1}.' \
                                                                       ''.format(md_file_list, type(md_file_list))

        if md_file_list is not None and len(scan_number_list) != len(
                md_file_list):
            raise RuntimeError(
                'If MD file list is not None, then it must have the same size ({0}) as the '
                'scans ({1}) to merge.'.format(len(md_file_list),
                                               len(scan_number_list)))

        # start thread
        QThread.__init__(self)

        # set values
        self._mainWindow = main_window
        self._expNumber = exp_number
        self._scanNumberList = scan_number_list[:]
        self._outputMDFileList = None
        if md_file_list is not None:
            self._outputMDFileList = md_file_list[:]

        # other about preprocessed options
        self._checkPreprocessedScans = False
        self._preProcessedDir = None
        self._redoMerge = True

        # link signals
        self.mergeMsgSignal.connect(self._mainWindow.update_merge_value)
        self.saveMsgSignal.connect(self._mainWindow.update_file_name)

        return

    def __del__(self):
        """Delete signal

        :return:
        """
        self.wait()

        return

    def run(self):
        """Execute the thread!

        i.e., merging the scans
        :return:
        """
        if self._outputMDFileList is None or len(self._outputMDFileList) == 0:
            save_file = False
        else:
            save_file = True

        for index, scan_number in enumerate(self._scanNumberList):
            # set up merging parameters
            pt_number_list = list()

            # emit signal for run start (mode 0)
            # self.peakMergeSignal.emit(scan_number, 'In merging')
            self.mergeMsgSignal.emit(scan_number, 'Being merged')

            # merge if not merged
            merged_ws_name = None
            out_file_name = 'No File To Save'
            try:
                status, ret_tup = self._mainWindow.controller.merge_pts_in_scan(
                    exp_no=self._expNumber,
                    scan_no=scan_number,
                    pt_num_list=pt_number_list,
                    rewrite=self._redoMerge,
                    preprocessed_dir=self._preProcessedDir)
                if status:
                    merged_ws_name = str(ret_tup[0])
                    error_message = ''
                else:
                    error_message = str(ret_tup)

                # save
                if save_file:
                    out_file_name = self._outputMDFileList[index]
                    self._mainWindow.controller.save_merged_scan(
                        exp_number=self._expNumber,
                        scan_number=scan_number,
                        pt_number_list=pt_number_list,
                        merged_ws_name=merged_ws_name,
                        output=out_file_name)
                # END-IF-ELSE

            except RuntimeError as run_err:
                # error
                status = False
                error_message = 'Failed: {0}'.format(run_err)

            # continue to
            if status:
                # successfully merge peak
                assert merged_ws_name is not None, 'Impossible situation'
                self.mergeMsgSignal.emit(scan_number, merged_ws_name)
                self.saveMsgSignal.emit(scan_number, out_file_name)
            else:
                # merging error
                self.mergeMsgSignal.emit(scan_number, error_message)
                continue
            # END-IF

        return

    def set_pre_process_options(self, option_to_use, pre_process_dir):
        """
        set the pre-process options
        :param option_to_use:
        :param pre_process_dir:
        :return:
        """
        # check
        assert isinstance(option_to_use, bool), 'Option to use pre-process must be a boolean but not a {0}.' \
                                                ''.format(type(option_to_use))

        self._checkPreprocessedScans = option_to_use

        if self._checkPreprocessedScans:
            assert isinstance(pre_process_dir, str), 'Directory {0} to store preprocessed data must be a string ' \
                                                     'but not a {1).'.format(pre_process_dir, type(pre_process_dir))
            if os.path.exists(pre_process_dir) is False:
                raise RuntimeError(
                    'Directory {0} does not exist.'.format(pre_process_dir))
            self._preProcessedDir = pre_process_dir
        # END-IF

        return

    def set_rewrite(self, flag):
        """
        set the flag to re-merge the scan regardless whether the target workspace is in memory
        or a pre-processed MD workspace does exist.
        :param flag:
        :return:
        """
        assert isinstance(
            flag, bool
        ), 'Re-merge/re-write flag must be a boolean but not a {0}'.format(
            type(flag))

        self._redoMerge = flag

        return
Beispiel #6
0
class AddPeaksThread(QThread):
    """
    A QThread class to add peaks to Mantid to calculate UB matrix
    """
    # signal for a peak is added: int_0 = experiment number, int_1 = scan number
    peakAddedSignal = pyqtSignal(int, int)
    # signal for status: int_0 = experiment number, int_1 = scan number, int_2 = progress (0...)
    peakStatusSignal = pyqtSignal(int, int, int)
    # signal for final error report: int_0 = experiment number, str_1 = error message
    peakAddedErrorSignal = pyqtSignal(int, str)

    def __init__(self, main_window, exp_number, scan_number_list):
        """
        Initialization
        :param main_window:
        :param exp_number:
        :param scan_number_list:
        """
        # check
        assert main_window is not None, 'Main window cannot be None'
        assert isinstance(exp_number,
                          int), 'Experiment number must be an integer.'
        assert isinstance(scan_number_list, list), 'Scan number list must be a list but not %s.' \
                                                   '' % str(type(scan_number_list))

        # init thread
        super(AddPeaksThread, self).__init__()

        # set values
        self._mainWindow = main_window
        self._expNumber = exp_number
        self._scanNumberList = scan_number_list

        # connect to the updateTextEdit slot defined in app1.py
        self.peakAddedSignal.connect(self._mainWindow.update_peak_added_info)
        self.peakStatusSignal.connect(
            self._mainWindow.update_adding_peaks_status)
        self.peakAddedErrorSignal.connect(
            self._mainWindow.report_peak_addition)

        return

    def __del__(self):
        """
        Delete signal
        :return:
        """
        self.wait()

        return

    def run(self):
        """
        method for thread is running
        :return:
        """
        # declare list of failed
        failed_list = list()

        # loop over all scan numbers
        for index, scan_number in enumerate(self._scanNumberList):
            # update state
            self.peakStatusSignal.emit(self._expNumber, scan_number, index)

            # merge peak
            status, err_msg = self._mainWindow.controller.merge_pts_in_scan(
                self._expNumber, scan_number, [], False,
                self._mainWindow.controller.pre_processed_dir)

            # continue to the next scan if there is something wrong
            if status is False:
                failed_list.append((scan_number, err_msg))
                continue

            # find peak
            self._mainWindow.controller.find_peak(self._expNumber, scan_number)

            # get PeakInfo
            peak_info = self._mainWindow.controller.get_peak_info(
                self._expNumber, scan_number)
            assert isinstance(peak_info, r4c.PeakProcessRecord)

            # send signal to main window for peak being added
            self.peakAddedSignal.emit(self._expNumber, scan_number)
        # END-FOR

        # send signal with unphysical scan number to flag the end of operation.
        self.peakStatusSignal.emit(self._expNumber, -1,
                                   len(self._scanNumberList))

        # construct a final error message for main GUI
        # TEST: Exp 423 Scan 82
        if len(failed_list) > 0:
            failed_scans_str = 'Unable to merge scans: '
            sum_error_str = ''
            for fail_tup in failed_list:
                failed_scans_str += '%d, ' % fail_tup[0]
                sum_error_str += '%s\n' % fail_tup[1]
            # END-FOR

            self.peakAddedErrorSignal.emit(
                self._expNumber, failed_scans_str + '\n' + sum_error_str)
        # END-IF

        return
Beispiel #7
0
class IntegratePeaksThread(QThread):
    """
    A thread to integrate peaks
    """
    # signal to emit before a merge/integration status: exp number, scan number, progress, mode
    peakMergeSignal = pyqtSignal(int, int, float, list, int)
    # signal to report state: (1) experiment, (2) scan, (3) mode, (4) message
    mergeMsgSignal = pyqtSignal(int, int, int, str)

    def __init__(self,
                 main_window,
                 exp_number,
                 scan_tuple_list,
                 mask_det,
                 mask_name,
                 norm_type,
                 num_pt_bg_left,
                 num_pt_bg_right,
                 scale_factor=1.000):
        """

        :param main_window:
        :param exp_number:
        :param scan_tuple_list: list of tuples for scan as (scan number, pt number list, state as merged)
        :param mask_det:
        :param mask_name:
        :param norm_type: type of normalization
        :param num_pt_bg_left: number of Pt in the left
        :param num_pt_bg_right: number of Pt for background in the right
        """
        # start thread
        QThread.__init__(self)

        # check
        assert main_window is not None, 'Main window cannot be None'
        assert isinstance(exp_number,
                          int), 'Experiment number must be an integer.'
        assert isinstance(scan_tuple_list, list), 'Scan (info) tuple list must be a list but not %s.' \
                                                  '' % str(type(scan_tuple_list))
        assert isinstance(mask_det, bool), 'Parameter mask_det must be a boolean but not %s.' \
                                           '' % str(type(mask_det))
        assert isinstance(
            mask_name, str), 'Name of mask must be a string but not %s.' % str(
                type(mask_name))
        assert isinstance(norm_type, str), 'Normalization type must be a string but not %s.' \
                                           '' % str(type(norm_type))
        assert isinstance(num_pt_bg_left, int) and num_pt_bg_left >= 0,\
            'Number of Pt at left for background {0} must be non-negative integers but not of type {1}.' \
            ''.format(num_pt_bg_left, type(num_pt_bg_left))
        assert isinstance(num_pt_bg_right, int) and num_pt_bg_right >= 0,\
            'Number of Pt at right for background {0} must be non-negative integers but not of type {1}.' \
            ''.format(num_pt_bg_right, type(num_pt_bg_right))

        # set values
        self._mainWindow = main_window
        self._expNumber = exp_number
        self._scanTupleList = scan_tuple_list[:]
        self._maskDetector = mask_det
        self._normalizeType = norm_type
        self._selectedMaskName = mask_name
        self._numBgPtLeft = num_pt_bg_left
        self._numBgPtRight = num_pt_bg_right
        self._scaleFactor = scale_factor

        # other about preprocessed options
        self._checkPreprocessedScans = True

        # link signals
        self.peakMergeSignal.connect(self._mainWindow.update_merge_value)
        self.mergeMsgSignal.connect(self._mainWindow.update_merge_message)

        return

    def __del__(self):
        """
        Delete signal
        :return:
        """
        self.wait()

        return

    def run(self):
        """
        Execute the thread!
        :return:
        """
        for index, scan_tup in enumerate(self._scanTupleList):
            # check
            assert isinstance(scan_tup, tuple) and len(scan_tup) == 3
            scan_number, pt_number_list, merged = scan_tup

            # emit signal for run start (mode 0)
            mode = int(0)
            self.peakMergeSignal.emit(self._expNumber, scan_number,
                                      float(index), [0., 0., 0.], mode)

            # merge if not merged
            if merged is False:
                merged_ws_name = 'X'
                try:
                    pre_dir = self._mainWindow.controller.pre_processed_dir
                    status, ret_tup = \
                        self._mainWindow.controller.merge_pts_in_scan(exp_no=self._expNumber,
                                                                      scan_no=scan_number,
                                                                      pt_num_list=pt_number_list,
                                                                      rewrite=False,
                                                                      preprocessed_dir=pre_dir)

                    if status:
                        merged_ws_name = str(ret_tup[0])
                        error_message = ''
                    else:
                        error_message = str(ret_tup)
                except RuntimeError as run_err:
                    status = False
                    error_message = str(run_err)

                # continue to
                if status:
                    # successfully merge peak
                    assert isinstance(merged_ws_name, str), 'Merged workspace %s must be a string but not %s.' \
                                                            '' % (str(merged_ws_name), type(merged_ws_name))
                    self.mergeMsgSignal.emit(self._expNumber, scan_number, 1,
                                             merged_ws_name)
                else:
                    self.mergeMsgSignal.emit(self._expNumber, scan_number, 0,
                                             error_message)
                    continue
                # self._mainWindow.ui.tableWidget_mergeScans.set_status(scan_number, 'Merged')
            else:
                # merged
                pass
            # END-IF

            # calculate peak center
            try:
                # status, ret_obj = self._mainWindow.controller.calculate_peak_center(self._expNumber, scan_number,
                #                                                                     pt_number_list)
                status, ret_obj = self._mainWindow.controller.find_peak(
                    self._expNumber, scan_number, pt_number_list)

            except RuntimeError as run_err:
                status = False
                ret_obj = 'RuntimeError: %s.' % str(run_err)
            except AssertionError as ass_err:
                status = False
                ret_obj = 'AssertionError: %s.' % str(ass_err)

            if status:
                center_i = ret_obj  # 3-tuple
            else:
                error_msg = 'Unable to find peak for exp %d scan %d: %s.' % (
                    self._expNumber, scan_number, str(ret_obj))
                # no need... self._mainWindow.controller.set_peak_intensity(self._expNumber, scan_number, 0.)
                self._mainWindow.ui.tableWidget_mergeScans.set_peak_intensity(
                    None, scan_number, 0., False)
                self._mainWindow.ui.tableWidget_mergeScans.set_status(
                    scan_number, error_msg)
                continue

            # check given mask workspace
            if self._maskDetector:
                self._mainWindow.controller.check_generate_mask_workspace(
                    self._expNumber,
                    scan_number,
                    self._selectedMaskName,
                    check_throw=True)

            bkgd_pt_list = (self._numBgPtLeft, self._numBgPtRight)
            # integrate peak
            try:
                status, ret_obj = self._mainWindow.controller.integrate_scan_peaks(
                    exp=self._expNumber,
                    scan=scan_number,
                    peak_radius=1.0,
                    peak_centre=center_i,
                    merge_peaks=False,
                    use_mask=self._maskDetector,
                    normalization=self._normalizeType,
                    mask_ws_name=self._selectedMaskName,
                    scale_factor=self._scaleFactor,
                    background_pt_tuple=bkgd_pt_list)
            except ValueError as val_err:
                status = False
                ret_obj = 'Unable to integrate scan {0} due to {1}.'.format(
                    scan_number, str(val_err))
            except RuntimeError as run_err:
                status = False
                ret_obj = 'Unable to integrate scan {0}: {1}.'.format(
                    scan_number, run_err)

            # handle integration error
            if status:
                # get PT dict
                pt_dict = ret_obj
                assert isinstance(pt_dict, dict), 'dictionary must'
                self.set_integrated_peak_info(scan_number, pt_dict)
                # information setup include
                # - lorentz correction factor
                # - peak integration dictionary
                # - motor information: peak_info_obj.set_motor(motor_name, motor_step, motor_std_dev)
            else:
                # integration failed
                error_msg = str(ret_obj)
                self.mergeMsgSignal.emit(self._expNumber, scan_number, 0,
                                         error_msg)
                continue

            intensity1 = pt_dict['simple intensity']
            peak_centre = self._mainWindow.controller.get_peak_info(
                self._expNumber, scan_number).get_peak_centre()

            # emit signal to main app for peak intensity value
            mode = 1
            # center_i
            self.peakMergeSignal.emit(self._expNumber, scan_number,
                                      float(intensity1), list(peak_centre),
                                      mode)
        # END-FOR

        # terminate the process
        mode = int(2)
        self.peakMergeSignal.emit(self._expNumber, -1,
                                  len(self._scanTupleList), [0, 0, 0], mode)
        # self._mainWindow.ui.tableWidget_mergeScans.select_all_rows(False)

        return

    def set_integrated_peak_info(self, scan_number, peak_integration_dict):
        """
        set the integrated peak information including
        * calculate Lorentz correction
        * add the integration result dictionary
        * add motor step information
        :return:
        """
        # get peak information
        peak_info_obj = self._mainWindow.controller.get_peak_info(
            self._expNumber, scan_number)

        # get Q-vector of the peak center and calculate |Q| from it
        peak_center_q = peak_info_obj.get_peak_centre_v3d().norm()
        # get wave length
        wavelength = self._mainWindow.controller.get_wave_length(
            self._expNumber, [scan_number])

        # get motor step (choose from omega, phi and chi)
        try:
            motor_move_tup = self._mainWindow.controller.get_motor_step(
                self._expNumber, scan_number)
            motor_name, motor_step, motor_std_dev = motor_move_tup
        except RuntimeError as run_err:
            return str(run_err)
        except AssertionError as ass_err:
            return str(ass_err)

        # calculate lorentz correction
        # TODO/FIXME/NOW2 : peak center Q shall be from calculation!
        lorentz_factor = peak_integration_utility.calculate_lorentz_correction_factor(
            peak_center_q, wavelength, motor_step)

        peak_info_obj.lorentz_correction_factor = lorentz_factor
        # set motor
        peak_info_obj.set_motor(motor_name, motor_step, motor_std_dev)
        # set peak integration dictionary
        peak_info_obj.set_integration(peak_integration_dict)

        return
Beispiel #8
0
class Worker(QThread):
    """
    使用示例:
        # 在activity 类中声明一个func thread

        self.delete_file_func = Worker()

        # 为线程信号绑定槽

        self.delete_file_func.thread_signal.connect(....)    # 正常通信频道
        self.delete_file_func.thread_error_signal.connect(....)    # 异常通信频道

    """
    # 正常通信信号
    __success = pyqtSignal(Response)
    # 异常通信信号
    __error = pyqtSignal(Response)
    # 终止信号
    __end = pyqtSignal(Response)
    # 是否在运行
    __is_working = False

    @property
    def success(self):
        return self.__success

    @property
    def error(self):
        return self.__error

    @property
    def end(self):
        return self.__end

    @property
    def running(self):
        return self.__is_working

    def strike(self, response=None):
        """
        手动激发
        :param response:
        :return:
        """
        # noinspection PyUnresolvedReferences
        self.end.emit(response or Response())

    def __init__(self):
        super().__init__()
        self.func = None
        self._id = uuid.uuid4().hex
        self.func_args = []
        self.func_kwargs = {}
        # noinspection PyUnresolvedReferences
        self.success.connect(lambda: None)
        # noinspection PyUnresolvedReferences
        self.error.connect(lambda: None)

    def set_func(self, func, task_id=None, *args, **kwargs):
        self.func_args = list(args)
        self.func_kwargs = kwargs
        self.func = func
        self._id = task_id
        logging.info("设置线程 func={}  arg={}  kwargs={}".format(
            func.__name__, args, kwargs))

    def kill(self):
        self.__is_working = False
        self.terminate()

    def run(self):
        self.__is_working = True
        response = Response()
        response.task_id = self._id
        if not self.func:
            return
        try:
            result = self.func(*self.func_args, **self.func_kwargs)
            response.data = result
            self.thread_signal.emit(response)
        except Exception as e:
            response.code = 1
            response.data = e
            # noinspection PyUnresolvedReferences
            self.error.emit(response)
            logging.exception(
                "执行失败 func={}  arg={}  kwargs={}, error={}".format(
                    self.func.__name__, self.func_args, self.func_kwargs, e))
        finally:
            # noinspection PyUnresolvedReferences
            self.end.emit(response)
        self.__is_working = False
Beispiel #9
0
class MyNavigation(QObject):
    mpl_mv = pyqtSignal(float, float, name='mpl_move')
    padWidth = 2

    def __init__(self, ax, mplwidget=None):
        super(MyNavigation, self).__init__()
        self.mplwidget = mplwidget
        self.ax = ax
        self.figure = ax.figure
        self.canvas = ax.figure.canvas
        self.zoomer = None
        self.mode = "normal"
        self.drag = False
        self.interval = None
        self.distance = None
        self.curve = myCurve(self.ax)
        self.canvas.mpl_connect("button_press_event", self.on_press)
        self.canvas.mpl_connect("button_release_event", self.on_release)
        self.canvas.mpl_connect("motion_notify_event", self.on_move)
        self.canvas.mpl_connect("scroll_event", self.on_scroll)
        self.canvas.mpl_connect("axes_leave_event", self.on_leave_axes)
        self.canvas.mpl_connect("axes_enter_event", self.on_enter_axes)

    def on_enter_axes(self, event):
        if event.inaxes == self.ax and self.mplwidget:
            if self.mode == "zoom":
                self.mplwidget.setCursor(Qt.CrossCursor)
            if self.mode == "pan":
                self.mplwidget.setCursor(Qt.OpenHandCursor)
            if self.mode == "interval":
                self.mplwidget.setCursor(Qt.SizeHorCursor)
            if self.mode == "curve":
                self.mplwidget.setCursor(Qt.CrossCursor)

    def on_leave_axes(self, event):
        if event.inaxes == self.ax and self.mplwidget:
            self.mplwidget.setCursor(Qt.ArrowCursor)

    def on_press(self, event):
        if event.inaxes != self.ax:
            return
        self.startx, self.starty = event.xdata, event.ydata

        if event.button == 1 and self.mode == "zoom":
            self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
            self.zoomer = patches.Rectangle((self.startx, self.starty),
                                            0,
                                            0,
                                            fill=False,
                                            ls="dashed",
                                            transform=self.ax.transData)
            self.zoomer.set_animated(True)
            self.ax.add_patch(self.zoomer)
            self.ax.draw_artist(self.zoomer)
            self.canvas.blit(self.ax.bbox)

        if event.button == 1 and self.mode == "pan":
            if self.mplwidget:
                self.mplwidget.setCursor(Qt.ClosedHandCursor)
            self.pcx_startx, self.pcx_starty = event.x, event.y
            self.bg = self.canvas.copy_from_bbox(
                self.ax.bbox.padded(-self.padWidth))
            self.b = self.bg.get_extents()
            for l in self.ax.lines:
                l.set_animated(True)
            self.ax.patch.set_animated(True)
            self.canvas.restore_region(self.bg)
            self.canvas.blit(self.ax.bbox.padded(-self.padWidth))
            self.drag = True

        if event.button == 1 and self.mode == "interval":
            self.remove_interval()
            self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
            self.interval = self.ax.axvspan(self.startx,
                                            self.startx,
                                            fc="gray",
                                            alpha=0.2)
            self.interval.set_animated(True)
            self.drag = True

        if event.button == 1 and self.mode == "distance":
            self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
            self.distance, = self.ax.plot([self.startx, self.startx],
                                          [self.starty, self.starty],
                                          "black",
                                          ls="-.")
            self.txt = self.ax.text(self.startx,
                                    self.starty,
                                    "(%8.3e,%8.3e)\n%8.3e" % (0, 0, 0),
                                    ha='center')
            self.txt.set_animated(True)
            self.distance.set_animated(True)
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.txt)
            self.canvas.blit(self.ax.bbox)
            self.drag = True

        if event.button == 1 and self.mode == "curve":
            self.curve.add_point((event.xdata, event.ydata))
            self.curve.l.set_animated(True)
            self.canvas.draw()
            self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
            self.drag = True
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.curve.l)
            self.canvas.blit(self.ax.bbox)

    def on_move(self, event):
        if event.inaxes != self.ax:
            return
        self.curx, self.cury = event.xdata, event.ydata
        if self.mplwidget:
            self.mpl_mv.emit(self.curx, self.cury)
        if self.zoomer is not None:
            w = event.xdata - self.startx
            h = event.ydata - self.starty
            self.zoomer.set_width(w)
            self.zoomer.set_height(h)
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.zoomer)
            self.canvas.blit(self.ax.bbox)
        if self.mode == "pan" and self.drag:
            dx = event.x - self.pcx_startx
            dy = event.y - self.pcx_starty
            self.bg.set_x(self.b[0] + dx)
            self.bg.set_y(self.b[1] - dy)
            self.ax.draw_artist(self.ax.patch)
            self.canvas.restore_region(self.bg)
            self.canvas.blit(self.ax.bbox.padded(-self.padWidth))
        if self.mode == "interval" and self.drag:
            xy = self.interval.get_xy()
            xy[2, 0] = event.xdata
            xy[3, 0] = event.xdata
            self.interval.set_xy(xy)
            self.interval.set_xy(xy)
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.interval)
            self.canvas.blit(self.ax.bbox)
        if self.mode == "distance" and self.drag:
            self.distance.set_xdata([self.startx, self.curx])
            self.distance.set_ydata([self.starty, self.cury])
            self.txt.set_x(0.5 * (self.startx + self.curx))
            self.txt.set_y(0.5 * (self.starty + self.cury))
            dx = abs(self.startx - self.curx)
            dy = abs(self.starty - self.cury)
            self.txt.set_text("(%8.3e,%8.3e)\n%8.3e" %
                              (dx, dy, np.sqrt(dx * dx + dy * dy)))
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.distance)
            self.ax.draw_artist(self.txt)
            self.canvas.blit(self.ax.bbox)

        if self.mode == "curve" and self.drag:
            self.curve.change_point((self.curx, self.cury))
            self.canvas.restore_region(self.bg)
            self.ax.draw_artist(self.curve.l)
            self.canvas.blit(self.ax.bbox)

    def on_release(self, event):
        if event.inaxes == self.ax:
            self.curx, self.cury = event.xdata, event.ydata
        if self.zoomer is not None:
            x1 = self.startx
            x2 = self.startx + self.zoomer.get_width()
            y1 = self.starty
            y2 = self.starty + self.zoomer.get_height()
            if x2 < x1:
                x2, x1 = x1, x2
            if y2 < y1:
                y2, y1 = y1, y2
            self.zoomer.remove()
            self.zoomer = None
            if x1 != x2 and y1 != y2:
                self.ax.set_xlim(x1, x2)
                self.ax.set_ylim(y1, y2)
            self.canvas.draw()
        if self.mode == "zoom" and event.button == 3 and event.inaxes == self.ax:
            self.ax.autoscale()
            self.canvas.draw()
        if self.mode == "pan" and self.drag:
            if self.mplwidget:
                self.mplwidget.setCursor(Qt.OpenHandCursor)
            for l in self.ax.lines:
                l.set_animated(False)
            self.ax.patch.set_animated(False)
            self.drag = False
            dx = self.curx - self.startx
            dy = self.cury - self.starty
            b = self.ax.get_xlim()
            self.ax.set_xlim(b[0] - dx, b[1] - dx)
            b = self.ax.get_ylim()
            self.ax.set_ylim(b[0] - dy, b[1] - dy)
            self.canvas.draw()
        if self.mode == "interval" and self.drag and event.inaxes == self.ax:
            self.interval.set_animated(False)
            x1, x2 = self.get_interval()
            if x1 == x2:
                self.remove_interval()
            self.canvas.draw()
            self.drag = False

        if self.mode == "curve" and self.drag and event.inaxes == self.ax:
            self.curve.change_point((self.curx, self.cury))
            self.curve.l.set_animated(False)
            self.canvas.draw()
            self.drag = False
        if self.mode == "distance" and self.drag and event.inaxes == self.ax:
            self.distance.remove()
            self.txt.remove()
            del self.txt
            del self.distance
            self.canvas.draw()
            self.drag = False

    def on_scroll(self, event):
        if self.mode != 'zoom':
            return
        scale = -1
        if event.button == "up":
            scale = 1
        b = self.ax.get_xbound()
        self.ax.set_xlim(b[0] - scale * 0.1 * (b[1] - b[0]),
                         b[1] + scale * 0.1 * (b[1] - b[0]))
        b = self.ax.get_ybound()
        self.ax.set_ylim(b[0] - scale * 0.1 * (b[1] - b[0]),
                         b[1] + scale * 0.1 * (b[1] - b[0]))
        self.canvas.draw()

    def get_interval(self):
        if self.interval:
            x1 = self.interval.xy[0, 0]
            x2 = self.interval.xy[2, 0]
            if x2 < x1:
                x1, x2 = x2, x1
            return (x1, x2)
        else:
            return None

    def remove_interval(self):
        if self.interval:
            self.interval.remove()
            self.canvas.draw()
            del self.interval
            self.interval = None

    def apply_curve_to_line(self, linenum):
        if len(self.ax.get_lines()) < linenum + 1:
            return
        if len(self.curve.points) < 2:
            return
        c = self.ax.get_lines()[linenum]
        xs, ys = list(
            zip(*np.sort(np.array(self.curve.points,
                                  dtype=[("x", float), ("y", float)]),
                         order="x")))
        n1 = np.searchsorted(c.get_xdata(), xs[0])
        n2 = np.searchsorted(c.get_xdata(), xs[-1])
        xx = c.get_xdata()[n1:n2]
        yy = np.interp(xx, xs, ys)
        zz = c.get_ydata()
        zz[n1:n2] = yy
        c.set_ydata(zz)
        self.canvas.draw_idle()

    def pinch_line_to_curve(self, linenum):
        if len(self.ax.get_lines()) < linenum + 1:
            return
        if len(self.curve.points) < 2:
            return
        c = self.ax.get_lines()[linenum]
        xxs, yys = list(
            zip(*np.sort(np.array(self.curve.points,
                                  dtype=[("x", float), ("y", float)]),
                         order="x")))
        n1 = np.searchsorted(c.get_xdata(), xxs[0])
        n2 = np.searchsorted(c.get_xdata(), xxs[-1])
        xs = c.get_xdata()
        ys = c.get_ydata()

        def f(x):
            return np.interp(x, xxs, yys)

        for n in range(n1 + 1, n2):
            ss = ys[n] - f(xs[n])
            ys[n] = 0.5 * ss + f(xs[n])
        c.set_data(xs, ys)
        self.canvas.draw_idle()

    def interpolate_line_on_interval(self, linenum, inteprolation_degree):
        if len(self.ax.get_lines()) < linenum + 1:
            return
        c = self.ax.get_lines()[linenum]
        xs = c.get_xdata()
        ys = c.get_ydata()
        if self.get_interval() is not None:
            x1, x2 = self.get_interval()
            n1 = np.array(xs).searchsorted(x1)
            n2 = np.array(xs).searchsorted(x2)
        else:
            n1 = 0
            n2 = len(xs)
        if n1 == n2:
            return
        xs = xs[n1:n2]
        ys = ys[n1:n2]
        p = np.polyfit(xs, ys, inteprolation_degree)
        ys = np.polyval(p, xs)
        self.curve.points = list(zip(xs, ys))
        self.canvas.draw_idle()