Example #1
0
    def __init__(self, parent=None):
        """Constructor

        :param QWidget parent: The parent of the widget
        """
        super(DataViewer, self).__init__(parent)

        self.__stack = qt.QStackedWidget(self)
        self.__numpySelection = NumpyAxesSelector(self)
        self.__numpySelection.selectedAxisChanged.connect(self.__numpyAxisChanged)
        self.__numpySelection.selectionChanged.connect(self.__numpySelectionChanged)
        self.__numpySelection.customAxisChanged.connect(self.__numpyCustomAxisChanged)

        self.setLayout(qt.QVBoxLayout(self))
        self.layout().addWidget(self.__stack, 1)

        group = qt.QGroupBox(self)
        group.setLayout(qt.QVBoxLayout())
        group.layout().addWidget(self.__numpySelection)
        group.setTitle("Axis selection")
        self.__axisSelection = group

        self.layout().addWidget(self.__axisSelection)

        self.__currentAvailableViews = []
        self.__currentView = None
        self.__data = None
        self.__useAxisSelection = False
        self.__userSelectedView = None

        self.__views = []
        self.__index = {}
        """store stack index for each views"""

        self._initializeViews()
Example #2
0
 def test_none(self):
     data = numpy.arange(3 * 3 * 3)
     widget = NumpyAxesSelector()
     widget.setData(data)
     widget.setData(None)
     result = widget.selectedData()
     self.assertIsNone(result)
Example #3
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__x_axis_errors = None
        self.__values = None

        self._plot = Plot1D(self)

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        self._plot.sigActiveCurveChanged.connect(
            self._setYLabelFromActiveLegend)

        layout = qt.QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot, 0, 0)

        self.setLayout(layout)
Example #4
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__x_axis_errors = None
        self.__values = None

        self._plot = Plot1D(self)

        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        self._plot.sigActiveCurveChanged.connect(
            self._setYLabelFromActiveLegend)

        layout = qt.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)

        self.setLayout(layout)
Example #5
0
    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)
Example #6
0
 def test_none(self):
     data = numpy.arange(3 * 3 * 3)
     widget = NumpyAxesSelector()
     widget.setData(data)
     widget.setData(None)
     result = widget.selectedData()
     self.assertIsNone(result)
Example #7
0
 def test_data_event(self):
     data = numpy.arange(3 * 3 * 3)
     widget = NumpyAxesSelector()
     listener = SignalListener()
     widget.dataChanged.connect(listener)
     widget.setData(data)
     widget.setData(None)
     self.assertEqual(listener.callCount(), 2)
Example #8
0
 def test_data_event(self):
     data = numpy.arange(3 * 3 * 3)
     widget = NumpyAxesSelector()
     listener = SignalListener()
     widget.dataChanged.connect(listener)
     widget.setData(data)
     widget.setData(None)
     self.assertEqual(listener.callCount(), 2)
Example #9
0
    def test_output_1dim(self):
        data = numpy.arange(3 * 3 * 3)
        data.shape = 3, 3, 3
        expectedResult = data[0, 0, 0]

        widget = NumpyAxesSelector()
        widget.setData(data)
        result = widget.selectedData()
        self.assertTrue(numpy.array_equal(result, expectedResult))
Example #10
0
    def test_output_1dim(self):
        data = numpy.arange(3 * 3 * 3)
        data.shape = 3, 3, 3
        expectedResult = data[0, 0, 0]

        widget = NumpyAxesSelector()
        widget.setData(data)
        result = widget.selectedData()
        self.assertTrue(numpy.array_equal(result, expectedResult))
    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)
Example #12
0
    def test_output_moredim(self):
        data = numpy.arange(3 * 3 * 3 * 3)
        data.shape = 3, 3, 3, 3
        expectedResult = data

        widget = NumpyAxesSelector()
        widget.setAxisNames(["x", "y", "z", "boum"])
        widget.setData(data[0])
        result = widget.selectedData()
        self.assertEqual(result, None)
        widget.setData(data)
        result = widget.selectedData()
        self.assertTrue(numpy.array_equal(result, expectedResult))
Example #13
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__x_axis_errors = None
        self.__values = None

        self._plot = Plot1D(self)

        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        self._plot.sigActiveCurveChanged.connect(self._setYLabelFromActiveLegend)

        layout = qt.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)

        self.setLayout(layout)
Example #14
0
    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)
Example #15
0
    def test_output_moredim(self):
        data = numpy.arange(3 * 3 * 3 * 3)
        data.shape = 3, 3, 3, 3
        expectedResult = data

        widget = NumpyAxesSelector()
        widget.setAxisNames(["x", "y", "z", "boum"])
        widget.setData(data[0])
        result = widget.selectedData()
        self.assertEqual(result, None)
        widget.setData(data)
        result = widget.selectedData()
        self.assertTrue(numpy.array_equal(result, expectedResult))
Example #16
0
    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()
Example #17
0
    def __init__(self, parent=None, colormap=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayComplexImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = ComplexImageView(self)
        if colormap is not None:
            for mode in (ComplexImageView.ComplexMode.ABSOLUTE,
                         ComplexImageView.ComplexMode.SQUARE_AMPLITUDE,
                         ComplexImageView.ComplexMode.REAL,
                         ComplexImageView.ComplexMode.IMAGINARY):
                self._plot.setColormap(colormap, mode)

        self._plot.getPlot().getIntensityHistogramAction().setVisible(True)
        self._plot.setKeepDataAspectRatio(True)
        maskToolWidget = self._plot.getPlot().getMaskToolsDockWidget().widget()
        maskToolWidget.setItemMaskUpdated(True)

        # not closable
        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)
        layout.addWidget(self._auxSigSlider)

        self.setLayout(layout)
Example #18
0
 def test_selected_data_event(self):
     data = numpy.arange(3 * 3 * 3)
     data.shape = 3, 3, 3
     widget = NumpyAxesSelector()
     listener = SignalListener()
     widget.selectionChanged.connect(listener)
     widget.setData(data)
     widget.setAxisNames(["x"])
     widget.setData(None)
     self.assertEqual(listener.callCount(), 3)
     listener.clear()
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap(
            Colormap(name="viridis",
                     vmin=None,
                     vmax=None,
                     normalization=Colormap.LINEAR))
        self._plot.getIntensityHistogramAction().setVisible(True)

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._auxSigSlider)
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        self.setLayout(layout)
Example #20
0
    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)
Example #21
0
    def test_h5py_dataset(self):
        with self.h5_temporary_file() as h5file:
            dataset = h5file["data"]
            expectedResult = dataset[0]

            widget = NumpyAxesSelector()
            widget.setData(dataset)
            widget.setAxisNames(["y", "x"])
            result = widget.selectedData()
            self.assertTrue(numpy.array_equal(result, expectedResult))
Example #22
0
 def test_selected_data_event(self):
     data = numpy.arange(3 * 3 * 3)
     data.shape = 3, 3, 3
     widget = NumpyAxesSelector()
     listener = SignalListener()
     widget.selectionChanged.connect(listener)
     widget.setData(data)
     widget.setAxisNames(["x"])
     widget.setData(None)
     self.assertEqual(listener.callCount(), 3)
     listener.clear()
Example #23
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signal = None
        self.__signal_name = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap({
            "name": "viridis",
            "vmin": 0.,
            "vmax": 1.,  # ignored (autoscale) but mandatory
            "normalization": "linear",
            "autoscale": True
        })

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._legend = qt.QLabel(self)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._legend)
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        self.setLayout(layout)
Example #24
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signal = None
        self.__signal_name = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__axis_errors = None
        self.__values = None

        self.__first_curve_added = False

        self._plot = Plot1D(self)
        self._plot.setDefaultColormap(   # for scatters
                {"name": "viridis",
                 "vmin": 0., "vmax": 1.,   # ignored (autoscale) but mandatory
                 "normalization": "linear",
                 "autoscale": True})

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        layout = qt.QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot, 0, 0)

        self.setLayout(layout)
Example #25
0
    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()
Example #26
0
    def test_h5py_dataset(self):
        with self.h5_temporary_file() as h5file:
            dataset = h5file["data"]
            expectedResult = dataset[0]

            widget = NumpyAxesSelector()
            widget.setData(dataset)
            widget.setAxisNames(["y", "x"])
            result = widget.selectedData()
            self.assertTrue(numpy.array_equal(result, expectedResult))
Example #27
0
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap(Colormap(name="viridis",
                                               vmin=None, vmax=None,
                                               normalization=Colormap.LINEAR))
        self._plot.getIntensityHistogramAction().setVisible(True)

        # not closable
        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)
        layout.addWidget(self._auxSigSlider)

        self.setLayout(layout)
Example #28
0
    def __init__(self, parent=None):
        """Constructor

        :param QWidget parent: The parent of the widget
        """
        super(DataViewer, self).__init__(parent)

        self.__stack = qt.QStackedWidget(self)
        self.__numpySelection = NumpyAxesSelector(self)
        self.__numpySelection.selectedAxisChanged.connect(self.__numpyAxisChanged)
        self.__numpySelection.selectionChanged.connect(self.__numpySelectionChanged)
        self.__numpySelection.customAxisChanged.connect(self.__numpyCustomAxisChanged)

        self.setLayout(qt.QVBoxLayout(self))
        self.layout().addWidget(self.__stack, 1)

        group = qt.QGroupBox(self)
        group.setLayout(qt.QVBoxLayout())
        group.layout().addWidget(self.__numpySelection)
        group.setTitle("Axis selection")
        self.__axisSelection = group

        self.layout().addWidget(self.__axisSelection)

        self.__currentAvailableViews = []
        self.__currentView = None
        self.__data = None
        self.__info = None
        self.__useAxisSelection = False
        self.__userSelectedView = None
        self.__hooks = None

        self.__views = []
        self.__index = {}
        """store stack index for each views"""

        self._initializeViews()
Example #29
0
class ArrayComplexImagePlot(qt.QWidget):
    """
    Widget for plotting an image of complex from a multi-dimensional signal array
    and two 1D axes array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last two dimensions must have the same length as
    the axes arrays.

    Sliders are provided to select indices on the first (n - 2) dimensions of
    the signal array, and the plot is updated to show the image corresponding
    to the selection.

    If one or both of the axes does not have regularly spaced values, the
    the image is plotted as a coloured scatter plot.
    """
    def __init__(self, parent=None, colormap=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayComplexImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = ComplexImageView(self)
        if colormap is not None:
            for mode in (ComplexImageView.ComplexMode.ABSOLUTE,
                         ComplexImageView.ComplexMode.SQUARE_AMPLITUDE,
                         ComplexImageView.ComplexMode.REAL,
                         ComplexImageView.ComplexMode.IMAGINARY):
                self._plot.setColormap(colormap, mode)

        self._plot.getPlot().getIntensityHistogramAction().setVisible(True)
        self._plot.setKeepDataAspectRatio(True)

        # not closable
        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)
        layout.addWidget(self._auxSigSlider)

        self.setLayout(layout)

    def _sliderIdxChanged(self, value):
        self._updateImage()

    def getPlot(self):
        """Returns the plot used for the display

        :rtype: PlotWidget
        """
        return self._plot.getPlot()

    def setImageData(self,
                     signals,
                     x_axis=None,
                     y_axis=None,
                     signals_names=None,
                     xlabel=None,
                     ylabel=None,
                     title=None):
        """

        :param signals: list of n-D datasets, whose last 2 dimensions are used as the
            image's values, or list of 3D datasets interpreted as RGBA image.
        :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 signals_names: Names for each image, used as subtitle and legend.
        :param xlabel: Label for X axis
        :param ylabel: Label for Y axis
        :param title: Graph title
        """
        self._selector.selectionChanged.disconnect(self._updateImage)
        self._auxSigSlider.valueChanged.disconnect(self._sliderIdxChanged)

        self.__signals = signals
        self.__signals_names = signals_names
        self.__x_axis = x_axis
        self.__x_axis_name = xlabel
        self.__y_axis = y_axis
        self.__y_axis_name = ylabel
        self.__title = title

        self._selector.clear()
        self._selector.setAxisNames(["Y", "X"])
        self._selector.setData(signals[0])

        if len(signals[0].shape) <= 2:
            self._selector.hide()
        else:
            self._selector.show()

        self._auxSigSlider.setMaximum(len(signals) - 1)
        if len(signals) > 1:
            self._auxSigSlider.show()
        else:
            self._auxSigSlider.hide()
        self._auxSigSlider.setValue(0)

        self._updateImage()
        self._plot.getPlot().resetZoom()

        self._selector.selectionChanged.connect(self._updateImage)
        self._auxSigSlider.valueChanged.connect(self._sliderIdxChanged)

    def _updateImage(self):
        selection = self._selector.selection()
        auxSigIdx = self._auxSigSlider.value()

        images = [img[selection] for img in self.__signals]
        image = images[auxSigIdx]

        x_axis = self.__x_axis
        y_axis = self.__y_axis

        if x_axis is None and y_axis is None:
            xcalib = NoCalibration()
            ycalib = NoCalibration()
        else:
            if x_axis is None:
                # no calibration
                x_axis = numpy.arange(image.shape[1])
            elif numpy.isscalar(x_axis) or len(x_axis) == 1:
                # constant axis
                x_axis = x_axis * numpy.ones((image.shape[1], ))
            elif len(x_axis) == 2:
                # linear calibration
                x_axis = x_axis[0] * numpy.arange(image.shape[1]) + x_axis[1]

            if y_axis is None:
                y_axis = numpy.arange(image.shape[0])
            elif numpy.isscalar(y_axis) or len(y_axis) == 1:
                y_axis = y_axis * numpy.ones((image.shape[0], ))
            elif len(y_axis) == 2:
                y_axis = y_axis[0] * numpy.arange(image.shape[0]) + y_axis[1]

            xcalib = ArrayCalibration(x_axis)
            ycalib = ArrayCalibration(y_axis)

        self._plot.setData(image)
        if xcalib.is_affine():
            xorigin, xscale = xcalib(0), xcalib.get_slope()
        else:
            _logger.warning("Unsupported complex image X axis calibration")
            xorigin, xscale = 0., 1.

        if ycalib.is_affine():
            yorigin, yscale = ycalib(0), ycalib.get_slope()
        else:
            _logger.warning("Unsupported complex image Y axis calibration")
            yorigin, yscale = 0., 1.

        self._plot.setOrigin((xorigin, yorigin))
        self._plot.setScale((xscale, yscale))

        if self.__title:
            title = self.__title
            if len(self.__signals_names) > 1:
                # Append dataset name only when there is many datasets
                title += '\n' + self.__signals_names[auxSigIdx]
        else:
            title = self.__signals_names[auxSigIdx]
        self._plot.setGraphTitle(title)
        self._plot.getXAxis().setLabel(self.__x_axis_name)
        self._plot.getYAxis().setLabel(self.__y_axis_name)

    def clear(self):
        old = self._selector.blockSignals(True)
        self._selector.clear()
        self._selector.blockSignals(old)
        self._plot.setData(None)
Example #30
0
class DataViewer(qt.QFrame):
    """Widget to display any kind of data

    .. image:: img/DataViewer.png

    The method :meth:`setData` allows to set any data to the widget. Mostly
    `numpy.array` and `h5py.Dataset` are supported with adapted views. Other
    data types are displayed using a text viewer.

    A default view is automatically selected when a data is set. The method
    :meth:`setDisplayMode` allows to change the view. To have a graphical tool
    to select the view, prefer using the widget :class:`DataViewerFrame`.

    The dimension of the input data and the expected dimension of the selected
    view can differ. For example you can display an image (2D) from 4D
    data. In this case a :class:`NumpyAxesSelector` is displayed to allow the
    user to select the axis mapping and the slicing of other axes.

    .. code-block:: python

        import numpy
        data = numpy.random.rand(500,500)
        viewer = DataViewer()
        viewer.setData(data)
        viewer.setVisible(True)
    """

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.EMPTY_MODE", since_version="0.7", skip_backtrace_count=2)
    def EMPTY_MODE(self):
        return DataViews.EMPTY_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.PLOT1D_MODE", since_version="0.7", skip_backtrace_count=2)
    def PLOT1D_MODE(self):
        return DataViews.PLOT1D_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.PLOT2D_MODE", since_version="0.7", skip_backtrace_count=2)
    def PLOT2D_MODE(self):
        return DataViews.PLOT2D_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.PLOT3D_MODE", since_version="0.7", skip_backtrace_count=2)
    def PLOT3D_MODE(self):
        return DataViews.PLOT3D_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.RAW_MODE", since_version="0.7", skip_backtrace_count=2)
    def RAW_MODE(self):
        return DataViews.RAW_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.RAW_ARRAY_MODE", since_version="0.7", skip_backtrace_count=2)
    def RAW_ARRAY_MODE(self):
        return DataViews.RAW_ARRAY_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.RAW_RECORD_MODE", since_version="0.7", skip_backtrace_count=2)
    def RAW_RECORD_MODE(self):
        return DataViews.RAW_RECORD_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.RAW_SCALAR_MODE", since_version="0.7", skip_backtrace_count=2)
    def RAW_SCALAR_MODE(self):
        return DataViews.RAW_SCALAR_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.STACK_MODE", since_version="0.7", skip_backtrace_count=2)
    def STACK_MODE(self):
        return DataViews.STACK_MODE

    # TODO: Can be removed for silx 0.8
    @classproperty
    @deprecation.deprecated(replacement="DataViews.HDF5_MODE", since_version="0.7", skip_backtrace_count=2)
    def HDF5_MODE(self):
        return DataViews.HDF5_MODE

    displayedViewChanged = qt.Signal(object)
    """Emitted when the displayed view changes"""

    dataChanged = qt.Signal()
    """Emitted when the data changes"""

    currentAvailableViewsChanged = qt.Signal()
    """Emitted when the current available views (which support the current
    data) change"""

    def __init__(self, parent=None):
        """Constructor

        :param QWidget parent: The parent of the widget
        """
        super(DataViewer, self).__init__(parent)

        self.__stack = qt.QStackedWidget(self)
        self.__numpySelection = NumpyAxesSelector(self)
        self.__numpySelection.selectedAxisChanged.connect(self.__numpyAxisChanged)
        self.__numpySelection.selectionChanged.connect(self.__numpySelectionChanged)
        self.__numpySelection.customAxisChanged.connect(self.__numpyCustomAxisChanged)

        self.setLayout(qt.QVBoxLayout(self))
        self.layout().addWidget(self.__stack, 1)

        group = qt.QGroupBox(self)
        group.setLayout(qt.QVBoxLayout())
        group.layout().addWidget(self.__numpySelection)
        group.setTitle("Axis selection")
        self.__axisSelection = group

        self.layout().addWidget(self.__axisSelection)

        self.__currentAvailableViews = []
        self.__currentView = None
        self.__data = None
        self.__info = None
        self.__useAxisSelection = False
        self.__userSelectedView = None
        self.__hooks = None

        self.__views = []
        self.__index = {}
        """store stack index for each views"""

        self._initializeViews()

    def _initializeViews(self):
        """Inisialize the available views"""
        views = self.createDefaultViews(self.__stack)
        self.__views = list(views)
        self.setDisplayMode(DataViews.EMPTY_MODE)

    def setGlobalHooks(self, hooks):
        """Set a data view hooks for all the views

        :param DataViewHooks context: The hooks to use
        """
        self.__hooks = hooks
        for v in self.__views:
            v.setHooks(hooks)

    def createDefaultViews(self, parent=None):
        """Create and returns available views which can be displayed by default
        by the data viewer. It is called internally by the widget. It can be
        overwriten to provide a different set of viewers.

        :param QWidget parent: QWidget parent of the views
        :rtype: List[silx.gui.data.DataViews.DataView]
        """
        viewClasses = [
            DataViews._EmptyView,
            DataViews._Hdf5View,
            DataViews._NXdataView,
            DataViews._Plot1dView,
            DataViews._ImageView,
            DataViews._Plot3dView,
            DataViews._RawView,
            DataViews._StackView,
        ]
        views = []
        for viewClass in viewClasses:
            try:
                view = viewClass(parent)
                views.append(view)
            except Exception:
                _logger.warning("%s instantiation failed. View is ignored" % viewClass.__name__)
                _logger.debug("Backtrace", exc_info=True)

        return views

    def clear(self):
        """Clear the widget"""
        self.setData(None)

    def normalizeData(self, data):
        """Returns a normalized data if the embed a numpy or a dataset.
        Else returns the data."""
        return _normalizeData(data)

    def __getStackIndex(self, view):
        """Get the stack index containing the view.

        :param silx.gui.data.DataViews.DataView view: The view
        """
        if view not in self.__index:
            widget = view.getWidget()
            index = self.__stack.addWidget(widget)
            self.__index[view] = index
        else:
            index = self.__index[view]
        return index

    def __clearCurrentView(self):
        """Clear the current selected view"""
        view = self.__currentView
        if view is not None:
            view.clear()

    def __numpyCustomAxisChanged(self, name, value):
        view = self.__currentView
        if view is not None:
            view.setCustomAxisValue(name, value)

    def __updateNumpySelectionAxis(self):
        """
        Update the numpy-selector according to the needed axis names
        """
        previous = self.__numpySelection.blockSignals(True)
        self.__numpySelection.clear()
        info = self._getInfo()
        axisNames = self.__currentView.axesNames(self.__data, info)
        if info.isArray and info.size != 0 and self.__data is not None and axisNames is not None:
            self.__useAxisSelection = True
            self.__numpySelection.setAxisNames(axisNames)
            self.__numpySelection.setCustomAxis(self.__currentView.customAxisNames())
            data = self.normalizeData(self.__data)
            self.__numpySelection.setData(data)
            if hasattr(data, "shape"):
                isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
            else:
                isVisible = True
            self.__axisSelection.setVisible(isVisible)
        else:
            self.__useAxisSelection = False
            self.__axisSelection.setVisible(False)
        self.__numpySelection.blockSignals(previous)

    def __updateDataInView(self):
        """
        Update the views using the current data
        """
        if self.__useAxisSelection:
            self.__displayedData = self.__numpySelection.selectedData()
        else:
            self.__displayedData = self.__data

        # TODO: would be good to avoid that, it should be synchonous
        qt.QTimer.singleShot(10, self.__setDataInView)

    def __setDataInView(self):
        self.__currentView.setData(self.__displayedData)

    def setDisplayedView(self, view):
        """Set the displayed view.

        Change the displayed view according to the view itself.

        :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
        """
        self.__userSelectedView = view
        self._setDisplayedView(view)

    def _setDisplayedView(self, view):
        """Internal set of the displayed view.

        Change the displayed view according to the view itself.

        :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
        """
        if self.__currentView is view:
            return
        self.__clearCurrentView()
        self.__currentView = view
        self.__updateNumpySelectionAxis()
        self.__updateDataInView()
        stackIndex = self.__getStackIndex(self.__currentView)
        if self.__currentView is not None:
            self.__currentView.select()
        self.__stack.setCurrentIndex(stackIndex)
        self.displayedViewChanged.emit(view)

    def getViewFromModeId(self, modeId):
        """Returns the first available view which have the requested modeId.
        Return None if modeId does not correspond to an existing view.

        :param int modeId: Requested mode id
        :rtype: silx.gui.data.DataViews.DataView
        """
        for view in self.__views:
            if view.modeId() == modeId:
                return view
        return None

    def setDisplayMode(self, modeId):
        """Set the displayed view using display mode.

        Change the displayed view according to the requested mode.

        :param int modeId: Display mode, one of

            - `DataViews.EMPTY_MODE`: display nothing
            - `DataViews.PLOT1D_MODE`: display the data as a curve
            - `DataViews.IMAGE_MODE`: display the data as an image
            - `DataViews.PLOT3D_MODE`: display the data as an isosurface
            - `DataViews.RAW_MODE`: display the data as a table
            - `DataViews.STACK_MODE`: display the data as a stack of images
            - `DataViews.HDF5_MODE`: display the data as a table of HDF5 info
            - `DataViews.NXDATA_MODE`: display the data as NXdata
        """
        try:
            view = self.getViewFromModeId(modeId)
        except KeyError:
            raise ValueError("Display mode %s is unknown" % modeId)
        self._setDisplayedView(view)

    def displayedView(self):
        """Returns the current displayed view.

        :rtype: silx.gui.data.DataViews.DataView
        """
        return self.__currentView

    def addView(self, view):
        """Allow to add a view to the dataview.

        If the current data support this view, it will be displayed.

        :param DataView view: A dataview
        """
        if self.__hooks is not None:
            view.setHooks(self.__hooks)
        self.__views.append(view)
        # TODO It can be skipped if the view do not support the data
        self.__updateAvailableViews()

    def removeView(self, view):
        """Allow to remove a view which was available from the dataview.

        If the view was displayed, the widget will be updated.

        :param DataView view: A dataview
        """
        self.__views.remove(view)
        self.__stack.removeWidget(view.getWidget())
        # invalidate the full index. It will be updated as expected
        self.__index = {}

        if self.__userSelectedView is view:
            self.__userSelectedView = None

        if view is self.__currentView:
            self.__updateView()
        else:
            # TODO It can be skipped if the view is not part of the
            # available views
            self.__updateAvailableViews()

    def __updateAvailableViews(self):
        """
        Update available views from the current data.
        """
        data = self.__data
        info = self._getInfo()
        # sort available views according to priority
        priorities = [v.getDataPriority(data, info) for v in self.__views]
        views = zip(priorities, self.__views)
        views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views)
        views = sorted(views, reverse=True)

        # store available views
        if len(views) == 0:
            self.__setCurrentAvailableViews([])
            available = []
        else:
            available = [v[1] for v in views]
            self.__setCurrentAvailableViews(available)

    def __updateView(self):
        """Display the data using the widget which fit the best"""
        data = self.__data

        # update available views for this data
        self.__updateAvailableViews()
        available = self.__currentAvailableViews

        # display the view with the most priority (the default view)
        view = self.getDefaultViewFromAvailableViews(data, available)
        self.__clearCurrentView()
        try:
            self._setDisplayedView(view)
        except Exception as e:
            # in case there is a problem to read the data, try to use a safe
            # view
            view = self.getSafeViewFromAvailableViews(data, available)
            self._setDisplayedView(view)
            raise e

    def getSafeViewFromAvailableViews(self, data, available):
        """Returns a view which is sure to display something without failing
        on rendering.

        :param object data: data which will be displayed
        :param List[view] available: List of available views, from highest
            priority to lowest.
        :rtype: DataView
        """
        hdf5View = self.getViewFromModeId(DataViewer.HDF5_MODE)
        if hdf5View in available:
            return hdf5View
        return self.getViewFromModeId(DataViews.EMPTY_MODE)

    def getDefaultViewFromAvailableViews(self, data, available):
        """Returns the default view which will be used according to available
        views.

        :param object data: data which will be displayed
        :param List[view] available: List of available views, from highest
            priority to lowest.
        :rtype: DataView
        """
        if len(available) > 0:
            # returns the view with the highest priority
            if self.__userSelectedView in available:
                return self.__userSelectedView
            self.__userSelectedView = None
            view = available[0]
        else:
            # else returns the empty view
            view = self.getViewFromModeId(DataViews.EMPTY_MODE)
        return view

    def __setCurrentAvailableViews(self, availableViews):
        """Set the current available viewa

        :param List[DataView] availableViews: Current available viewa
        """
        self.__currentAvailableViews = availableViews
        self.currentAvailableViewsChanged.emit()

    def currentAvailableViews(self):
        """Returns the list of available views for the current data

        :rtype: List[DataView]
        """
        return self.__currentAvailableViews

    def availableViews(self):
        """Returns the list of registered views

        :rtype: List[DataView]
        """
        return self.__views

    def setData(self, data):
        """Set the data to view.

        It mostly can be a h5py.Dataset or a numpy.ndarray. Other kind of
        objects will be displayed as text rendering.

        :param numpy.ndarray data: The data.
        """
        self.__data = data
        self._invalidateInfo()
        self.__displayedData = None
        self.__updateView()
        self.__updateNumpySelectionAxis()
        self.__updateDataInView()
        self.dataChanged.emit()

    def __numpyAxisChanged(self):
        """
        Called when axis selection of the numpy-selector changed
        """
        self.__clearCurrentView()

    def __numpySelectionChanged(self):
        """
        Called when data selection of the numpy-selector changed
        """
        self.__updateDataInView()

    def data(self):
        """Returns the data"""
        return self.__data

    def _invalidateInfo(self):
        """Invalidate DataInfo cache."""
        self.__info = None

    def _getInfo(self):
        """Returns the DataInfo of the current selected data.

        This value is cached.

        :rtype: DataInfo
        """
        if self.__info is None:
            self.__info = DataViews.DataInfo(self.__data)
        return self.__info

    def displayMode(self):
        """Returns the current display mode"""
        return self.__currentView.modeId()

    def replaceView(self, modeId, newView):
        """Replace one of the builtin data views with a custom view.
        Return True in case of success, False in case of failure.

        .. note::

            This method must be called just after instantiation, before
            the viewer is used.

        :param int modeId: Unique mode ID identifying the DataView to
            be replaced. One of:

            - `DataViews.EMPTY_MODE`
            - `DataViews.PLOT1D_MODE`
            - `DataViews.IMAGE_MODE`
            - `DataViews.PLOT2D_MODE`
            - `DataViews.COMPLEX_IMAGE_MODE`
            - `DataViews.PLOT3D_MODE`
            - `DataViews.RAW_MODE`
            - `DataViews.STACK_MODE`
            - `DataViews.HDF5_MODE`
            - `DataViews.NXDATA_MODE`
            - `DataViews.NXDATA_INVALID_MODE`
            - `DataViews.NXDATA_SCALAR_MODE`
            - `DataViews.NXDATA_CURVE_MODE`
            - `DataViews.NXDATA_XYVSCATTER_MODE`
            - `DataViews.NXDATA_IMAGE_MODE`
            - `DataViews.NXDATA_STACK_MODE`

        :param DataViews.DataView newView: New data view
        :return: True if replacement was successful, else False
        """
        assert isinstance(newView, DataViews.DataView)
        isReplaced = False
        for idx, view in enumerate(self.__views):
            if view.modeId() == modeId:
                if self.__hooks is not None:
                    newView.setHooks(self.__hooks)
                self.__views[idx] = newView
                isReplaced = True
                break
            elif isinstance(view, DataViews.CompositeDataView):
                isReplaced = view.replaceView(modeId, newView)
                if isReplaced:
                    break

        if isReplaced:
            self.__updateAvailableViews()
        return isReplaced
Example #31
0
class ArrayImagePlot(qt.QWidget):
    """
    Widget for plotting an image from a multi-dimensional signal array
    and two 1D axes array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last two dimensions must have the same length as
    the axes arrays.

    Sliders are provided to select indices on the first (n - 2) dimensions of
    the signal array, and the plot is updated to show the image corresponding
    to the selection.

    If one or both of the axes does not have regularly spaced values, the
    the image is plotted as a coloured scatter plot.
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap(
            Colormap(name="viridis",
                     vmin=None,
                     vmax=None,
                     normalization=Colormap.LINEAR))
        self._plot.getIntensityHistogramAction().setVisible(True)
        self._plot.setKeepDataAspectRatio(True)

        # not closable
        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)
        layout.addWidget(self._auxSigSlider)

        self.setLayout(layout)

    def _sliderIdxChanged(self, value):
        self._updateImage()

    def getPlot(self):
        """Returns the plot used for the display

        :rtype: Plot2D
        """
        return self._plot

    def setImageData(self,
                     signals,
                     x_axis=None,
                     y_axis=None,
                     signals_names=None,
                     xlabel=None,
                     ylabel=None,
                     title=None,
                     isRgba=False,
                     xscale=None,
                     yscale=None):
        """

        :param signals: list of n-D datasets, whose last 2 dimensions are used as the
            image's values, or list of 3D datasets interpreted as RGBA image.
        :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 signals_names: Names for each image, used as subtitle and legend.
        :param xlabel: Label for X axis
        :param ylabel: Label for Y axis
        :param title: Graph title
        :param isRgba: True if data is a 3D RGBA image
        :param str xscale: Scale of X axis in (None, 'linear', 'log')
        :param str yscale: Scale of Y axis in (None, 'linear', 'log')
        """
        self._selector.selectionChanged.disconnect(self._updateImage)
        self._auxSigSlider.valueChanged.disconnect(self._sliderIdxChanged)

        self.__signals = signals
        self.__signals_names = signals_names
        self.__x_axis = x_axis
        self.__x_axis_name = xlabel
        self.__y_axis = y_axis
        self.__y_axis_name = ylabel
        self.__title = title

        self._selector.clear()
        if not isRgba:
            self._selector.setAxisNames(["Y", "X"])
            img_ndim = 2
        else:
            self._selector.setAxisNames(["Y", "X", "RGB(A) channel"])
            img_ndim = 3
        self._selector.setData(signals[0])

        if len(signals[0].shape) <= img_ndim:
            self._selector.hide()
        else:
            self._selector.show()

        self._auxSigSlider.setMaximum(len(signals) - 1)
        if len(signals) > 1:
            self._auxSigSlider.show()
        else:
            self._auxSigSlider.hide()
        self._auxSigSlider.setValue(0)

        self._axis_scales = xscale, yscale
        self._updateImage()
        self._plot.resetZoom()

        self._selector.selectionChanged.connect(self._updateImage)
        self._auxSigSlider.valueChanged.connect(self._sliderIdxChanged)

    def _updateImage(self):
        selection = self._selector.selection()
        auxSigIdx = self._auxSigSlider.value()

        legend = self.__signals_names[auxSigIdx]

        images = [img[selection] for img in self.__signals]
        image = images[auxSigIdx]

        x_axis = self.__x_axis
        y_axis = self.__y_axis

        if x_axis is None and y_axis is None:
            xcalib = NoCalibration()
            ycalib = NoCalibration()
        else:
            if x_axis is None:
                # no calibration
                x_axis = numpy.arange(image.shape[1])
            elif numpy.isscalar(x_axis) or len(x_axis) == 1:
                # constant axis
                x_axis = x_axis * numpy.ones((image.shape[1], ))
            elif len(x_axis) == 2:
                # linear calibration
                x_axis = x_axis[0] * numpy.arange(image.shape[1]) + x_axis[1]

            if y_axis is None:
                y_axis = numpy.arange(image.shape[0])
            elif numpy.isscalar(y_axis) or len(y_axis) == 1:
                y_axis = y_axis * numpy.ones((image.shape[0], ))
            elif len(y_axis) == 2:
                y_axis = y_axis[0] * numpy.arange(image.shape[0]) + y_axis[1]

            xcalib = ArrayCalibration(x_axis)
            ycalib = ArrayCalibration(y_axis)

        self._plot.remove(kind=(
            "scatter",
            "image",
        ))
        if xcalib.is_affine() and ycalib.is_affine():
            # regular image
            xorigin, xscale = xcalib(0), xcalib.get_slope()
            yorigin, yscale = ycalib(0), ycalib.get_slope()
            origin = (xorigin, yorigin)
            scale = (xscale, yscale)

            self._plot.getXAxis().setScale('linear')
            self._plot.getYAxis().setScale('linear')
            self._plot.addImage(image,
                                legend=legend,
                                origin=origin,
                                scale=scale,
                                replace=True,
                                resetzoom=False)
        else:
            xaxisscale, yaxisscale = self._axis_scales

            if xaxisscale is not None:
                self._plot.getXAxis().setScale('log' if xaxisscale ==
                                               'log' else 'linear')
            if yaxisscale is not None:
                self._plot.getYAxis().setScale('log' if yaxisscale ==
                                               'log' else 'linear')

            scatterx, scattery = numpy.meshgrid(x_axis, y_axis)
            # fixme: i don't think this can handle "irregular" RGBA images
            self._plot.addScatter(numpy.ravel(scatterx),
                                  numpy.ravel(scattery),
                                  numpy.ravel(image),
                                  legend=legend)

        if self.__title:
            title = self.__title
            if len(self.__signals_names) > 1:
                # Append dataset name only when there is many datasets
                title += '\n' + self.__signals_names[auxSigIdx]
        else:
            title = self.__signals_names[auxSigIdx]
        self._plot.setGraphTitle(title)
        self._plot.getXAxis().setLabel(self.__x_axis_name)
        self._plot.getYAxis().setLabel(self.__y_axis_name)

    def clear(self):
        old = self._selector.blockSignals(True)
        self._selector.clear()
        self._selector.blockSignals(old)
        self._plot.clear()
Example #32
0
class ArrayCurvePlot(qt.QWidget):
    """
    Widget for plotting a curve from a multi-dimensional signal array
    and a 1D axis array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last dimension must have the same length as
    the axis array.

    The widget provides sliders to select indices on the first (n - 1)
    dimensions of the signal array, and buttons to add/replace selected
    curves to the plot.

    This widget also handles simple 2D or 3D scatter plots (third dimension
    displayed as colour of points).
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__x_axis_errors = None
        self.__values = None

        self._plot = Plot1D(self)

        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        self._plot.sigActiveCurveChanged.connect(
            self._setYLabelFromActiveLegend)

        layout = qt.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)

        self.setLayout(layout)

    def getPlot(self):
        """Returns the plot used for the display

        :rtype: Plot1D
        """
        return self._plot

    def setCurvesData(self,
                      ys,
                      x=None,
                      yerror=None,
                      xerror=None,
                      ylabels=None,
                      xlabel=None,
                      title=None,
                      xscale=None,
                      yscale=None):
        """

        :param List[ndarray] ys: List of arrays to be represented by the y (vertical) axis.
            It can be multiple n-D array whose last dimension must
            have the same length as x (and values must be None)
        :param ndarray x: 1-D dataset used as the curve's x values. If provided,
            its lengths must be equal to the length of the last dimension of
            ``y`` (and equal to the length of ``value``, for a scatter plot).
        :param ndarray yerror: Single array of errors for y (same shape), or None.
            There can only be one array, and it applies to the first/main y
            (no y errors for auxiliary_signals curves).
        :param ndarray xerror: 1-D dataset of errors for x, or None
        :param str ylabels: Labels for each curve's Y axis
        :param str xlabel: Label for X axis
        :param str title: Graph title
        :param str xscale: Scale of X axis in (None, 'linear', 'log')
        :param str yscale: Scale of Y axis in (None, 'linear', 'log')
        """
        self.__signals = ys
        self.__signals_names = ylabels or (["Y"] * len(ys))
        self.__signal_errors = yerror
        self.__axis = x
        self.__axis_name = xlabel
        self.__x_axis_errors = xerror

        if self.__selector_is_connected:
            self._selector.selectionChanged.disconnect(self._updateCurve)
            self.__selector_is_connected = False
        self._selector.setData(ys[0])
        self._selector.setAxisNames(["Y"])

        if len(ys[0].shape) < 2:
            self._selector.hide()
        else:
            self._selector.show()

        self._plot.setGraphTitle(title or "")
        if xscale is not None:
            self._plot.getXAxis().setScale('log' if xscale ==
                                           'log' else 'linear')
        if yscale is not None:
            self._plot.getYAxis().setScale('log' if yscale ==
                                           'log' else 'linear')
        self._updateCurve()

        if not self.__selector_is_connected:
            self._selector.selectionChanged.connect(self._updateCurve)
            self.__selector_is_connected = True

    def _updateCurve(self):
        selection = self._selector.selection()
        ys = [sig[selection] for sig in self.__signals]
        y0 = ys[0]
        len_y = len(y0)
        x = self.__axis
        if x is None:
            x = numpy.arange(len_y)
        elif numpy.isscalar(x) or len(x) == 1:
            # constant axis
            x = x * numpy.ones_like(y0)
        elif len(x) == 2 and len_y != 2:
            # linear calibration a + b * x
            x = x[0] + x[1] * numpy.arange(len_y)

        self._plot.remove(kind=("curve", ))

        for i in range(len(self.__signals)):
            legend = self.__signals_names[i]

            # errors only supported for primary signal in NXdata
            y_errors = None
            if i == 0 and self.__signal_errors is not None:
                y_errors = self.__signal_errors[self._selector.selection()]
            self._plot.addCurve(x,
                                ys[i],
                                legend=legend,
                                xerror=self.__x_axis_errors,
                                yerror=y_errors)
            if i == 0:
                self._plot.setActiveCurve(legend)

        self._plot.resetZoom()
        self._plot.getXAxis().setLabel(self.__axis_name)
        self._plot.getYAxis().setLabel(self.__signals_names[0])

    def _setYLabelFromActiveLegend(self, previous_legend, new_legend):
        for ylabel in self.__signals_names:
            if new_legend is not None and new_legend == ylabel:
                self._plot.getYAxis().setLabel(ylabel)
                break

    def clear(self):
        old = self._selector.blockSignals(True)
        self._selector.clear()
        self._selector.blockSignals(old)
        self._plot.clear()
Example #33
0
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
Example #34
0
 def test_creation(self):
     data = numpy.arange(3 * 3 * 3)
     data.shape = 3, 3, 3
     widget = NumpyAxesSelector()
     widget.setVisible(True)
Example #35
0
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()
Example #36
0
class ArrayImagePlot(qt.QWidget):
    """
    Widget for plotting an image from a multi-dimensional signal array
    and two 1D axes array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last two dimensions must have the same length as
    the axes arrays.

    Sliders are provided to select indices on the first (n - 2) dimensions of
    the signal array, and the plot is updated to show the image corresponding
    to the selection.

    If one or both of the axes does not have regularly spaced values, the
    the image is plotted as a coloured scatter plot.
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signal = None
        self.__signal_name = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap({
            "name": "viridis",
            "vmin": 0.,
            "vmax": 1.,  # ignored (autoscale) but mandatory
            "normalization": "linear",
            "autoscale": True
        })

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._legend = qt.QLabel(self)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._legend)
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        self.setLayout(layout)

    def setImageData(self,
                     signal,
                     x_axis=None,
                     y_axis=None,
                     signal_name=None,
                     xlabel=None,
                     ylabel=None,
                     title=None):
        """

        :param signal: n-D dataset, whose last 2 dimensions are used as the
            image's 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 signal_name: Label used in the legend
        :param xlabel: Label for X axis
        :param ylabel: Label for Y axis
        :param title: Graph title
        """
        if self.__selector_is_connected:
            self._selector.selectionChanged.disconnect(self._updateImage)
            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._selector.setData(signal)
        self._selector.setAxisNames([ylabel or "Y", xlabel or "X"])

        if len(signal.shape) < 3:
            self.selectorDock.hide()
        else:
            self.selectorDock.show()

        self._plot.setGraphTitle(title or "")
        self._plot.setGraphXLabel(self.__x_axis_name or "X")
        self._plot.setGraphYLabel(self.__y_axis_name or "Y")

        self._updateImage()

        if not self.__selector_is_connected:
            self._selector.selectionChanged.connect(self._updateImage)
            self.__selector_is_connected = True

    def _updateImage(self):
        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)

        img = self._selector.selectedData()
        x_axis = self.__x_axis
        y_axis = self.__y_axis

        if x_axis is None and y_axis is None:
            xcalib = NoCalibration()
            ycalib = NoCalibration()
        else:
            if x_axis is None:
                # no calibration
                x_axis = numpy.arange(img.shape[-1])
            elif numpy.isscalar(x_axis) or len(x_axis) == 1:
                # constant axis
                x_axis = x_axis * numpy.ones((img.shape[-1], ))
            elif len(x_axis) == 2:
                # linear calibration
                x_axis = x_axis[0] * numpy.arange(img.shape[-1]) + x_axis[1]

            if y_axis is None:
                y_axis = numpy.arange(img.shape[-2])
            elif numpy.isscalar(y_axis) or len(y_axis) == 1:
                y_axis = y_axis * numpy.ones((img.shape[-2], ))
            elif len(y_axis) == 2:
                y_axis = y_axis[0] * numpy.arange(img.shape[-2]) + y_axis[1]

            xcalib = ArrayCalibration(x_axis)
            ycalib = ArrayCalibration(y_axis)

        self._plot.remove(kind=("scatter", "image"))
        if xcalib.is_affine() and ycalib.is_affine():
            # regular image
            xorigin, xscale = xcalib(0), xcalib.get_slope()
            yorigin, yscale = ycalib(0), ycalib.get_slope()
            origin = (xorigin, yorigin)
            scale = (xscale, yscale)

            self._plot.addImage(img, legend=legend, origin=origin, scale=scale)
        else:
            scatterx, scattery = numpy.meshgrid(x_axis, y_axis)
            self._plot.addScatter(numpy.ravel(scatterx),
                                  numpy.ravel(scattery),
                                  numpy.ravel(img),
                                  legend=legend)
        self._plot.setGraphXLabel(self.__x_axis_name)
        self._plot.setGraphYLabel(self.__y_axis_name)
        self._plot.resetZoom()

    def clear(self):
        self._plot.clear()
Example #37
0
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()
Example #38
0
class ArrayImagePlot(qt.QWidget):
    """
    Widget for plotting an image from a multi-dimensional signal array
    and two 1D axes array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last two dimensions must have the same length as
    the axes arrays.

    Sliders are provided to select indices on the first (n - 2) dimensions of
    the signal array, and the plot is updated to show the image corresponding
    to the selection.

    If one or both of the axes does not have regularly spaced values, the
    the image is plotted as a coloured scatter plot.
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayImagePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__x_axis = None
        self.__x_axis_name = None
        self.__y_axis = None
        self.__y_axis_name = None

        self._plot = Plot2D(self)
        self._plot.setDefaultColormap(Colormap(name="viridis",
                                               vmin=None, vmax=None,
                                               normalization=Colormap.LINEAR))
        self._plot.getIntensityHistogramAction().setVisible(True)

        # not closable
        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self._selector.selectionChanged.connect(self._updateImage)

        self._auxSigSlider = HorizontalSliderWithBrowser(parent=self)
        self._auxSigSlider.setMinimum(0)
        self._auxSigSlider.setValue(0)
        self._auxSigSlider.valueChanged[int].connect(self._sliderIdxChanged)
        self._auxSigSlider.setToolTip("Select auxiliary signals")

        layout = qt.QVBoxLayout()
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)
        layout.addWidget(self._auxSigSlider)

        self.setLayout(layout)

    def _sliderIdxChanged(self, value):
        self._updateImage()

    def getPlot(self):
        """Returns the plot used for the display

        :rtype: Plot2D
        """
        return self._plot

    def setImageData(self, signals,
                     x_axis=None, y_axis=None,
                     signals_names=None,
                     xlabel=None, ylabel=None,
                     title=None, isRgba=False):
        """

        :param signals: list of n-D datasets, whose last 2 dimensions are used as the
            image's values, or list of 3D datasets interpreted as RGBA image.
        :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 signals_names: Names for each image, used as subtitle and legend.
        :param xlabel: Label for X axis
        :param ylabel: Label for Y axis
        :param title: Graph title
        :param isRgba: True if data is a 3D RGBA image
        """
        self._selector.selectionChanged.disconnect(self._updateImage)
        self._auxSigSlider.valueChanged.disconnect(self._sliderIdxChanged)

        self.__signals = signals
        self.__signals_names = signals_names
        self.__x_axis = x_axis
        self.__x_axis_name = xlabel
        self.__y_axis = y_axis
        self.__y_axis_name = ylabel
        self.__title = title

        self._selector.clear()
        if not isRgba:
            self._selector.setAxisNames(["Y", "X"])
            img_ndim = 2
        else:
            self._selector.setAxisNames(["Y", "X", "RGB(A) channel"])
            img_ndim = 3
        self._selector.setData(signals[0])

        if len(signals[0].shape) <= img_ndim:
            self._selector.hide()
        else:
            self._selector.show()

        self._auxSigSlider.setMaximum(len(signals) - 1)
        if len(signals) > 1:
            self._auxSigSlider.show()
        else:
            self._auxSigSlider.hide()
        self._auxSigSlider.setValue(0)

        self._updateImage()

        self._selector.selectionChanged.connect(self._updateImage)
        self._auxSigSlider.valueChanged.connect(self._sliderIdxChanged)

    def _updateImage(self):
        selection = self._selector.selection()
        auxSigIdx = self._auxSigSlider.value()

        legend = self.__signals_names[auxSigIdx]

        images = [img[selection] for img in self.__signals]
        image = images[auxSigIdx]

        x_axis = self.__x_axis
        y_axis = self.__y_axis

        if x_axis is None and y_axis is None:
            xcalib = NoCalibration()
            ycalib = NoCalibration()
        else:
            if x_axis is None:
                # no calibration
                x_axis = numpy.arange(image.shape[1])
            elif numpy.isscalar(x_axis) or len(x_axis) == 1:
                # constant axis
                x_axis = x_axis * numpy.ones((image.shape[1], ))
            elif len(x_axis) == 2:
                # linear calibration
                x_axis = x_axis[0] * numpy.arange(image.shape[1]) + x_axis[1]

            if y_axis is None:
                y_axis = numpy.arange(image.shape[0])
            elif numpy.isscalar(y_axis) or len(y_axis) == 1:
                y_axis = y_axis * numpy.ones((image.shape[0], ))
            elif len(y_axis) == 2:
                y_axis = y_axis[0] * numpy.arange(image.shape[0]) + y_axis[1]

            xcalib = ArrayCalibration(x_axis)
            ycalib = ArrayCalibration(y_axis)

        self._plot.remove(kind=("scatter", "image",))
        if xcalib.is_affine() and ycalib.is_affine():
            # regular image
            xorigin, xscale = xcalib(0), xcalib.get_slope()
            yorigin, yscale = ycalib(0), ycalib.get_slope()
            origin = (xorigin, yorigin)
            scale = (xscale, yscale)

            self._plot.addImage(image, legend=legend,
                                origin=origin, scale=scale,
                                replace=True)
        else:
            scatterx, scattery = numpy.meshgrid(x_axis, y_axis)
            # fixme: i don't think this can handle "irregular" RGBA images
            self._plot.addScatter(numpy.ravel(scatterx),
                                  numpy.ravel(scattery),
                                  numpy.ravel(image),
                                  legend=legend)

        title = ""
        if self.__title:
            title += self.__title
        if not title.strip().endswith(self.__signals_names[auxSigIdx]):
            title += "\n" + self.__signals_names[auxSigIdx]
        self._plot.setGraphTitle(title)
        self._plot.getXAxis().setLabel(self.__x_axis_name)
        self._plot.getYAxis().setLabel(self.__y_axis_name)
        self._plot.resetZoom()

    def clear(self):
        old = self._selector.blockSignals(True)
        self._selector.clear()
        self._selector.blockSignals(old)
        self._plot.clear()
Example #39
0
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 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)
Example #41
0
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()
Example #42
0
class ArrayCurvePlot(qt.QWidget):
    """
    Widget for plotting a curve from a multi-dimensional signal array
    and a 1D axis array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last dimension must have the same length as
    the axis array.

    The widget provides sliders to select indices on the first (n - 1)
    dimensions of the signal array, and buttons to add/replace selected
    curves to the plot.

    This widget also handles simple 2D or 3D scatter plots (third dimension
    displayed as colour of points).
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signals = None
        self.__signals_names = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__x_axis_errors = None
        self.__values = None

        self._plot = Plot1D(self)

        self._selector = NumpyAxesSelector(self)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False

        self._plot.sigActiveCurveChanged.connect(self._setYLabelFromActiveLegend)

        layout = qt.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot)
        layout.addWidget(self._selector)

        self.setLayout(layout)

    def getPlot(self):
        """Returns the plot used for the display

        :rtype: Plot1D
        """
        return self._plot

    def setCurvesData(self, ys, x=None,
                      yerror=None, xerror=None,
                      ylabels=None, xlabel=None, title=None):
        """

        :param List[ndarray] ys: List of arrays to be represented by the y (vertical) axis.
            It can be multiple n-D array whose last dimension must
            have the same length as x (and values must be None)
        :param ndarray x: 1-D dataset used as the curve's x values. If provided,
            its lengths must be equal to the length of the last dimension of
            ``y`` (and equal to the length of ``value``, for a scatter plot).
        :param ndarray yerror: Single array of errors for y (same shape), or None.
            There can only be one array, and it applies to the first/main y
            (no y errors for auxiliary_signals curves).
        :param ndarray xerror: 1-D dataset of errors for x, or None
        :param str ylabels: Labels for each curve's Y axis
        :param str xlabel: Label for X axis
        :param str title: Graph title
        """
        self.__signals = ys
        self.__signals_names = ylabels or (["Y"] * len(ys))
        self.__signal_errors = yerror
        self.__axis = x
        self.__axis_name = xlabel
        self.__x_axis_errors = xerror

        if self.__selector_is_connected:
            self._selector.selectionChanged.disconnect(self._updateCurve)
            self.__selector_is_connected = False
        self._selector.setData(ys[0])
        self._selector.setAxisNames(["Y"])

        if len(ys[0].shape) < 2:
            self._selector.hide()
        else:
            self._selector.show()

        self._plot.setGraphTitle(title or "")
        self._updateCurve()

        if not self.__selector_is_connected:
            self._selector.selectionChanged.connect(self._updateCurve)
            self.__selector_is_connected = True

    def _updateCurve(self):
        selection = self._selector.selection()
        ys = [sig[selection] for sig in self.__signals]
        y0 = ys[0]
        len_y = len(y0)
        x = self.__axis
        if x is None:
            x = numpy.arange(len_y)
        elif numpy.isscalar(x) or len(x) == 1:
            # constant axis
            x = x * numpy.ones_like(y0)
        elif len(x) == 2 and len_y != 2:
            # linear calibration a + b * x
            x = x[0] + x[1] * numpy.arange(len_y)

        self._plot.remove(kind=("curve",))

        for i in range(len(self.__signals)):
            legend = self.__signals_names[i]

            # errors only supported for primary signal in NXdata
            y_errors = None
            if i == 0 and self.__signal_errors is not None:
                y_errors = self.__signal_errors[self._selector.selection()]
            self._plot.addCurve(x, ys[i], legend=legend,
                                xerror=self.__x_axis_errors,
                                yerror=y_errors)
            if i == 0:
                self._plot.setActiveCurve(legend)

        self._plot.resetZoom()
        self._plot.getXAxis().setLabel(self.__axis_name)
        self._plot.getYAxis().setLabel(self.__signals_names[0])

    def _setYLabelFromActiveLegend(self, previous_legend, new_legend):
        for ylabel in self.__signals_names:
            if new_legend is not None and new_legend == ylabel:
                self._plot.getYAxis().setLabel(ylabel)
                break

    def clear(self):
        old = self._selector.blockSignals(True)
        self._selector.clear()
        self._selector.blockSignals(old)
        self._plot.clear()
Example #43
0
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()
Example #44
0
class DataViewer(qt.QFrame):
    """Widget to display any kind of data

    .. image:: img/DataViewer.png

    The method :meth:`setData` allows to set any data to the widget. Mostly
    `numpy.array` and `h5py.Dataset` are supported with adapted views. Other
    data types are displayed using a text viewer.

    A default view is automatically selected when a data is set. The method
    :meth:`setDisplayMode` allows to change the view. To have a graphical tool
    to select the view, prefer using the widget :class:`DataViewerFrame`.

    The dimension of the input data and the expected dimension of the selected
    view can differ. For example you can display an image (2D) from 4D
    data. In this case a :class:`NumpyAxesSelector` is displayed to allow the
    user to select the axis mapping and the slicing of other axes.

    .. code-block:: python

        import numpy
        data = numpy.random.rand(500,500)
        viewer = DataViewer()
        viewer.setData(data)
        viewer.setVisible(True)
    """

    EMPTY_MODE = 0
    PLOT1D_MODE = 10
    PLOT2D_MODE = 20
    PLOT3D_MODE = 30
    RAW_MODE = 40
    RAW_ARRAY_MODE = 41
    RAW_RECORD_MODE = 42
    RAW_SCALAR_MODE = 43
    STACK_MODE = 50
    HDF5_MODE = 60

    displayedViewChanged = qt.Signal(object)
    """Emitted when the displayed view changes"""

    dataChanged = qt.Signal()
    """Emitted when the data changes"""

    currentAvailableViewsChanged = qt.Signal()
    """Emitted when the current available views (which support the current
    data) change"""
    def __init__(self, parent=None):
        """Constructor

        :param QWidget parent: The parent of the widget
        """
        super(DataViewer, self).__init__(parent)

        self.__stack = qt.QStackedWidget(self)
        self.__numpySelection = NumpyAxesSelector(self)
        self.__numpySelection.selectedAxisChanged.connect(
            self.__numpyAxisChanged)
        self.__numpySelection.selectionChanged.connect(
            self.__numpySelectionChanged)
        self.__numpySelection.customAxisChanged.connect(
            self.__numpyCustomAxisChanged)

        self.setLayout(qt.QVBoxLayout(self))
        self.layout().addWidget(self.__stack, 1)

        group = qt.QGroupBox(self)
        group.setLayout(qt.QVBoxLayout())
        group.layout().addWidget(self.__numpySelection)
        group.setTitle("Axis selection")
        self.__axisSelection = group

        self.layout().addWidget(self.__axisSelection)

        self.__currentAvailableViews = []
        self.__currentView = None
        self.__data = None
        self.__useAxisSelection = False
        self.__userSelectedView = None

        self.__views = []
        self.__index = {}
        """store stack index for each views"""

        self._initializeViews()

    def _initializeViews(self):
        """Inisialize the available views"""
        views = self.createDefaultViews(self.__stack)
        self.__views = list(views)
        self.setDisplayMode(self.EMPTY_MODE)

    def createDefaultViews(self, parent=None):
        """Create and returns available views which can be displayed by default
        by the data viewer. It is called internally by the widget. It can be
        overwriten to provide a different set of viewers.

        :param QWidget parent: QWidget parent of the views
        :rtype: list[silx.gui.data.DataViews.DataView]
        """
        viewClasses = [
            DataViews._EmptyView,
            DataViews._Hdf5View,
            DataViews._NXdataView,
            DataViews._Plot1dView,
            DataViews._ImageView,
            DataViews._Plot3dView,
            DataViews._RawView,
            DataViews._StackView,
        ]
        views = []
        for viewClass in viewClasses:
            try:
                view = viewClass(parent)
                views.append(view)
            except Exception:
                _logger.warning("%s instantiation failed. View is ignored" %
                                viewClass.__name__)
                _logger.debug("Backtrace", exc_info=True)

        return views

    def clear(self):
        """Clear the widget"""
        self.setData(None)

    def normalizeData(self, data):
        """Returns a normalized data if the embed a numpy or a dataset.
        Else returns the data."""
        return _normalizeData(data)

    def __getStackIndex(self, view):
        """Get the stack index containing the view.

        :param silx.gui.data.DataViews.DataView view: The view
        """
        if view not in self.__index:
            widget = view.getWidget()
            index = self.__stack.addWidget(widget)
            self.__index[view] = index
        else:
            index = self.__index[view]
        return index

    def __clearCurrentView(self):
        """Clear the current selected view"""
        view = self.__currentView
        if view is not None:
            view.clear()

    def __numpyCustomAxisChanged(self, name, value):
        view = self.__currentView
        if view is not None:
            view.setCustomAxisValue(name, value)

    def __updateNumpySelectionAxis(self):
        """
        Update the numpy-selector according to the needed axis names
        """
        previous = self.__numpySelection.blockSignals(True)
        self.__numpySelection.clear()
        info = DataViews.DataInfo(self.__data)
        axisNames = self.__currentView.axesNames(self.__data, info)
        if info.isArray and self.__data is not None and axisNames is not None:
            self.__useAxisSelection = True
            self.__numpySelection.setAxisNames(axisNames)
            self.__numpySelection.setCustomAxis(
                self.__currentView.customAxisNames())
            data = self.normalizeData(self.__data)
            self.__numpySelection.setData(data)
            if hasattr(data, "shape"):
                isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
            else:
                isVisible = True
            self.__axisSelection.setVisible(isVisible)
        else:
            self.__useAxisSelection = False
            self.__axisSelection.setVisible(False)
        self.__numpySelection.blockSignals(previous)

    def __updateDataInView(self):
        """
        Update the views using the current data
        """
        if self.__useAxisSelection:
            self.__displayedData = self.__numpySelection.selectedData()
        else:
            self.__displayedData = self.__data

        qt.QTimer.singleShot(10, self.__setDataInView)

    def __setDataInView(self):
        self.__currentView.setData(self.__displayedData)

    def setDisplayedView(self, view):
        """Set the displayed view.

        Change the displayed view according to the view itself.

        :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
        """
        self.__userSelectedView = view
        self._setDisplayedView(view)

    def _setDisplayedView(self, view):
        """Internal set of the displayed view.

        Change the displayed view according to the view itself.

        :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
        """
        if self.__currentView is view:
            return
        self.__clearCurrentView()
        self.__currentView = view
        self.__updateNumpySelectionAxis()
        self.__updateDataInView()
        stackIndex = self.__getStackIndex(self.__currentView)
        if self.__currentView is not None:
            self.__currentView.select()
        self.__stack.setCurrentIndex(stackIndex)
        self.displayedViewChanged.emit(view)

    def getViewFromModeId(self, modeId):
        """Returns the first available view which have the requested modeId.

        :param int modeId: Requested mode id
        :rtype: silx.gui.data.DataViews.DataView
        """
        for view in self.__views:
            if view.modeId() == modeId:
                return view
        return view

    def setDisplayMode(self, modeId):
        """Set the displayed view using display mode.

        Change the displayed view according to the requested mode.

        :param int modeId: Display mode, one of

            - `EMPTY_MODE`: display nothing
            - `PLOT1D_MODE`: display the data as a curve
            - `PLOT2D_MODE`: display the data as an image
            - `PLOT3D_MODE`: display the data as an isosurface
            - `RAW_MODE`: display the data as a table
            - `STACK_MODE`: display the data as a stack of images
            - `HDF5_MODE`: display the data as a table
        """
        try:
            view = self.getViewFromModeId(modeId)
        except KeyError:
            raise ValueError("Display mode %s is unknown" % modeId)
        self._setDisplayedView(view)

    def displayedView(self):
        """Returns the current displayed view.

        :rtype: silx.gui.data.DataViews.DataView
        """
        return self.__currentView

    def addView(self, view):
        """Allow to add a view to the dataview.

        If the current data support this view, it will be displayed.

        :param DataView view: A dataview
        """
        self.__views.append(view)
        # TODO It can be skipped if the view do not support the data
        self.__updateAvailableViews()

    def removeView(self, view):
        """Allow to remove a view which was available from the dataview.

        If the view was displayed, the widget will be updated.

        :param DataView view: A dataview
        """
        self.__views.remove(view)
        self.__stack.removeWidget(view.getWidget())
        # invalidate the full index. It will be updated as expected
        self.__index = {}

        if self.__userSelectedView is view:
            self.__userSelectedView = None

        if view is self.__currentView:
            self.__updateView()
        else:
            # TODO It can be skipped if the view is not part of the
            # available views
            self.__updateAvailableViews()

    def __updateAvailableViews(self):
        """
        Update available views from the current data.
        """
        data = self.__data
        # sort available views according to priority
        info = DataViews.DataInfo(data)
        priorities = [v.getDataPriority(data, info) for v in self.__views]
        views = zip(priorities, self.__views)
        views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views)
        views = sorted(views, reverse=True)

        # store available views
        if len(views) == 0:
            self.__setCurrentAvailableViews([])
            available = []
        else:
            available = [v[1] for v in views]
            self.__setCurrentAvailableViews(available)

    def __updateView(self):
        """Display the data using the widget which fit the best"""
        data = self.__data

        # update available views for this data
        self.__updateAvailableViews()
        available = self.__currentAvailableViews

        # display the view with the most priority (the default view)
        view = self.getDefaultViewFromAvailableViews(data, available)
        self.__clearCurrentView()
        try:
            self._setDisplayedView(view)
        except Exception as e:
            # in case there is a problem to read the data, try to use a safe
            # view
            view = self.getSafeViewFromAvailableViews(data, available)
            self._setDisplayedView(view)
            raise e

    def getSafeViewFromAvailableViews(self, data, available):
        """Returns a view which is sure to display something without failing
        on rendering.

        :param object data: data which will be displayed
        :param list[view] available: List of available views, from highest
            priority to lowest.
        :rtype: DataView
        """
        hdf5View = self.getViewFromModeId(DataViewer.HDF5_MODE)
        if hdf5View in available:
            return hdf5View
        return self.getViewFromModeId(DataViewer.EMPTY_MODE)

    def getDefaultViewFromAvailableViews(self, data, available):
        """Returns the default view which will be used according to available
        views.

        :param object data: data which will be displayed
        :param list[view] available: List of available views, from highest
            priority to lowest.
        :rtype: DataView
        """
        if len(available) > 0:
            # returns the view with the highest priority
            if self.__userSelectedView in available:
                return self.__userSelectedView
            self.__userSelectedView = None
            view = available[0]
        else:
            # else returns the empty view
            view = self.getViewFromModeId(DataViewer.EMPTY_MODE)
        return view

    def __setCurrentAvailableViews(self, availableViews):
        """Set the current available viewa

        :param List[DataView] availableViews: Current available viewa
        """
        self.__currentAvailableViews = availableViews
        self.currentAvailableViewsChanged.emit()

    def currentAvailableViews(self):
        """Returns the list of available views for the current data

        :rtype: List[DataView]
        """
        return self.__currentAvailableViews

    def availableViews(self):
        """Returns the list of registered views

        :rtype: List[DataView]
        """
        return self.__views

    def setData(self, data):
        """Set the data to view.

        It mostly can be a h5py.Dataset or a numpy.ndarray. Other kind of
        objects will be displayed as text rendering.

        :param numpy.ndarray data: The data.
        """
        self.__data = data
        self.__displayedData = None
        self.__updateView()
        self.__updateNumpySelectionAxis()
        self.__updateDataInView()
        self.dataChanged.emit()

    def __numpyAxisChanged(self):
        """
        Called when axis selection of the numpy-selector changed
        """
        self.__clearCurrentView()

    def __numpySelectionChanged(self):
        """
        Called when data selection of the numpy-selector changed
        """
        self.__updateDataInView()

    def data(self):
        """Returns the data"""
        return self.__data

    def displayMode(self):
        """Returns the current display mode"""
        return self.__currentView.modeId()
Example #45
0
class ArrayCurvePlot(qt.QWidget):
    """
    Widget for plotting a curve from a multi-dimensional signal array
    and a 1D axis array.

    The signal array can have an arbitrary number of dimensions, the only
    limitation being that the last dimension must have the same length as
    the axis array.

    The widget provides sliders to select indices on the first (n - 1)
    dimensions of the signal array, and buttons to add/replace selected
    curves to the plot.

    This widget also handles simple 2D or 3D scatter plots (third dimension
    displayed as colour of points).
    """
    def __init__(self, parent=None):
        """

        :param parent: Parent QWidget
        """
        super(ArrayCurvePlot, self).__init__(parent)

        self.__signal = None
        self.__signal_name = None
        self.__signal_errors = None
        self.__axis = None
        self.__axis_name = None
        self.__axis_errors = None
        self.__values = None

        self.__first_curve_added = False

        self._plot = Plot1D(self)
        self._plot.setDefaultColormap(   # for scatters
                {"name": "viridis",
                 "vmin": 0., "vmax": 1.,   # ignored (autoscale) but mandatory
                 "normalization": "linear",
                 "autoscale": True})

        self.selectorDock = qt.QDockWidget("Data selector", self._plot)
        # not closable
        self.selectorDock.setFeatures(qt.QDockWidget.DockWidgetMovable
                                      | qt.QDockWidget.DockWidgetFloatable)
        self._selector = NumpyAxesSelector(self.selectorDock)
        self._selector.setNamedAxesSelectorVisibility(False)
        self.__selector_is_connected = False
        self.selectorDock.setWidget(self._selector)
        self._plot.addTabbedDockWidget(self.selectorDock)

        layout = qt.QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._plot, 0, 0)

        self.setLayout(layout)

    def setCurveData(self,
                     y,
                     x=None,
                     values=None,
                     yerror=None,
                     xerror=None,
                     ylabel=None,
                     xlabel=None,
                     title=None):
        """

        :param y: dataset to be represented by the y (vertical) axis.
            For a scatter, this must be a 1D array and x and values must be
            1-D arrays of the same size.
            In other cases, it can be a n-D array whose last dimension must
            have the same length as x (and values must be None)
        :param x: 1-D dataset used as the curve's x values. If provided,
            its lengths must be equal to the length of the last dimension of
            ``y`` (and equal to the length of ``value``, for a scatter plot).
        :param values: Values, to be provided for a x-y-value scatter plot.
            This will be used to compute the color map and assign colors
            to the points.
        :param yerror: 1-D dataset of errors for y, or None
        :param xerror: 1-D dataset of errors for x, or None
        :param ylabel: Label for Y axis
        :param xlabel: Label for X axis
        :param title: Graph title
        """
        self.__signal = y
        self.__signal_name = ylabel
        self.__signal_errors = yerror
        self.__axis = x
        self.__axis_name = xlabel
        self.__axis_errors = xerror
        self.__values = values

        if self.__selector_is_connected:
            self._selector.selectionChanged.disconnect(self._updateCurve)
            self.__selector_is_connected = False
        self._selector.setData(y)
        self._selector.setAxisNames([ylabel or "Y"])

        if len(y.shape) < 2:
            self.selectorDock.hide()
        else:
            self.selectorDock.show()

        self._plot.setGraphTitle(title or "")
        self._plot.setGraphXLabel(self.__axis_name or "X")
        self._plot.setGraphYLabel(self.__signal_name or "Y")
        self._updateCurve()

        if not self.__selector_is_connected:
            self._selector.selectionChanged.connect(self._updateCurve)
            self.__selector_is_connected = True

    def _updateCurve(self):
        y = self._selector.selectedData()
        x = self.__axis
        if x is None:
            x = numpy.arange(len(y))
        elif numpy.isscalar(x) or len(x) == 1:
            # constant axis
            x = x * numpy.ones_like(y)
        elif len(x) == 2 and len(y) != 2:
            # linear calibration a + b * x
            x = x[0] + x[1] * numpy.arange(len(y))
        legend = self.__signal_name + "["
        for sl in self._selector.selection():
            if sl == slice(None):
                legend += ":, "
            else:
                legend += str(sl) + ", "
        legend = legend[:-2] + "]"
        if self.__signal_errors is not None:
            y_errors = self.__signal_errors[self._selector.selection()]
        else:
            y_errors = None

        self._plot.remove(kind=("curve", "scatter"))

        # values: x-y-v scatter
        if self.__values is not None:
            self._plot.addScatter(x,
                                  y,
                                  self.__values,
                                  legend=legend,
                                  xerror=self.__axis_errors,
                                  yerror=y_errors)

        # x monotonically increasing: curve
        elif numpy.all(numpy.diff(x) > 0):
            self._plot.addCurve(x,
                                y,
                                legend=legend,
                                xerror=self.__axis_errors,
                                yerror=y_errors)

        # scatter
        else:
            self._plot.addScatter(x,
                                  y,
                                  value=numpy.ones_like(y),
                                  legend=legend,
                                  xerror=self.__axis_errors,
                                  yerror=y_errors)
        self._plot.resetZoom()
        self._plot.setGraphXLabel(self.__axis_name)
        self._plot.setGraphYLabel(self.__signal_name)

    def clear(self):
        self._plot.clear()