class ArrayStackPlot(qt.QWidget): """ Widget for plotting a n-D array (n >= 3) as a stack of images. Three axis arrays can be provided to calibrate the axes. The signal array can have an arbitrary number of dimensions, the only limitation being that the last 3 dimensions must have the same length as the axes arrays. Sliders are provided to select indices on the first (n - 3) dimensions of the signal array, and the plot is updated to load the stack corresponding to the selection. """ def __init__(self, parent=None): """ :param parent: Parent QWidget """ super(ArrayStackPlot, self).__init__(parent) self.__signal = None self.__signal_name = None # the Z, Y, X axes apply to the last three dimensions of the signal # (in that order) self.__z_axis = None self.__z_axis_name = None self.__y_axis = None self.__y_axis_name = None self.__x_axis = None self.__x_axis_name = None self._stack_view = StackView(self) self._hline = qt.QFrame(self) self._hline.setFrameStyle(qt.QFrame.HLine) self._hline.setFrameShadow(qt.QFrame.Sunken) self._legend = qt.QLabel(self) self._selector = NumpyAxesSelector(self) self._selector.setNamedAxesSelectorVisibility(False) self.__selector_is_connected = False layout = qt.QVBoxLayout() layout.addWidget(self._stack_view) layout.addWidget(self._hline) layout.addWidget(self._legend) layout.addWidget(self._selector) self.setLayout(layout) def getStackView(self): """Returns the plot used for the display :rtype: StackView """ return self._stack_view def setStackData(self, signal, x_axis=None, y_axis=None, z_axis=None, signal_name=None, xlabel=None, ylabel=None, zlabel=None, title=None): """ :param signal: n-D dataset, whose last 3 dimensions are used as the 3D stack values. :param x_axis: 1-D dataset used as the image's x coordinates. If provided, its lengths must be equal to the length of the last dimension of ``signal``. :param y_axis: 1-D dataset used as the image's y. If provided, its lengths must be equal to the length of the 2nd to last dimension of ``signal``. :param z_axis: 1-D dataset used as the image's z. If provided, its lengths must be equal to the length of the 3rd to last dimension of ``signal``. :param signal_name: Label used in the legend :param xlabel: Label for X axis :param ylabel: Label for Y axis :param zlabel: Label for Z axis :param title: Graph title """ if self.__selector_is_connected: self._selector.selectionChanged.disconnect(self._updateStack) self.__selector_is_connected = False self.__signal = signal self.__signal_name = signal_name or "" self.__x_axis = x_axis self.__x_axis_name = xlabel self.__y_axis = y_axis self.__y_axis_name = ylabel self.__z_axis = z_axis self.__z_axis_name = zlabel self._selector.setData(signal) self._selector.setAxisNames(["Y", "X", "Z"]) self._stack_view.setGraphTitle(title or "") # by default, the z axis is the image position (dimension not plotted) self._stack_view.getPlotWidget().getXAxis().setLabel(self.__x_axis_name or "X") self._stack_view.getPlotWidget().getYAxis().setLabel(self.__y_axis_name or "Y") self._updateStack() ndims = len(signal.shape) self._stack_view.setFirstStackDimension(ndims - 3) # the legend label shows the selection slice producing the volume # (only interesting for ndim > 3) if ndims > 3: self._selector.setVisible(True) self._legend.setVisible(True) self._hline.setVisible(True) else: self._selector.setVisible(False) self._legend.setVisible(False) self._hline.setVisible(False) if not self.__selector_is_connected: self._selector.selectionChanged.connect(self._updateStack) self.__selector_is_connected = True @staticmethod def _get_origin_scale(axis): """Assuming axis is a regularly spaced 1D array, return a tuple (origin, scale) where: - origin = axis[0] - scale = (axis[n-1] - axis[0]) / (n -1) :param axis: 1D numpy array :return: Tuple (axis[0], (axis[-1] - axis[0]) / (len(axis) - 1)) """ return axis[0], (axis[-1] - axis[0]) / (len(axis) - 1) def _updateStack(self): """Update displayed stack according to the current axes selector data.""" stk = self._selector.selectedData() x_axis = self.__x_axis y_axis = self.__y_axis z_axis = self.__z_axis calibrations = [] for axis in [z_axis, y_axis, x_axis]: if axis is None: calibrations.append(NoCalibration()) elif len(axis) == 2: calibrations.append( LinearCalibration(y_intercept=axis[0], slope=axis[1])) else: calibrations.append(ArrayCalibration(axis)) legend = self.__signal_name + "[" for sl in self._selector.selection(): if sl == slice(None): legend += ":, " else: legend += str(sl) + ", " legend = legend[:-2] + "]" self._legend.setText("Displayed data: " + legend) self._stack_view.setStack(stk, calibrations=calibrations) self._stack_view.setLabels(labels=[ self.__z_axis_name, self.__y_axis_name, self.__x_axis_name ]) def clear(self): old = self._selector.blockSignals(True) self._selector.clear() self._selector.blockSignals(old) self._stack_view.clear()
class ArrayVolumePlot(qt.QWidget): """ Widget for plotting a n-D array (n >= 3) as a 3D scalar field. Three axis arrays can be provided to calibrate the axes. The signal array can have an arbitrary number of dimensions, the only limitation being that the last 3 dimensions must have the same length as the axes arrays. Sliders are provided to select indices on the first (n - 3) dimensions of the signal array, and the plot is updated to load the stack corresponding to the selection. """ def __init__(self, parent=None): """ :param parent: Parent QWidget """ super(ArrayVolumePlot, self).__init__(parent) self.__signal = None self.__signal_name = None # the Z, Y, X axes apply to the last three dimensions of the signal # (in that order) self.__z_axis = None self.__z_axis_name = None self.__y_axis = None self.__y_axis_name = None self.__x_axis = None self.__x_axis_name = None from ._VolumeWindow import VolumeWindow self._view = VolumeWindow(self) self._hline = qt.QFrame(self) self._hline.setFrameStyle(qt.QFrame.HLine) self._hline.setFrameShadow(qt.QFrame.Sunken) self._legend = qt.QLabel(self) self._selector = NumpyAxesSelector(self) self._selector.setNamedAxesSelectorVisibility(False) self.__selector_is_connected = False layout = qt.QVBoxLayout() layout.addWidget(self._view) layout.addWidget(self._hline) layout.addWidget(self._legend) layout.addWidget(self._selector) self.setLayout(layout) def getVolumeView(self): """Returns the plot used for the display :rtype: SceneWindow """ return self._view def setData(self, signal, x_axis=None, y_axis=None, z_axis=None, signal_name=None, xlabel=None, ylabel=None, zlabel=None, title=None): """ :param signal: n-D dataset, whose last 3 dimensions are used as the 3D stack values. :param x_axis: 1-D dataset used as the image's x coordinates. If provided, its lengths must be equal to the length of the last dimension of ``signal``. :param y_axis: 1-D dataset used as the image's y. If provided, its lengths must be equal to the length of the 2nd to last dimension of ``signal``. :param z_axis: 1-D dataset used as the image's z. If provided, its lengths must be equal to the length of the 3rd to last dimension of ``signal``. :param signal_name: Label used in the legend :param xlabel: Label for X axis :param ylabel: Label for Y axis :param zlabel: Label for Z axis :param title: Graph title """ if self.__selector_is_connected: self._selector.selectionChanged.disconnect(self._updateVolume) self.__selector_is_connected = False self.__signal = signal self.__signal_name = signal_name or "" self.__x_axis = x_axis self.__x_axis_name = xlabel self.__y_axis = y_axis self.__y_axis_name = ylabel self.__z_axis = z_axis self.__z_axis_name = zlabel self._selector.setData(signal) self._selector.setAxisNames(["Y", "X", "Z"]) self._updateVolume() # the legend label shows the selection slice producing the volume # (only interesting for ndim > 3) if signal.ndim > 3: self._selector.setVisible(True) self._legend.setVisible(True) self._hline.setVisible(True) else: self._selector.setVisible(False) self._legend.setVisible(False) self._hline.setVisible(False) if not self.__selector_is_connected: self._selector.selectionChanged.connect(self._updateVolume) self.__selector_is_connected = True def _updateVolume(self): """Update displayed stack according to the current axes selector data.""" x_axis = self.__x_axis y_axis = self.__y_axis z_axis = self.__z_axis offset = [] scale = [] for axis in [x_axis, y_axis, z_axis]: if axis is None: calibration = NoCalibration() elif len(axis) == 2: calibration = LinearCalibration(y_intercept=axis[0], slope=axis[1]) else: calibration = ArrayCalibration(axis) if not calibration.is_affine(): _logger.warning("Axis has not linear values, ignored") offset.append(0.) scale.append(1.) else: offset.append(calibration(0)) scale.append(calibration.get_slope()) legend = self.__signal_name + "[" for sl in self._selector.selection(): if sl == slice(None): legend += ":, " else: legend += str(sl) + ", " legend = legend[:-2] + "]" self._legend.setText("Displayed data: " + legend) # Update SceneWidget data = self._selector.selectedData() volumeView = self.getVolumeView() volumeView.setData(data, offset=offset, scale=scale) volumeView.setAxesLabels(self.__x_axis_name, self.__y_axis_name, self.__z_axis_name) def clear(self): old = self._selector.blockSignals(True) self._selector.clear() self._selector.blockSignals(old) self.getVolumeView().clear()
class ArrayVolumePlot(qt.QWidget): """ Widget for plotting a n-D array (n >= 3) as a 3D scalar field. Three axis arrays can be provided to calibrate the axes. The signal array can have an arbitrary number of dimensions, the only limitation being that the last 3 dimensions must have the same length as the axes arrays. Sliders are provided to select indices on the first (n - 3) dimensions of the signal array, and the plot is updated to load the stack corresponding to the selection. """ def __init__(self, parent=None): """ :param parent: Parent QWidget """ super(ArrayVolumePlot, self).__init__(parent) self.__signal = None self.__signal_name = None # the Z, Y, X axes apply to the last three dimensions of the signal # (in that order) self.__z_axis = None self.__z_axis_name = None self.__y_axis = None self.__y_axis_name = None self.__x_axis = None self.__x_axis_name = None from silx.gui.plot3d.ScalarFieldView import ScalarFieldView from silx.gui.plot3d import SFViewParamTree self._view = ScalarFieldView(self) def computeIsolevel(data): data = data[numpy.isfinite(data)] if len(data) == 0: return 0 else: return numpy.mean(data) + numpy.std(data) self._view.addIsosurface(computeIsolevel, '#FF0000FF') # Create a parameter tree for the scalar field view options = SFViewParamTree.TreeView(self._view) options.setSfView(self._view) # Add the parameter tree to the main window in a dock widget dock = qt.QDockWidget() dock.setWidget(options) self._view.addDockWidget(qt.Qt.RightDockWidgetArea, dock) self._hline = qt.QFrame(self) self._hline.setFrameStyle(qt.QFrame.HLine) self._hline.setFrameShadow(qt.QFrame.Sunken) self._legend = qt.QLabel(self) self._selector = NumpyAxesSelector(self) self._selector.setNamedAxesSelectorVisibility(False) self.__selector_is_connected = False layout = qt.QVBoxLayout() layout.addWidget(self._view) layout.addWidget(self._hline) layout.addWidget(self._legend) layout.addWidget(self._selector) self.setLayout(layout) def getVolumeView(self): """Returns the plot used for the display :rtype: ScalarFieldView """ return self._view def normalizeComplexData(self, data): """ Converts a complex data array to its amplitude, if necessary. :param data: the data to normalize :return: """ if hasattr(data, "dtype"): isComplex = numpy.issubdtype(data.dtype, numpy.complexfloating) else: isComplex = isinstance(data, numbers.Complex) if isComplex: data = numpy.absolute(data) return data def setData(self, signal, x_axis=None, y_axis=None, z_axis=None, signal_name=None, xlabel=None, ylabel=None, zlabel=None, title=None): """ :param signal: n-D dataset, whose last 3 dimensions are used as the 3D stack values. :param x_axis: 1-D dataset used as the image's x coordinates. If provided, its lengths must be equal to the length of the last dimension of ``signal``. :param y_axis: 1-D dataset used as the image's y. If provided, its lengths must be equal to the length of the 2nd to last dimension of ``signal``. :param z_axis: 1-D dataset used as the image's z. If provided, its lengths must be equal to the length of the 3rd to last dimension of ``signal``. :param signal_name: Label used in the legend :param xlabel: Label for X axis :param ylabel: Label for Y axis :param zlabel: Label for Z axis :param title: Graph title """ signal = self.normalizeComplexData(signal) if self.__selector_is_connected: self._selector.selectionChanged.disconnect(self._updateVolume) self.__selector_is_connected = False self.__signal = signal self.__signal_name = signal_name or "" self.__x_axis = x_axis self.__x_axis_name = xlabel self.__y_axis = y_axis self.__y_axis_name = ylabel self.__z_axis = z_axis self.__z_axis_name = zlabel self._selector.setData(signal) self._selector.setAxisNames(["Y", "X", "Z"]) self._view.setAxesLabels(self.__x_axis_name or 'X', self.__y_axis_name or 'Y', self.__z_axis_name or 'Z') self._updateVolume() # the legend label shows the selection slice producing the volume # (only interesting for ndim > 3) if signal.ndim > 3: self._selector.setVisible(True) self._legend.setVisible(True) self._hline.setVisible(True) else: self._selector.setVisible(False) self._legend.setVisible(False) self._hline.setVisible(False) if not self.__selector_is_connected: self._selector.selectionChanged.connect(self._updateVolume) self.__selector_is_connected = True def _updateVolume(self): """Update displayed stack according to the current axes selector data.""" data = self._selector.selectedData() x_axis = self.__x_axis y_axis = self.__y_axis z_axis = self.__z_axis offset = [] scale = [] for axis in [x_axis, y_axis, z_axis]: if axis is None: calibration = NoCalibration() elif len(axis) == 2: calibration = LinearCalibration(y_intercept=axis[0], slope=axis[1]) else: calibration = ArrayCalibration(axis) if not calibration.is_affine(): _logger.warning("Axis has not linear values, ignored") offset.append(0.) scale.append(1.) else: offset.append(calibration(0)) scale.append(calibration.get_slope()) legend = self.__signal_name + "[" for sl in self._selector.selection(): if sl == slice(None): legend += ":, " else: legend += str(sl) + ", " legend = legend[:-2] + "]" self._legend.setText("Displayed data: " + legend) self._view.setData(data, copy=False) self._view.setScale(*scale) self._view.setTranslation(*offset) self._view.setAxesLabels(self.__x_axis_name, self.__y_axis_name, self.__z_axis_name) def clear(self): old = self._selector.blockSignals(True) self._selector.clear() self._selector.blockSignals(old) self._view.setData(None)
class ArrayStackPlot(qt.QWidget): """ Widget for plotting a n-D array (n >= 3) as a stack of images. Three axis arrays can be provided to calibrate the axes. The signal array can have an arbitrary number of dimensions, the only limitation being that the last 3 dimensions must have the same length as the axes arrays. Sliders are provided to select indices on the first (n - 3) dimensions of the signal array, and the plot is updated to load the stack corresponding to the selection. """ def __init__(self, parent=None): """ :param parent: Parent QWidget """ super(ArrayStackPlot, self).__init__(parent) self.__signal = None self.__signal_name = None # the Z, Y, X axes apply to the last three dimensions of the signal # (in that order) self.__z_axis = None self.__z_axis_name = None self.__y_axis = None self.__y_axis_name = None self.__x_axis = None self.__x_axis_name = None self._stack_view = StackView(self) self._hline = qt.QFrame(self) self._hline.setFrameStyle(qt.QFrame.HLine) self._hline.setFrameShadow(qt.QFrame.Sunken) self._legend = qt.QLabel(self) self._selector = NumpyAxesSelector(self) self._selector.setNamedAxesSelectorVisibility(False) self.__selector_is_connected = False layout = qt.QVBoxLayout() layout.addWidget(self._stack_view) layout.addWidget(self._hline) layout.addWidget(self._legend) layout.addWidget(self._selector) self.setLayout(layout) def getStackView(self): """Returns the plot used for the display :rtype: StackView """ return self._stack_view def setStackData(self, signal, x_axis=None, y_axis=None, z_axis=None, signal_name=None, xlabel=None, ylabel=None, zlabel=None, title=None): """ :param signal: n-D dataset, whose last 3 dimensions are used as the 3D stack values. :param x_axis: 1-D dataset used as the image's x coordinates. If provided, its lengths must be equal to the length of the last dimension of ``signal``. :param y_axis: 1-D dataset used as the image's y. If provided, its lengths must be equal to the length of the 2nd to last dimension of ``signal``. :param z_axis: 1-D dataset used as the image's z. If provided, its lengths must be equal to the length of the 3rd to last dimension of ``signal``. :param signal_name: Label used in the legend :param xlabel: Label for X axis :param ylabel: Label for Y axis :param zlabel: Label for Z axis :param title: Graph title """ if self.__selector_is_connected: self._selector.selectionChanged.disconnect(self._updateStack) self.__selector_is_connected = False self.__signal = signal self.__signal_name = signal_name or "" self.__x_axis = x_axis self.__x_axis_name = xlabel self.__y_axis = y_axis self.__y_axis_name = ylabel self.__z_axis = z_axis self.__z_axis_name = zlabel self._selector.setData(signal) self._selector.setAxisNames(["Y", "X", "Z"]) self._stack_view.setGraphTitle(title or "") # by default, the z axis is the image position (dimension not plotted) self._stack_view.getPlot().getXAxis().setLabel(self.__x_axis_name or "X") self._stack_view.getPlot().getYAxis().setLabel(self.__y_axis_name or "Y") self._updateStack() ndims = len(signal.shape) self._stack_view.setFirstStackDimension(ndims - 3) # the legend label shows the selection slice producing the volume # (only interesting for ndim > 3) if ndims > 3: self._selector.setVisible(True) self._legend.setVisible(True) self._hline.setVisible(True) else: self._selector.setVisible(False) self._legend.setVisible(False) self._hline.setVisible(False) if not self.__selector_is_connected: self._selector.selectionChanged.connect(self._updateStack) self.__selector_is_connected = True @staticmethod def _get_origin_scale(axis): """Assuming axis is a regularly spaced 1D array, return a tuple (origin, scale) where: - origin = axis[0] - scale = (axis[n-1] - axis[0]) / (n -1) :param axis: 1D numpy array :return: Tuple (axis[0], (axis[-1] - axis[0]) / (len(axis) - 1)) """ return axis[0], (axis[-1] - axis[0]) / (len(axis) - 1) def _updateStack(self): """Update displayed stack according to the current axes selector data.""" stk = self._selector.selectedData() x_axis = self.__x_axis y_axis = self.__y_axis z_axis = self.__z_axis calibrations = [] for axis in [z_axis, y_axis, x_axis]: if axis is None: calibrations.append(NoCalibration()) elif len(axis) == 2: calibrations.append( LinearCalibration(y_intercept=axis[0], slope=axis[1])) else: calibrations.append(ArrayCalibration(axis)) legend = self.__signal_name + "[" for sl in self._selector.selection(): if sl == slice(None): legend += ":, " else: legend += str(sl) + ", " legend = legend[:-2] + "]" self._legend.setText("Displayed data: " + legend) self._stack_view.setStack(stk, calibrations=calibrations) self._stack_view.setLabels( labels=[self.__z_axis_name, self.__y_axis_name, self.__x_axis_name]) def clear(self): old = self._selector.blockSignals(True) self._selector.clear() self._selector.blockSignals(old) self._stack_view.clear()
class XpadVisualisation(QWidget): unfoldButtonClicked = pyqtSignal() def __init__(self): super(QWidget, self).__init__() self.layout = QVBoxLayout(self) self.raw_data = None self.flatfield_image = None self.path = None self.diagram_data_array = [] self.angles = [] # Initialize tab screen self.tabs = QTabWidget() self.raw_data_tab = QWidget() # Create an unfolding data tab self.unfolded_data_tab = UnfoldingDataTab(self) self.diagram_tab = QWidget() self.fitting_data_tab = QWidget() # Create raw data display tab self.raw_data_tab.layout = QVBoxLayout(self.raw_data_tab) self.raw_data_viewer = RawDataViewer(self.raw_data_tab) # Create diagram plot data tab self.diagram_tab.layout = QVBoxLayout(self.diagram_tab) self.diagram_data_plot = Plot1D(self.diagram_tab) # Create fitting curve tab self.fitting_data_tab.layout = QVBoxLayout(self.fitting_data_tab) self.fitting_data_selector = NumpyAxesSelector(self.fitting_data_tab) self.fitting_data_plot = Plot1D(self.fitting_data_tab) self.fitting_widget = self.fitting_data_plot.getFitAction() self.fit_action = FitAction(plot=self.fitting_data_plot, parent=self.fitting_data_plot) self.toolbar = QToolBar("New") # Create automatic fitting tab self.automatic_fit_tab = FittingDataTab(self) self.unfolded_data_tab.viewer.get_unfold_with_flatfield_action( ).unfoldWithFlatfieldClicked.connect(self.get_calibration) self.unfolded_data_tab.viewer.get_unfold_action( ).unfoldClicked.connect(self.get_calibration) self.unfolded_data_tab.unfoldingFinished.connect( self.create_diagram_array) self.init_UI() def init_UI(self): self.tabs.resize(400, 300) # Add tabs self.tabs.addTab(self.raw_data_tab, "Raw data") self.tabs.addTab(self.unfolded_data_tab, "Unfolded data") self.tabs.addTab(self.diagram_tab, "Diffraction diagram") self.tabs.addTab(self.fitting_data_tab, "Fitted data") self.tabs.addTab(self.automatic_fit_tab, "Automatic fit") self.raw_data_tab.layout.addWidget(self.raw_data_viewer) self.diagram_tab.layout.addWidget(self.diagram_data_plot) self.diagram_data_plot.setGraphTitle(f"Diagram diffraction") self.diagram_data_plot.setGraphXLabel("two-theta (°)") self.diagram_data_plot.setGraphYLabel("intensity") self.diagram_data_plot.setYAxisLogarithmic(True) self.fitting_data_selector.setNamedAxesSelectorVisibility(False) self.fitting_data_selector.setVisible(True) self.fitting_data_selector.setAxisNames("12") self.fitting_data_plot.setYAxisLogarithmic(True) self.fitting_data_plot.setGraphXLabel("two-theta (°)") self.fitting_data_plot.setGraphYLabel("intensity") self.fitting_data_plot.getRoiAction().trigger() self.fitting_widget.setXRangeUpdatedOnZoom(False) self.toolbar.addAction(self.fit_action) self.fit_action.setVisible(True) self.fitting_data_plot.addToolBar(self.toolbar) self.fitting_data_tab.layout.addWidget(self.fitting_data_plot) self.fitting_data_tab.layout.addWidget(self.fitting_data_selector) # Add tabs to widget self.layout.addWidget(self.tabs) # self.unfold_timer.timeout.connect(self.unfold_data) self.fitting_data_selector.selectionChanged.connect(self.fitting_curve) self.fitting_data_plot.getCurvesRoiWidget().sigROIWidgetSignal.connect( self.get_roi_list) self.unfolded_data_tab.viewer.scatter_selector.selectionChanged.connect( self.synchronize_visualisation) @pyqtSlot() def on_click(self): print("\n") for currentQTableWidgetItem in self.tableWidget.selectedItems(): print(currentQTableWidgetItem.row(), currentQTableWidgetItem.column(), currentQTableWidgetItem.text()) def set_data(self, path: str) -> None: self.path = path with File(os.path.join(path), mode='r') as h5file: self.raw_data = get_dataset(h5file, DataPath.IMAGE_INTERPRETATION.value)[:] # We put the raw data in the dataviewer self.raw_data_viewer.set_movie(self.raw_data, self.flatfield_image) self.unfolded_data_tab.images = self.raw_data self.unfolded_data_tab.path = self.path # We allocate a number of view in the stack of unfolded data and fitting data self.unfolded_data_tab.viewer.set_stack_slider(self.raw_data.shape[0]) self.fitting_data_selector.setData( numpy.zeros((self.raw_data.shape[0], 1, 1))) def set_calibration(self, calibration): # Check if there is a empty list of coordinate in the direct beam calibration if not [] in [value for value in calibration.values()]: self.unfolded_data_tab.set_calibration(calibration) if self.unfolded_data_tab.images is not None: self.unfolded_data_tab.start_unfolding() else: print("Direct beam not calibrated yet.") def get_calibration(self): self.unfoldButtonClicked.emit() def create_diagram_array(self): self.diagram_data_array = [] numpy.seterr(divide='ignore', invalid='ignore') for image in self.unfolded_data_tab.viewer.get_scatter_items(): self.diagram_data_array.append( extract_diffraction_diagram( image[0], image[1], image[2], 1.0 / self.unfolded_data_tab.geometry["calib"], -100, 100, patch_data_flag=True)) self.plot_diagram() self.automatic_fit_tab.set_data_to_fit(self.diagram_data_array) self.fitting_data_selector.selectionChanged.emit() set_plot_limits(self.diagram_data_plot, self.diagram_data_plot.getActiveCurve()) def plot_diagram(self, images_to_remove=[-1]): self.diagram_data_plot.setGraphTitle( f"Diagram diffraction of {self.path.split('/')[-1]}") for index, curve in enumerate(self.diagram_data_array): if index not in images_to_remove: self.diagram_data_plot.addCurve(curve[0], curve[1], f'Data of image {index}', color="#0000FF", replace=False, symbol='o') def get_flatfield(self, flat_img: numpy.ndarray): self.flatfield_image = flat_img self.raw_data_viewer.get_action_flatfield().set_flatfield( self.flatfield_image) self.unfolded_data_tab.flatfield = flat_img def synchronize_visualisation(self): # When user change the unfolded view, it set the raw image to the same frame self.raw_data_viewer.setFrameNumber( self.unfolded_data_tab.viewer.scatter_selector.selection()[0]) def fitting_curve(self): if len(self.diagram_data_array) > 0: self.clear_plot_fitting_widget() curve = self.diagram_data_array[ self.fitting_data_selector.selection()[0]] self.fitting_data_plot.addCurve(curve[0], curve[1], symbol='o') set_plot_limits(self.fitting_data_plot, curve) def get_roi_list(self, events: dict): self.rois_list = list(events["roilist"]) def clear_plot_fitting_widget(self): self.fitting_data_plot.clear() self.fitting_data_plot.clearMarkers()
class FittingDataTab(QWidget): def __init__(self, parent, data_to_fit=None): super().__init__(parent) self._data_to_fit = data_to_fit self._fitted_data = [] self.layout = QVBoxLayout(self) self.automatic_plot = Plot1D(self) self.fitting_data_selector = NumpyAxesSelector(self) self.fit = FitManager() self.fit.addtheory("pearson7", function=pearson7bg, parameters=[ 'backgr', 'slopeLin', 'amplitude', 'center', 'fwhm', 'exposant' ], estimate=estimate_pearson7) """ self.fitting_widget = self.fitting_data_plot.getFitAction() self.fit_action = FitAction(plot=self.fitting_data_plot, parent=self.fitting_data_plot) self.toolbar = QToolBar("New") """ self.init_ui() def init_ui(self): self.setLayout(self.layout) self.layout.addWidget(self.automatic_plot) self.layout.addWidget(self.fitting_data_selector) self.fitting_data_selector.setNamedAxesSelectorVisibility(False) self.fitting_data_selector.setVisible(True) self.fitting_data_selector.setAxisNames("12") # self.fitting_data_selector.selectionChanged.connect(self.automatic_plot_fit) def set_data_to_fit(self, data_to_fit): self._data_to_fit = data_to_fit self.fitting_data_selector.setData( numpy.zeros((len(data_to_fit), 1, 1))) self.start_automatic_fit() def automatic_fit(self): if self._data_to_fit is not None: prominence = max( self._data_to_fit[0][1][~numpy.isnan(self._data_to_fit[0][1])]) print(prominence) peaks, _ = find_peaks(self._data_to_fit[0][1], prominence=prominence / 3.0) plt.plot(peaks, self._data_to_fit[0][1][peaks], "xr") plt.plot(self._data_to_fit[0][1]) plt.legend(['Test detection with prominence']) plt.show() def plot_fit(self): if len(self._fitted_data) > 0 and len(self._data_to_fit) > 0: self.automatic_plot.addCurve( self._data_to_fit[self.fitting_data_selector.selection()[0]] [0], self._data_to_fit[self.fitting_data_selector.selection() [0]][1], 'Data to fit') self.automatic_plot.addCurve( self._fitted_data[self.fitting_data_selector.selection()[0]] [0], self._fitted_data[self.fitting_data_selector.selection() [0]][1], 'Fitted data') def start_automatic_fit(self): self.fit.settheory("pearson7") print("Start fitting...") for data in self._data_to_fit: # copy the data arrays, with all the values even the nan ones to keep the size # the copy is used to not erase data on arrays ploted in the last panel x = data[0].copy() y = data[1].copy() # get the max of the y array without any nan value (it would be the maximum) maximum = max(y[~numpy.isnan(y)]) # current maximum / peak we are looking for (for the first iteration it will be the max) current_maximum = max(y[~numpy.isnan(y)]) try: cpt_peak = 0 print("Searching peak and fitting it...") # plot the original curve, were we are going to plot each fitted peak. self.automatic_plot.addCurve(x, y, "Data to fit") print("Max : ", maximum, " Current max : ", current_maximum) # this threshold means that we only want peaks that are at least at 1/4 distance in y axis of the max. while current_maximum > (maximum / 4.0): peak = numpy.where(y == current_maximum)[0][0] left = peak - 35 if peak - 35 > 0 else 0 right = peak + 35 if peak + 35 < len(x) else len(x) - 1 x_peak = x[left:right] y_peak = y[left:right] # set the data to fit, i.e only the peak without all the curve self.fit.setdata(x=x_peak, y=y_peak) # use the estimate function we made to make a first guess of the parameters of the function that will fit our peak. self.fit.estimate() # fit the function. self.fit.runfit() # draw the resulted function of the fit. self.automatic_plot.addCurve( x_peak, pearson7bg( x_peak, *(param['fitresult'] for param in self.fit.fit_results)), f"Peak number {cpt_peak}") self._fitted_data.append( self.automatic_plot.getActiveCurve()) backgr = self.fit.fit_results[0]['fitresult'] fwhm = self.fit.fit_results[4]['fitresult'] # erase the peak to make it easier to found other peaks. y[left:right] = statistics.mean(y[~numpy.isnan(y)]) current_maximum = max(y[~numpy.isnan(y)]) cpt_peak += 1 print("new current_max : ", current_maximum) except (numpy.linalg.LinAlgError, TypeError): print( "Singular matrix error: fit is impossible with the given parameters" )
class UnfoldedDataViewer(QWidget): def __init__(self, parent): super().__init__(parent=parent) self.scatter_view = ScatterView(self) colormap = Colormap('viridis', normalization='log') self.scatter_view.setGraphTitle("Stack of unfolded data") self.scatter_view.setColormap(colormap) self.plot = self.scatter_view.getPlotWidget() self.plot.setGraphXLabel("two-theta (°)") self.plot.setGraphYLabel("psi (°)") self.plot.setKeepDataAspectRatio(False) self.plot.setYAxisInverted(True) self.scatter_selector = NumpyAxesSelector(self) # Prevent user from changing dimensions for the plot self.scatter_selector.setNamedAxesSelectorVisibility(False) self.scatter_selector.setVisible(True) self.scatter_selector.setAxisNames("12") self.layout = QVBoxLayout(self) self.layout.addWidget(self.scatter_view) self.layout.addWidget(self.scatter_selector) self.stack = [] self.initial_data_flag = True self.toolbar = QToolBar("Custom toolbar 1") self.scatter_view.addToolBar(self.toolbar) self.action_unfold = Unfold(self.plot, parent=self) self.action_unfold_with_flatfield = UnfoldWithFlatfield(self.plot, parent=self) self.action_save = SaveAction(self.plot, parent=self) self.toolbar.addAction(self.action_unfold) self.toolbar.addAction(self.action_unfold_with_flatfield) self.toolbar.addAction(self.action_save) self.scatter_selector.selectionChanged.connect( self.change_displayed_data) def add_scatter(self, scatter_image: tuple, scatter_factor: int): # Add an image to the stack. If it is the first, emit the selectionChanged signal to plot the first image self.stack.append((scatter_image[0][::scatter_factor], scatter_image[1][::scatter_factor], scatter_image[2][::scatter_factor])) if self.initial_data_flag: self.scatter_selector.selectionChanged.emit() self.initial_data_flag = False def set_stack_slider(self, nb_images: int): # Set the size of the sliderbar that will let the user navigate the images self.clear_scatter_view() self.scatter_selector.setData(numpy.zeros((nb_images, 1, 1))) def change_displayed_data(self): # If there is at least one unfolded image, clear the view, unpack the data and plot a scatter view of the image if len(self.stack) > 0: self.clear_scatter_view() tth_array, psi_array, intensity = self.stack[ self.scatter_selector.selection()[0]] self.plot.setGraphXLimits( min(tth_array) - 0.0, max(tth_array) + 0.0) self.plot.setGraphYLimits( min(psi_array) - 5.0, max(psi_array) + 5.0) start = time.time() self.scatter_view.setData(tth_array, psi_array, intensity, copy=False) end = time.time() print("Setting the data took :", (end - start) * 1000.0, " ms") def clear_scatter_view(self): self.scatter_view.setData(None, None, None) def reset_scatter_view(self): self.clear_scatter_view() self.stack = [] self.initial_data_flag = True def get_scatter_item(self, index: int) -> tuple: return self.stack[index] def get_scatter_items(self) -> list: return self.stack def get_unfold_action(self): return self.action_unfold def get_unfold_with_flatfield_action(self): return self.action_unfold_with_flatfield
def test_creation(self): data = numpy.arange(3 * 3 * 3) data.shape = 3, 3, 3 widget = NumpyAxesSelector() widget.setVisible(True)