New Upstream Release - pymca

Ready changes

Summary

Merged new upstream version: 5.9.2+dfsg (was: 5.8.7+dfsg).

Diff

diff --git a/LICENSE b/LICENSE
index cf7cc538..3fe5313a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
- The PyMca X-Ray Fluorescence Toolkit is Copyright (C) 2004-2022 of the European Synchrotron Radiation Facility (ESRF).
+ The PyMca X-Ray Fluorescence Toolkit is Copyright (C) 2004-2023 of the European Synchrotron Radiation Facility (ESRF).
 
 Unless otherways stated in the relevant accompanying source code, the default license of these modules is MIT.
 
diff --git a/PKG-INFO b/PKG-INFO
index 4c137173..b73be076 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 2.1
 Name: PyMca5
-Version: 5.8.7
+Version: 5.9.2
 Summary: Mapping and X-Ray Fluorescence Analysis
 Home-page: http://pymca.sourceforge.net
+Download-URL: https://github.com/vasole/pymca/archive/v5.9.2.tar.gz
 Author: V. Armando Sole
 Author-email: sole@esrf.fr
 License: MIT
-Download-URL: https://github.com/vasole/pymca/archive/v5.8.7.tar.gz
 Platform: any
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Programming Language :: Python :: 3
@@ -26,7 +26,9 @@ License-File: LICENSE
 License-File: LICENSE.GPL
 License-File: LICENSE.LGPL
 License-File: LICENSE.MIT
+Requires-Dist: numpy
+Requires-Dist: matplotlib>1.0
+Requires-Dist: fisx>=1.1.6
+Requires-Dist: h5py
 
 Stand-alone application and Python tools for interactive and/or batch processing analysis of X-Ray Fluorescence Spectra. Graphical user interface (GUI) and batch processing capabilities provided
-
-
diff --git a/PyMca5.egg-info/PKG-INFO b/PyMca5.egg-info/PKG-INFO
index 4c137173..b73be076 100644
--- a/PyMca5.egg-info/PKG-INFO
+++ b/PyMca5.egg-info/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 2.1
 Name: PyMca5
-Version: 5.8.7
+Version: 5.9.2
 Summary: Mapping and X-Ray Fluorescence Analysis
 Home-page: http://pymca.sourceforge.net
+Download-URL: https://github.com/vasole/pymca/archive/v5.9.2.tar.gz
 Author: V. Armando Sole
 Author-email: sole@esrf.fr
 License: MIT
-Download-URL: https://github.com/vasole/pymca/archive/v5.8.7.tar.gz
 Platform: any
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Programming Language :: Python :: 3
@@ -26,7 +26,9 @@ License-File: LICENSE
 License-File: LICENSE.GPL
 License-File: LICENSE.LGPL
 License-File: LICENSE.MIT
+Requires-Dist: numpy
+Requires-Dist: matplotlib>1.0
+Requires-Dist: fisx>=1.1.6
+Requires-Dist: h5py
 
 Stand-alone application and Python tools for interactive and/or batch processing analysis of X-Ray Fluorescence Spectra. Graphical user interface (GUI) and batch processing capabilities provided
-
-
diff --git a/PyMca5.egg-info/SOURCES.txt b/PyMca5.egg-info/SOURCES.txt
index 8fdd95c3..ccb1e6ec 100644
--- a/PyMca5.egg-info/SOURCES.txt
+++ b/PyMca5.egg-info/SOURCES.txt
@@ -428,6 +428,7 @@ PyMca5/PyMcaGui/plotting/RGBCorrelatorGraph.py
 PyMca5/PyMcaGui/plotting/RenameCurveDialog.py
 PyMca5/PyMcaGui/plotting/ScatterPlotCorrelatorWidget.py
 PyMca5/PyMcaGui/plotting/SilxMaskImageWidget.py
+PyMca5/PyMcaGui/plotting/SilxPlotActions.py
 PyMca5/PyMcaGui/plotting/SilxRGBCorrelatorGraph.py
 PyMca5/PyMcaGui/plotting/Silx_Icons.py
 PyMca5/PyMcaGui/plotting/Toolbars.py
@@ -746,6 +747,7 @@ PyMca5/tests/ConfigDictTest.py
 PyMca5/tests/DataTest.py
 PyMca5/tests/EdfFileTest.py
 PyMca5/tests/ElementsTest.py
+PyMca5/tests/FastXRFLinearFitTest.py
 PyMca5/tests/GefitTest.py
 PyMca5/tests/HDF5UtilsTest.py
 PyMca5/tests/McaAdvancedFitWidgetTest.py
diff --git a/PyMca5/PyMcaCore/RedisTools.py b/PyMca5/PyMcaCore/RedisTools.py
index 27922dc7..e509d726 100644
--- a/PyMca5/PyMcaCore/RedisTools.py
+++ b/PyMca5/PyMcaCore/RedisTools.py
@@ -33,8 +33,13 @@ import logging
 _logger = logging.getLogger(__name__)
 
 from bliss.config import get_sessions_list
-from bliss.config.settings import scan as rdsscan
-from bliss.data.node import get_node, get_nodes
+try:
+    from blissdata.settings import scan as rdsscan
+    from blissdata.data.node import get_node, get_nodes
+except ImportError:
+    _logger.info("Trying deprecated access to Redis")
+    from bliss.config.settings import scan as rdsscan
+    from bliss.data.node import get_node, get_nodes
 
 _NODE_TYPES = [ "channel",
                 "lima",
diff --git a/PyMca5/PyMcaCore/StackBase.py b/PyMca5/PyMcaCore/StackBase.py
index 5e86a675..89818bec 100644
--- a/PyMca5/PyMcaCore/StackBase.py
+++ b/PyMca5/PyMcaCore/StackBase.py
@@ -284,7 +284,7 @@ class StackBase(object):
             elif self.mcaIndex == 0:
                 mcaMax = numpy.nanmax(numpy.nanmax(self._stack.data, axis=-1), axis=-1)
             else:
-                _logger.info("Unsupported index for max spectrum calculation")
+                logger.info("Unsupported index for max spectrum calculation")
         else:
             t0 = time.time()
             shape = self._stack.data.shape
diff --git a/PyMca5/PyMcaGraph/Plot.py b/PyMca5/PyMcaGraph/Plot.py
index 24a8eb14..b66affe8 100644
--- a/PyMca5/PyMcaGraph/Plot.py
+++ b/PyMca5/PyMcaGraph/Plot.py
@@ -200,7 +200,9 @@ class Plot(PlotBase.PlotBase):
 
         # zoom handling (should we take care of it?)
         self.enableZoom = self.setZoomModeEnabled
-        self.setZoomModeEnabled(True)
+        # next line was giving troubles with silx backend(s) issue #1026
+        if backend in ["matplotlib", "mpl"]:
+            self.setZoomModeEnabled(True)
 
         self._defaultDataMargins = (0., 0., 0., 0.)
 
diff --git a/PyMca5/PyMcaGraph/backends/MatplotlibBackend.py b/PyMca5/PyMcaGraph/backends/MatplotlibBackend.py
index c36fb0bb..f4d4ae37 100644
--- a/PyMca5/PyMcaGraph/backends/MatplotlibBackend.py
+++ b/PyMca5/PyMcaGraph/backends/MatplotlibBackend.py
@@ -99,15 +99,18 @@ elif 'PyQt6.QtCore' in sys.modules:
     QtGui.QApplication = QtWidgets.QApplication
 else:
     try:
-        from PyQt4 import QtCore, QtGui
-        matplotlib.rcParams['backend'] = 'Qt4Agg'
+        from PyQt5 import QtCore, QtGui, QtWidgets
+        QtGui.QApplication = QtWidgets.QApplication
+        matplotlib.rcParams['backend'] = 'Qt5Agg'
     except ImportError:
         try:
-            from PyQt5 import QtCore, QtGui, QtWidgets
+            from PyQt6 import QtCore, QtGui, QtWidgets
             QtGui.QApplication = QtWidgets.QApplication
             matplotlib.rcParams['backend'] = 'Qt5Agg'
         except ImportError:
-            from PySide import QtCore, QtGui
+            from PySide6 import QtCore, QtGui, QtWidgets
+            QtGui.QApplication = QtWidgets.QApplication
+            matplotlib.rcParams['backend'] = 'Qt5Agg'
 if ("PyQt4.QtCore" in sys.modules) or ("PySide.QtCore" in sys.modules):
     from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
     TK = False
@@ -985,6 +988,11 @@ class MatplotlibGraph(FigureCanvas):
                 ddict['y'] = artist.get_ydata()
                 ddict['xdata'] = artist.get_xdata()
                 ddict['ydata'] = artist.get_ydata()
+                # matplotlib 3.7.x was giving different output than previous versions
+                for key in ["x", "y", "xdata", "ydata"]:
+                    if hasattr(ddict[key], "__len__"):
+                        if len(ddict[key]) == 1:
+                            ddict[key] = ddict[key][0]
                 self._callback(ddict)
             self._pickingInfo = {}
             return
@@ -1598,7 +1606,7 @@ class MatplotlibBackend(PlotBackend.PlotBackend):
                 scatterPlot = True
         if scatterPlot:
             # scatter plot
-            if color.dtype not in [numpy.float32, numpy.float]:
+            if color.dtype not in [numpy.float32, numpy.float64]:
                 actualColor = color / 255.
             else:
                 actualColor = color
@@ -1665,6 +1673,8 @@ class MatplotlibBackend(PlotBackend.PlotBackend):
             axes.fill_between(x, 1.0e-8, y)
         #curveList[-1].set_fillstyle('bottom')
         if hasattr(curveList[-1], "set_marker"):
+            if symbol is None:
+                symbol = "None"
             curveList[-1].set_marker(symbol)
         curveList[-1]._plot_info = {'color':color,
                                       'linewidth':linewidth,
diff --git a/PyMca5/PyMcaGui/PyMcaQt.py b/PyMca5/PyMcaGui/PyMcaQt.py
index e71aeb93..4778a8a9 100644
--- a/PyMca5/PyMcaGui/PyMcaQt.py
+++ b/PyMca5/PyMcaGui/PyMcaQt.py
@@ -351,6 +351,53 @@ elif BINDING == 'PyQt6':
         QAbstractItemView.AnyKeyPressed = QAbstractItemView.EditTrigger.AnyKeyPressed
         QAbstractItemView.AllEditTriggers = QAbstractItemView.EditTrigger.AllEditTriggers
 
+    if not hasattr(QPalette, "Normal"):
+        if hasattr(QPalette, "Active"):
+            QPalette.Normal = QPalette.Active
+        else:
+            QPalette.Disabled = QPalette.ColorGroup.Disabled
+            QPalette.Active = QPalette.ColorGroup.Active
+            QPalette.Inactive = QPalette.ColorGroup.Inactive
+            QPalette.Normal = QPalette.ColorGroup.Normal
+
+            QPalette.Window = QPalette.ColorRole.Window
+            QPalette.WindowText = QPalette.ColorRole.WindowText
+            QPalette.Base = QPalette.ColorRole.Base
+            QPalette.AlternateBase = QPalette.ColorRole.AlternateBase
+            QPalette.ToolTipBase = QPalette.ColorRole.ToolTipBase
+            QPalette.ToolTipText = QPalette.ColorRole.ToolTipText
+            QPalette.PlaceholderText = QPalette.ColorRole.PlaceholderText
+            QPalette.Text = QPalette.ColorRole.Text
+            QPalette.Button = QPalette.ColorRole.Button
+            QPalette.ButtonText = QPalette.ColorRole.ButtonText
+            QPalette.BrightText = QPalette.ColorRole.BrightText
+
+        try:
+            from silx.gui import qt as SilxQt
+            if not hasattr(SilxQt.QPalette, "Normal"):
+                if hasattr(SilxQt.QPalette, "Active"):
+                    SilxQt.QPalette.Normal = SilxQt.QPalette.Active
+                else:
+                    SilxQt.QPalette.Disabled = SilxQt.QPalette.ColorGroup.Disabled
+                    SilxQt.QPalette.Active = SilxQt.QPalette.ColorGroup.Active
+                    SilxQt.QPalette.Inactive = SilxQt.QPalette.ColorGroup.Inactive
+                    SilxQt.QPalette.Normal = SilxQt.QPalette.ColorGroup.Normal
+
+                    SilxQt.QPalette.Window = SilxQt.QPalette.ColorRole.Window
+                    SilxQt.QPalette.WindowText = SilxQt.QPalette.ColorRole.WindowText
+                    SilxQt.QPalette.Base = SilxQt.QPalette.ColorRole.Base
+                    SilxQt.QPalette.AlternateBase = SilxQt.QPalette.ColorRole.AlternateBase
+                    SilxQt.QPalette.ToolTipBase = SilxQt.QPalette.ColorRole.ToolTipBase
+                    SilxQt.QPalette.ToolTipText = SilxQt.QPalette.ColorRole.ToolTipText
+                    SilxQt.QPalette.PlaceholderText = SilxQt.QPalette.ColorRole.PlaceholderText
+                    SilxQt.QPalette.Text = SilxQt.QPalette.ColorRole.Text
+                    SilxQt.QPalette.Button = SilxQt.QPalette.ColorRole.Button
+                    SilxQt.QPalette.ButtonText = SilxQt.QPalette.ColorRole.ButtonText
+                    SilxQt.QPalette.BrightText = SilxQt.QPalette.ColorRole.BrightText
+        except Exception:
+            _logger.info("Exception patching silx")
+            pass
+
     # use a (bad) replacement for QDesktopWidget
     class QDesktopWidget:
         def height(self):
diff --git a/PyMca5/PyMcaGui/io/QSpecFileWidget.py b/PyMca5/PyMcaGui/io/QSpecFileWidget.py
index 256f8ad6..0b6900c7 100644
--- a/PyMca5/PyMcaGui/io/QSpecFileWidget.py
+++ b/PyMca5/PyMcaGui/io/QSpecFileWidget.py
@@ -819,7 +819,10 @@ class QSpecFileWidget(QSelectorWidget.QSelectorWidget):
         else:
             ddict['auto'] = "OFF"
         ddict["2d"]= self.meshBox.isChecked()
-        ddict["3d"]= self.object3DBox.isChecked()
+        if hasattr(self, "object3DBox"):
+            ddict["3d"] = self.object3DBox.isChecked()
+        else:
+            ddict["3d"] = False
         ddict["mca"]= self.forceMcaBox.isChecked()
         return ddict
 
diff --git a/PyMca5/PyMcaGui/io/SpecFileDataInfo.py b/PyMca5/PyMcaGui/io/SpecFileDataInfo.py
index 83082a5c..83809368 100644
--- a/PyMca5/PyMcaGui/io/SpecFileDataInfo.py
+++ b/PyMca5/PyMcaGui/io/SpecFileDataInfo.py
@@ -30,25 +30,19 @@ __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
 import sys
 from PyMca5.PyMcaGui import PyMcaQt as qt
 
-try:
-    from silx.gui.widgets.TableWidget import TableWidget
-except ImportError:
-    from PyMca5.PyMcaGui.misc.TableWidget import TableWidget
+from PyMca5.PyMcaGui.misc.TableWidget import TableWidget
 
 QTVERSION = qt.qVersion()
 
 class QTable(TableWidget):
     def setText(self, row, col, text):
-        if qt.qVersion() < "4.0.0":
-            QTable.setText(self, row, col, text)
+        item = self.item(row, col)
+        if item is None:
+            item = qt.QTableWidgetItem(text,
+                                       qt.QTableWidgetItem.Type)
+            self.setItem(row, col, item)
         else:
-            item = self.item(row, col)
-            if item is None:
-                item = qt.QTableWidgetItem(text,
-                                           qt.QTableWidgetItem.Type)
-                self.setItem(row, col, item)
-            else:
-                item.setText(text)
+            item.setText(text)
 
 class SpecFileDataInfoCustomEvent(qt.QEvent):
     def __init__(self, ddict):
@@ -84,13 +78,10 @@ class SpecFileDataInfo(qt.QTabWidget):
     ]
 
     def __init__(self, info, parent=None, name="DataSpecFileInfo", fl=0):
-        if QTVERSION < '4.0.0':
-            qt.QTabWidget.__init__(self, parent, name, fl)
-            self.setContentsMargins(5, 5, 5, 5)
-        else:
-            qt.QTabWidget.__init__(self, parent)
-            if name is not None:self.setWindowTitle(name)
-            self._notifyCloseEventToWidget = []
+        qt.QTabWidget.__init__(self, parent)
+        if name is not None:
+            self.setWindowTitle(name)
+        self._notifyCloseEventToWidget = []
         self.info= info
         self.__createInfoTable()
         self.__createMotorTable()
@@ -99,14 +90,13 @@ class SpecFileDataInfo(qt.QTabWidget):
         self.__createEDFHeaderText()
         self.__createFileHeaderText()
 
-    if QTVERSION > '4.0.0':
-        def sizeHint(self):
-            return qt.QSize(2 * qt.QTabWidget.sizeHint(self).width(),
-                            3 * qt.QTabWidget.sizeHint(self).height())
+    def sizeHint(self):
+        return qt.QSize(2 * qt.QTabWidget.sizeHint(self).width(),
+                        3 * qt.QTabWidget.sizeHint(self).height())
 
-        def notifyCloseEventToWidget(self, widget):
-            if widget not in self._notifyCloseEventToWidget:
-                self._notifyCloseEventToWidget.append(widget)
+    def notifyCloseEventToWidget(self, widget):
+        if widget not in self._notifyCloseEventToWidget:
+            self._notifyCloseEventToWidget.append(widget)
 
     def __createInfoTable(self):
         pars= [ par for par in self.InfoTableItems if par[0] in self.info.keys() ]
@@ -119,41 +109,32 @@ class SpecFileDataInfo(qt.QTabWidget):
             self.__adjustTable(table)
             self.addTab(table, "Info")
 
-    def __createTable(self, rows, head_par, head_val):
-        if qt.qVersion() < '4.0.0':
-            table= QTable(self)
+    def __createTable(self, rows, head_par, head_val, index=False):
+        table= QTable()
+        if index:
+            labels = ["Index"]
         else:
-            table= QTable()
-        table.setColumnCount(2)
+            labels = []
+        labels = labels + [head_par, head_val]
+        table.setColumnCount(len(labels))
         table.setRowCount(rows)
-        if qt.qVersion() < '4.0.0':
-            table.setReadOnly(1)
-            table.setSelectionMode(QTable.SingleRow)
-        else:
-            table.setSelectionMode(qt.QTableWidget.NoSelection)
+        #table.setSelectionMode(qt.QTableWidget.NoSelection)
         table.verticalHeader().hide()
-        if qt.qVersion() < '4.0.0':
-            table.setLeftMargin(0)
-            table.horizontalHeader().setLabel(0, head_par)
-            table.horizontalHeader().setLabel(1, head_val)
-        else:
-            labels = [head_par, head_val]
-            for i in range(len(labels)):
-                item = table.horizontalHeaderItem(i)
-                if item is None:
-                    item = qt.QTableWidgetItem(labels[i],
-                                               qt.QTableWidgetItem.Type)
-                item.setText(labels[i])
-                table.setHorizontalHeaderItem(i,item)
+        for i in range(len(labels)):
+            item = table.horizontalHeaderItem(i)
+            if item is None:
+                item = qt.QTableWidgetItem(labels[i],
+                                           qt.QTableWidgetItem.Type)
+            item.setText(labels[i])
+            table.setHorizontalHeaderItem(i,item)
         return table
 
     def __adjustTable(self, table):
         for col in range(table.columnCount()):
             table.resizeColumnToContents(col)
-        if qt.qVersion() > '4.0.0':
-            rheight = table.horizontalHeader().sizeHint().height()
-            for row in range(table.rowCount()):
-                table.setRowHeight(row, rheight)
+        rheight = table.horizontalHeader().sizeHint().height()
+        for row in range(table.rowCount()):
+            table.setRowHeight(row, rheight)
 
     def __createMotorTable(self):
         nameKeys = ["MotorNames", "motor_mne"]
@@ -188,17 +169,27 @@ class SpecFileDataInfo(qt.QTabWidget):
                 print("Incorrent number of labels or values")
                 return
             if num:
-                table= self.__createTable(num, "Motor", "Position")
-                if sys.version_info > (3, 3):
-                    sorted_list = sorted(names, key=str.casefold)
-                else:
-                    sorted_list = sorted(names)
-                for i in range(num):
-                    idx = names.index(sorted_list[i])
-                    table.setText(i, 0, str(names[idx]))
-                    table.setText(i, 1, str(pos[idx]))
+                table= self.__createTable(num, "Motor", "Position", index=True)
+                numbers = list(range(num))
+                def sort_column(column, table=table, numbers=numbers, names=names, positions=pos):
+                    if column == 1:
+                        sorted_list = sorted(names, key=str.casefold)
+                        sorted_list = [names.index(x) for x in sorted_list]
+                    else:
+                        sorted_list = [int(x) for x in numbers]
+                    for i in range(num):
+                        idx = sorted_list[i]
+                        table.setText(i, 0, "%d" % numbers[idx])
+                        table.setText(i, 1, str(names[idx]))
+                        table.setText(i, 2, str(pos[idx]))
+                sort_column(0)
+                tip = "Copy selection to clipboard with CTRL-C."
+                tip += "\nDoubleclick on Index or Motor header to sort accordingly."
+                table.setToolTip(tip)
                 self.__adjustTable(table)
                 self.addTab(table, "Motors")
+                headerView = table.horizontalHeader()
+                headerView.sectionDoubleClicked[int].connect(sort_column)
 
     def __createCounterTable(self):
         nameKeys = ["LabelNames", "counter_mne"]
@@ -241,12 +232,8 @@ class SpecFileDataInfo(qt.QTabWidget):
             return
         text= self.info.get("Header", None)
         if text is not None:
-            if qt.qVersion() < '4.0.0':
-                wid = qt.QTextEdit(self)
-                wid.setText("\n".join(text))
-            else:
-                wid = qt.QTextEdit()
-                wid.insertHtml("<BR>".join(text))
+            wid = qt.QTextEdit()
+            wid.insertHtml("<BR>".join(text))
             wid.setReadOnly(1)
             self.addTab(wid, "Scan Header")
 
@@ -274,12 +261,8 @@ class SpecFileDataInfo(qt.QTabWidget):
     def __createFileHeaderText(self):
         text= self.info.get("FileHeader", None)
         if text not in [None, []]:
-            if qt.qVersion() < '4.0.0':
-                wid = qt.QTextEdit(self)
-                wid.setText("\n".join(text))
-            else:
-                wid = qt.QTextEdit()
-                wid.insertHtml("<BR>".join(text))
+            wid = qt.QTextEdit()
+            wid.insertHtml("<BR>".join(text))
             wid.setReadOnly(1)
             self.addTab(wid, "File Header")
 
diff --git a/PyMca5/PyMcaGui/io/hdf5/Hdf5NodeView.py b/PyMca5/PyMcaGui/io/hdf5/Hdf5NodeView.py
index 5f40497a..d3913346 100644
--- a/PyMca5/PyMcaGui/io/hdf5/Hdf5NodeView.py
+++ b/PyMca5/PyMcaGui/io/hdf5/Hdf5NodeView.py
@@ -110,6 +110,10 @@ class Plot2DWithPlugins(Plot2D):
                     method="getPlugin2DInstance",
                     directoryList=PLUGINS_DIR)
         self._toolbar.addWidget(pluginsToolButton)
+        if hasattr(self, "getIntensityHistogramAction"):
+            self.getIntensityHistogramAction().setVisible(True)
+        else:
+            print("Plot2D getIntensityHistogramAction missing")
 
 
 class Plot2DViewWithPlugins(DataViews._Plot2dView):
@@ -117,7 +121,6 @@ class Plot2DViewWithPlugins(DataViews._Plot2dView):
         widget = Plot2DWithPlugins(parent=parent)
         widget.setDefaultColormap(self.defaultColormap())
         widget.getColormapAction().setColorDialog(self.defaultColorDialog())
-        widget.getIntensityHistogramAction().setVisible(True)
         widget.setKeepDataAspectRatio(False)
         widget.getXAxis().setLabel('X')
         widget.getYAxis().setLabel('Y')
diff --git a/PyMca5/PyMcaGui/physics/xrf/McaCalWidget.py b/PyMca5/PyMcaGui/physics/xrf/McaCalWidget.py
index f0244a8e..2f4c9b85 100644
--- a/PyMca5/PyMcaGui/physics/xrf/McaCalWidget.py
+++ b/PyMca5/PyMcaGui/physics/xrf/McaCalWidget.py
@@ -429,7 +429,7 @@ class McaCalWidget(qt.QDialog):
             self.current  = current
             order = dict['caldict'][current]['order']
             self.caldict[current]['order'] = order
-            if order == "ID18":
+            if order in ["ID18", "ID14"]:
                 result = self.timeCalibratorCalibration()
                 if result is None:
                     return
@@ -808,6 +808,8 @@ class McaCalWidget(qt.QDialog):
         """
         if order == "TOF":
             return self.calculateTOF(usedpeaks)
+        if order in ["ID18", "ID14"]:
+            order = 2
         if len(usedpeaks) == 1:
             if (usedpeaks[0][0] - 0.0) > 1.0E-20:
                 return [0.0, usedpeaks[0][1]/usedpeaks[0][0], 0.0]
@@ -1024,7 +1026,7 @@ class CalibrationParameters(qt.QWidget):
                                        options=['1st','2nd'])
             else:
                 self.orderbox = SimpleComboBox(parw,
-                                       options=['1st','2nd','TOF', 'ID18'])
+                                       options=['1st','2nd','TOF', 'ID14'])
         layout.addWidget(lab)
         layout.addWidget(self.orderbox)
         lab= qt.QLabel("A:", parw)
@@ -1126,8 +1128,8 @@ class CalibrationParameters(qt.QWidget):
             self.CLabel.setText("Vr:")
             self.CText.setReadOnly(0)
             self.CFixed.show()
-        elif qstring == "ID18":
-            self.caldict[self.currentcal]['order'] = 'ID18'
+        elif qstring in ["ID14", "ID18"]:
+            self.caldict[self.currentcal]['order'] = 'ID14'
             self.CLabel.setText("C:")
             self.CText.setReadOnly(1)
             if QTVERSION > '4.0.0':
diff --git a/PyMca5/PyMcaGui/physics/xrf/QtMcaAdvancedFitReport.py b/PyMca5/PyMcaGui/physics/xrf/QtMcaAdvancedFitReport.py
index afe0c54b..3898d1c3 100644
--- a/PyMca5/PyMcaGui/physics/xrf/QtMcaAdvancedFitReport.py
+++ b/PyMca5/PyMcaGui/physics/xrf/QtMcaAdvancedFitReport.py
@@ -739,6 +739,9 @@ class QtMcaAdvancedFitReport:
             for peak0 in iterator:
                 name  = peak0+"esc"
                 peak  = peak0+"esc"
+                if name not in result[group]:
+                    # i.e. peak0 = "Al K" and Si detector
+                    continue
                 if result[group][name]['ratio'] > 0.0:
                     text += '<tr><td></td>'
                     energy = ("%.3f" % (result[group][peak]['energy']))
diff --git a/PyMca5/PyMcaGui/plotting/MaskScatterWidget.py b/PyMca5/PyMcaGui/plotting/MaskScatterWidget.py
index 3f64fcb4..312a221f 100644
--- a/PyMca5/PyMcaGui/plotting/MaskScatterWidget.py
+++ b/PyMca5/PyMcaGui/plotting/MaskScatterWidget.py
@@ -191,7 +191,7 @@ class MaskScatterWidget(PlotWindow.PlotWindow):
         image = numpy.histogram2d(y[idx], x[idx],
                                   bins=bins,
                                   #range=(binsY, binsX),
-                                  normed=False)
+                                  density=False)
         self._binsX = image[2]
         self._binsY = image[1]
         self._bins = bins
@@ -240,7 +240,7 @@ class MaskScatterWidget(PlotWindow.PlotWindow):
         self.yScale = (y0, deltaY)
         binsX = numpy.arange(bins[0]) * deltaX
         binsY = numpy.arange(bins[1]) * deltaY
-        image = numpy.histogram2d(y[idx], x[idx], bins=(binsY, binsX), normed=False)
+        image = numpy.histogram2d(y[idx], x[idx], bins=(binsY, binsX), density=False)
         self._binsX = image[2]
         self._binsY = image[1]
         self._bins = bins
@@ -258,13 +258,13 @@ class MaskScatterWidget(PlotWindow.PlotWindow):
                     mask = numpy.round(numpy.histogram2d(y[idx], x[idx],
                                        bins=(binsY, binsX),
                                        weights=weights,
-                                       normed=True)[0] * weightsSum * volume).astype(numpy.uint8)
+                                       density=True)[0] * weightsSum * volume).astype(numpy.uint8)
                 else:
                     #print("GOOD PATH")
                     mask = numpy.histogram2d(y[idx], x[idx],
                                              bins=(binsY, binsX),
                                              weights=weights,
-                                             normed=False)[0]
+                                             density=False)[0]
                     mask[mask > 0] = 1
                 #print(mask.min(), mask.max())
                 self._densityPlotWidget.setSelectionMask(mask, plot=False)
@@ -454,7 +454,9 @@ class MaskScatterWidget(PlotWindow.PlotWindow):
                     color = self._selectionColors[i].copy()
                     if useAlpha:
                         if len(color) == 4:
-                            if type(color[3]) in [numpy.uint8, numpy.int]:
+                            if type(color[3]) in [numpy.uint8,
+                                                  numpy.int32,
+                                                  numpy.int64]:
                                 color[3] = self._alphaLevel
                     # a copy of the input info is needed in order not
                     # to set the main curve to that color
diff --git a/PyMca5/PyMcaGui/plotting/Q4PyMcaPrintPreview.py b/PyMca5/PyMcaGui/plotting/Q4PyMcaPrintPreview.py
index 571a9e36..20bd248b 100644
--- a/PyMca5/PyMcaGui/plotting/Q4PyMcaPrintPreview.py
+++ b/PyMca5/PyMcaGui/plotting/Q4PyMcaPrintPreview.py
@@ -606,7 +606,7 @@ class GraphicsResizeRectItem(qt.QGraphicsRectItem):
         pen.setStyle(qt.Qt.NoPen)
         self.setPen(pen)
         self.setBrush(color)
-        self.setFlag(self.ItemIsMovable, True)
+        self.setFlag(qt.QGraphicsItem.ItemIsMovable, True)
         self.show()
 
     def hoverEnterEvent(self, event):
diff --git a/PyMca5/PyMcaGui/plotting/SilxMaskImageWidget.py b/PyMca5/PyMcaGui/plotting/SilxMaskImageWidget.py
index 7f1f66ce..4d6173cf 100644
--- a/PyMca5/PyMcaGui/plotting/SilxMaskImageWidget.py
+++ b/PyMca5/PyMcaGui/plotting/SilxMaskImageWidget.py
@@ -65,7 +65,8 @@ logging.disable(logging.ERROR)
 
 import silx
 from silx.gui.plot import PlotWidget
-from silx.gui.plot import PlotActions
+from PyMca5.PyMcaGui.plotting import SilxPlotActions as PlotActions
+
 from silx.gui.plot import PlotToolButtons
 from silx.gui.plot.MaskToolsWidget import MaskToolsWidget, MaskToolsDockWidget
 from silx.gui.plot.AlphaSlider import NamedImageAlphaSlider
@@ -579,6 +580,11 @@ class SilxMaskImageWidget(qt.QMainWindow):
                 PlotActions.ColormapAction(plot=self.plot, parent=self))
         self.addAction(self.colormapAction)
 
+        self.pixelIntensitiesHistoAction = self.group.addAction(
+                PlotActions.PixelIntensitiesHistoAction(plot=self.plot, parent=self))
+        self.addAction(self.pixelIntensitiesHistoAction)
+        self.pixelIntensitiesHistoAction.setVisible(True)
+
         self.copyAction = self.group.addAction(
                 PlotActions.CopyAction(plot=self.plot, parent=self))
         self.addAction(self.copyAction)
@@ -675,8 +681,8 @@ class SilxMaskImageWidget(qt.QMainWindow):
         index = objects.index(self.colormapAction)
         objects.insert(index + 1, self.keepDataAspectRatioButton)
         objects.insert(index + 2, self.yAxisInvertedButton)
-        objects.insert(index + 3, self.saveToolbutton)
-        objects.insert(index + 4, self.backgroundButton)
+        objects.insert(index + 4, self.saveToolbutton)
+        objects.insert(index + 5, self.backgroundButton)
         for obj in objects:
             if isinstance(obj, qt.QAction):
                 toolbar.addAction(obj)
diff --git a/PyMca5/PyMcaGui/plotting/SilxPlotActions.py b/PyMca5/PyMcaGui/plotting/SilxPlotActions.py
new file mode 100644
index 00000000..b3c76508
--- /dev/null
+++ b/PyMca5/PyMcaGui/plotting/SilxPlotActions.py
@@ -0,0 +1,35 @@
+#/*##########################################################################
+# Copyright (C) 2023 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+#############################################################################*/
+__author__ = "V.A. Sole - ESRF Data Analysis"
+__contact__ = "sole@esrf.fr"
+__license__ = "MIT"
+__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
+
+__doc__ = """Silx 1.x and 2.x compatibility layer"""
+
+try:
+    from silx.gui.plot.PlotActions import *
+except ImportError:
+    from silx.gui.plot.actions.control import *
+    from silx.gui.plot.actions.io import *
+    from silx.gui.plot.actions.histogram import PixelIntensitiesHistoAction
diff --git a/PyMca5/PyMcaGui/pymca/McaWindow.py b/PyMca5/PyMcaGui/pymca/McaWindow.py
index d670dcb1..ce5f2d2a 100644
--- a/PyMca5/PyMcaGui/pymca/McaWindow.py
+++ b/PyMca5/PyMcaGui/pymca/McaWindow.py
@@ -1031,7 +1031,7 @@ class McaWindow(ScanWindow):
                                                 legend=legend,
                                                 info=curveinfo,
                                                 own=True)
-                            if calibrationOrder == 'ID18':
+                            if calibrationOrder in ["ID14", "ID18"]:
                                 self.setGraphXLabel('Time')
                             else:
                                 self.setGraphXLabel('Energy')
@@ -1076,7 +1076,7 @@ class McaWindow(ScanWindow):
                                               legend=legend,
                                               info=curveinfo,
                                               own=True)
-                            if calibrationOrder == 'ID18':
+                            if calibrationOrder in ["ID14", "ID18"]:
                                 self.setGraphXLabel('Time')
                             else:
                                 self.setGraphXLabel('Energy')
diff --git a/PyMca5/PyMcaGui/pymca/PyMcaBatch.py b/PyMca5/PyMcaGui/pymca/PyMcaBatch.py
index 96a032c2..eea0aea1 100644
--- a/PyMca5/PyMcaGui/pymca/PyMcaBatch.py
+++ b/PyMca5/PyMcaGui/pymca/PyMcaBatch.py
@@ -35,6 +35,7 @@ import subprocess
 import signal
 import atexit
 import logging
+import traceback
 from glob import glob
 from contextlib import contextmanager
 try:
@@ -1822,6 +1823,10 @@ class McaBatchWindow(qt.QWidget):
             except Exception:
                 _logger.warning("ERROR on REPORT %s", sys.exc_info())
                 _logger.warning("%s", sys.exc_info()[1])
+                try:
+                    _logger.warning("%s", ''.join(traceback.format_tb(sys.exc_info()[2])))
+                except Exception:
+                    pass
                 _logger.warning("filename = %s key =%s " , filename, key)
                 _logger.warning("If your batch is stopped, please report this")
                 _logger.warning("error sending the above mentioned file and the")
diff --git a/PyMca5/PyMcaGui/pymca/PyMcaImageWindow.py b/PyMca5/PyMcaGui/pymca/PyMcaImageWindow.py
index e0f43c89..ee3c156d 100644
--- a/PyMca5/PyMcaGui/pymca/PyMcaImageWindow.py
+++ b/PyMca5/PyMcaGui/pymca/PyMcaImageWindow.py
@@ -88,7 +88,7 @@ class PyMcaImageWindow(RGBImageCalculator.RGBImageCalculator):
             self._connectCorrelator()
         if self._imageData is None:
             return
-        if self._imageData == []:
+        if len(self._imageData) == 0:
             return
 
         if not RGBImageCalculator.RGBImageCalculator._addImageClicked(self):
diff --git a/PyMca5/PyMcaGui/pymca/PyMcaMain.py b/PyMca5/PyMcaGui/pymca/PyMcaMain.py
index 12e385be..71549d40 100644
--- a/PyMca5/PyMcaGui/pymca/PyMcaMain.py
+++ b/PyMca5/PyMcaGui/pymca/PyMcaMain.py
@@ -127,11 +127,7 @@ if __name__ == '__main__':
 from PyMca5.PyMcaGui import PyMcaQt as qt
 from PyMca5.PyMcaGui.io import PyMcaFileDialogs
 QTVERSION = qt.qVersion()
-if sys.platform == 'darwin':
-    if backend is not None:
-        if backend.lower() in ["gl", "opengl"]:
-            if hasattr(qt, 'QOpenGLWidget'):
-                print("Warning: OpenGL backend not fully supported")
+
 try:
     import silx
     # try to import silx prior to importing matplotlib to prevent
diff --git a/PyMca5/PyMcaGui/pymca/QHDF5StackWizard.py b/PyMca5/PyMcaGui/pymca/QHDF5StackWizard.py
index 34c62c75..20810807 100644
--- a/PyMca5/PyMcaGui/pymca/QHDF5StackWizard.py
+++ b/PyMca5/PyMcaGui/pymca/QHDF5StackWizard.py
@@ -35,6 +35,9 @@ safe_str = qt.safe_str
 from PyMca5.PyMcaGui.io.hdf5 import QNexusWidget
 from PyMca5.PyMcaCore import NexusDataSource
 from PyMca5 import PyMcaDirs
+import logging
+
+_logger = logging.getLogger(__name__)
 
 
 class IntroductionPage(qt.QWizardPage):
@@ -150,6 +153,12 @@ class StackIndexWidget(qt.QWidget):
             self.buttonGroup.buttonClicked[int].connect(self._slot)
 
     def _slot(self, button):
+        if hasattr(button, "text"):
+            # received a button
+            pass
+        else:
+            # received an integer
+            button = self.buttonGroup.button(button)
         if "first" in safe_str(button.text()).lower():
             self._stackIndex =  0
         else:
diff --git a/PyMca5/PyMcaGui/pymca/RGBImageCalculator.py b/PyMca5/PyMcaGui/pymca/RGBImageCalculator.py
index 6f315ad8..76cd538b 100644
--- a/PyMca5/PyMcaGui/pymca/RGBImageCalculator.py
+++ b/PyMca5/PyMcaGui/pymca/RGBImageCalculator.py
@@ -331,7 +331,7 @@ class RGBImageCalculator(qt.QWidget):
         _logger.debug("Add image clicked")
         if self._imageData is None:
             return
-        if self._imageData == []:
+        if len(self._imageData) == 0:
             return
         text = "%s" % self.name.text()
         if not len(text):
diff --git a/PyMca5/PyMcaGui/pymca/ScanWindowInfoWidget.py b/PyMca5/PyMcaGui/pymca/ScanWindowInfoWidget.py
index fa6f7247..7138bbfe 100644
--- a/PyMca5/PyMcaGui/pymca/ScanWindowInfoWidget.py
+++ b/PyMca5/PyMcaGui/pymca/ScanWindowInfoWidget.py
@@ -31,6 +31,10 @@ __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
 
 import numpy
 from PyMca5.PyMcaGui import PyMcaQt as qt
+import logging
+
+_logger = logging.getLogger(__name__)
+
 QTVERSION = qt.qVersion()
 
 """
diff --git a/PyMca5/PyMcaGui/pymca/SilxMcaWindow.py b/PyMca5/PyMcaGui/pymca/SilxMcaWindow.py
index 09c487ea..30522adb 100644
--- a/PyMca5/PyMcaGui/pymca/SilxMcaWindow.py
+++ b/PyMca5/PyMcaGui/pymca/SilxMcaWindow.py
@@ -1022,7 +1022,7 @@ class McaWindow(ScanWindow.ScanWindow):
                                               legend=legend,
                                               info=curveinfo,
                                               own=True)
-                            if calibrationOrder == 'ID18':
+                            if calibrationOrder in ["ID14", "ID18"]:
                                 self.setGraphXLabel('Time')
                             else:
                                 self.setGraphXLabel('Energy')
@@ -1066,7 +1066,7 @@ class McaWindow(ScanWindow.ScanWindow):
                                               legend=legend,
                                               info=curveinfo,
                                               own=True)
-                            if calibrationOrder == 'ID18':
+                            if calibrationOrder in ["ID14", "ID18"]:
                                 self.setGraphXLabel('Time')
                             else:
                                 self.setGraphXLabel('Energy')
diff --git a/PyMca5/PyMcaGui/pymca/StackPluginResultsWindow.py b/PyMca5/PyMcaGui/pymca/StackPluginResultsWindow.py
index 40386e7f..1a167876 100644
--- a/PyMca5/PyMcaGui/pymca/StackPluginResultsWindow.py
+++ b/PyMca5/PyMcaGui/pymca/StackPluginResultsWindow.py
@@ -1,5 +1,5 @@
-#/*##########################################################################
-# Copyright (C) 2004-2022 European Synchrotron Radiation Facility
+# /*##########################################################################
+# Copyright (C) 2004-2023 European Synchrotron Radiation Facility
 #
 # This file is part of the PyMca X-ray Fluorescence Toolkit developed at
 # the ESRF.
@@ -31,6 +31,7 @@ import os
 import sys
 import numpy
 from PyMca5.PyMcaGui import PyMcaQt as qt
+
 if hasattr(qt, "QString"):
     QString = qt.QString
 else:
@@ -43,30 +44,32 @@ from PyMca5.PyMcaGui.pymca import ImageListStatsWidget
 from PyMca5.PyMcaGui.io import PyMcaFileDialogs
 from PyMca5.PyMcaIO import ArraySave
 
+
 class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
     def __init__(self, *var, **kw):
         ddict = {}
-        ddict['usetab'] = kw.get("usetab",True)
-        ddict['aspect'] = kw.get("aspect",True)
-        ddict['profileselection'] = kw.get("profileselection",True)
+        ddict["usetab"] = kw.get("usetab", True)
+        ddict["aspect"] = kw.get("aspect", True)
+        ddict["profileselection"] = kw.get("profileselection", True)
         ddict.update(kw)
-        ddict['standalonesave'] = False
+        ddict["standalonesave"] = False
         MaskImageWidget.MaskImageWidget.__init__(self, *var, **ddict)
         self.slider = qt.QSlider(self)
         self.slider.setOrientation(qt.Qt.Horizontal)
         self.slider.setMinimum(0)
         self.slider.setMaximum(0)
-        if ddict['usetab']:
+        if ddict["usetab"]:
             # The 1D graph
             self.spectrumGraph = ScanWindow.ScanWindow(self)
             self.spectrumGraph.enableOwnSave(False)
-            self.spectrumGraph.sigIconSignal.connect( \
-                                    self._spectrumGraphIconSlot)
+            self.spectrumGraph.sigIconSignal.connect(self._spectrumGraphIconSlot)
             self.spectrumGraph.saveMenu = qt.QMenu()
-            self.spectrumGraph.saveMenu.addAction(QString("Save From Current"),
-                                                  self.saveCurrentSpectrum)
-            self.spectrumGraph.saveMenu.addAction(QString("Save From All"),
-                                                  self.saveAllSpectra)
+            self.spectrumGraph.saveMenu.addAction(
+                QString("Save From Current"), self.saveCurrentSpectrum
+            )
+            self.spectrumGraph.saveMenu.addAction(
+                QString("Save From All"), self.saveAllSpectra
+            )
             self.mainTab.addTab(self.spectrumGraph, "VECTORS")
 
         self.mainLayout.addWidget(self.slider)
@@ -79,43 +82,42 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
         self.spectrumGraphTitles = None
         standalonesave = kw.get("standalonesave", True)
         if standalonesave:
-            self.graphWidget.saveToolButton.clicked.connect(\
-                                         self._saveToolButtonSignal)
+            self.graphWidget.saveToolButton.clicked.connect(self._saveToolButtonSignal)
             self._saveMenu = qt.QMenu()
-            self._saveMenu.addAction(QString("Image Data"),
-                                     self.saveImageList)
-            self._saveMenu.addAction(QString("Standard Graphics"),
-                                     self.graphWidget._saveIconSignal)
-            self._saveMenu.addAction(QString("Matplotlib") ,
-                             self._saveMatplotlibImage)
+            self._saveMenu.addAction(QString("Image Data"), self.saveImageList)
+            self._saveMenu.addAction(
+                QString("Standard Graphics"), self.graphWidget._saveIconSignal
+            )
+            self._saveMenu.addAction(QString("Matplotlib"), self._saveMatplotlibImage)
         self.multiplyIcon = qt.QIcon(qt.QPixmap(IconDict["swapsign"]))
         infotext = "Multiply image by -1"
-        self.multiplyButton = self.graphWidget._addToolButton(\
-                                        self.multiplyIcon,
-                                        self._multiplyIconChecked,
-                                        infotext,
-                                        toggle = False,
-                                        position = 12)
+        self.multiplyButton = self.graphWidget._addToolButton(
+            self.multiplyIcon,
+            self._multiplyIconChecked,
+            infotext,
+            toggle=False,
+            position=12,
+        )
 
         # The density plot widget
         self.__scatterPlotWidgetDataToUpdate = True
-        self.scatterPlotWidget = ScatterPlotCorrelatorWidget.ScatterPlotCorrelatorWidget(None,
-                                    labels=["Legend",
-                                            "X",
-                                            "Y"],
-                                    types=["Text",
-                                           "RadioButton",
-                                           "RadioButton"],
-                                    maxNRois=1)
+        self.scatterPlotWidget = (
+            ScatterPlotCorrelatorWidget.ScatterPlotCorrelatorWidget(
+                None,
+                labels=["Legend", "X", "Y"],
+                types=["Text", "RadioButton", "RadioButton"],
+                maxNRois=1,
+            )
+        )
         self.__scatterPlotWidgetDataToUpdate = True
         self.__maskToScatterConnected = True
         self.sigMaskImageWidgetSignal.connect(self._internalSlot)
-        self.scatterPlotWidget.sigMaskScatterWidgetSignal.connect( \
-                                              self._internalSlot)
+        self.scatterPlotWidget.sigMaskScatterWidgetSignal.connect(self._internalSlot)
 
         # add the command to show it to the menu
-        self.additionalSelectionMenu().addAction(QString("Show scatter plot"),
-                                                 self.showScatterPlot)
+        self.additionalSelectionMenu().addAction(
+            QString("Show scatter plot"), self.showScatterPlot
+        )
 
         # The stats widget
         self.statsWidget = None
@@ -149,9 +151,9 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             self.spectrumGraph.replot()
 
     def buildAndConnectImageButtonBox(self, replace=True, multiple=False):
-        super(StackPluginResultsWindow, self).\
-                                buildAndConnectImageButtonBox(replace=replace,
-                                                            multiple=multiple)
+        super(StackPluginResultsWindow, self).buildAndConnectImageButtonBox(
+            replace=replace, multiple=multiple
+        )
 
     def showImage(self, index=0, moveslider=True):
         if self.imageList is None:
@@ -164,9 +166,15 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
         if moveslider:
             self.slider.setValue(index)
 
-    def setStackPluginResults(self, images, spectra=None,
-                   image_names = None, spectra_names = None,
-                   xvalues=None, spectra_titles=None):
+    def setStackPluginResults(
+        self,
+        images,
+        spectra=None,
+        image_names=None,
+        spectra_names=None,
+        xvalues=None,
+        spectra_titles=None,
+    ):
         self.spectrumList = spectra
         if type(images) == type([]):
             self.imageList = images
@@ -180,13 +188,13 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             nimages = images.shape[0]
             self.imageList = [0] * nimages
             for i in range(nimages):
-                self.imageList[i] = images[i,:]
+                self.imageList[i] = images[i, :]
                 if 0:
-                    #leave the data as they originally come
+                    # leave the data as they originally come
                     if self.imageList[i].max() < 0:
                         self.imageList[i] *= -1
                         if self.spectrumList is not None:
-                            self.spectrumList [i] *= -1
+                            self.spectrumList[i] *= -1
             if image_names is None:
                 self.imageNames = []
                 for i in range(nimages):
@@ -195,7 +203,7 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
                 self.imageNames = image_names
 
         if self.imageList is not None:
-            self.slider.setMaximum(len(self.imageList)-1)
+            self.slider.setMaximum(len(self.imageList) - 1)
             self.showImage(0)
         else:
             self.slider.setMaximum(0)
@@ -246,9 +254,11 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             # only the the scatter plot to be updated unless hidden
             if self.scatterPlotWidget.isHidden():
                 return
-            if ddict["event"] in ["selectionMaskChanged",
-                                  "resetSelection",
-                                  "invertSelection"]:
+            if ddict["event"] in [
+                "selectionMaskChanged",
+                "resetSelection",
+                "invertSelection",
+            ]:
                 mask = self.getSelectionMask()
                 if mask is None:
                     mask = numpy.zeros(self.imageList[0].shape, numpy.uint8)
@@ -258,12 +268,13 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
 
         elif ddict["id"] == id(self.scatterPlotWidget):
             # signal generated by the scatter plot
-            if ddict["event"] in ["selectionMaskChanged",
-                                  "resetSelection",
-                                  "invertSelection"]:
+            if ddict["event"] in [
+                "selectionMaskChanged",
+                "resetSelection",
+                "invertSelection",
+            ]:
                 mask = self.scatterPlotWidget.getSelectionMask()
-                super(StackPluginResultsWindow, self).setSelectionMask(mask,
-                                                                    plot=True)
+                super(StackPluginResultsWindow, self).setSelectionMask(mask, plot=True)
                 ddict["id"] = id(self)
                 try:
                     self.__maskToScatterConnected = False
@@ -275,6 +286,8 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
         super(StackPluginResultsWindow, self).setSelectionMask(*var, **kw)
         if not self.scatterPlotWidget.isHidden():
             self._updateScatterPlotWidget()
+        if self.statsWidget is not None:
+            self.statsWidget.setSelectionMask(self.getSelectionMask())
 
     def showScatterPlot(self):
         if self.scatterPlotWidget.isHidden():
@@ -287,10 +300,10 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             return
         labels = []
         for i in range(len(self.imageList)):
-            labels.append(self.imageNames[i].replace(" ","_"))
-        return MaskImageWidget.MaskImageWidget.saveImageList(self,
-                                                             imagelist=self.imageList,
-                                                             labels=labels)
+            labels.append(self.imageNames[i].replace(" ", "_"))
+        return MaskImageWidget.MaskImageWidget.saveImageList(
+            self, imagelist=self.imageList, labels=labels
+        )
 
     def _spectrumGraphIconSlot(self, ddict):
         if ddict["event"] == "iconClicked" and ddict["key"] == "save":
@@ -300,21 +313,25 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
         return self.spectrumGraph._QSimpleOperation("save")
 
     def saveAllSpectra(self):
-        fltrs = ['Raw ASCII *.txt',
-                 '","-separated CSV *.csv',
-                 '";"-separated CSV *.csv',
-                 '"tab"-separated CSV *.csv',
-                 'OMNIC CSV *.csv']
+        fltrs = [
+            "Raw ASCII *.txt",
+            '","-separated CSV *.csv',
+            '";"-separated CSV *.csv',
+            '"tab"-separated CSV *.csv',
+            "OMNIC CSV *.csv",
+        ]
         message = "Enter file name to be used as root"
-        fileList, fileFilter = PyMcaFileDialogs.getFileList(parent=self,
-                                                            filetypelist=fltrs,
-                                                            message=message,
-                                                            currentdir=None,
-                                                            mode="SAVE",
-                                                            getfilter=True,
-                                                            single=True,
-                                                            currentfilter=None,
-                                                            native=None)
+        fileList, fileFilter = PyMcaFileDialogs.getFileList(
+            parent=self,
+            filetypelist=fltrs,
+            message=message,
+            currentdir=None,
+            mode="SAVE",
+            getfilter=True,
+            single=True,
+            currentfilter=None,
+            native=None,
+        )
         if not len(fileList):
             return
 
@@ -335,7 +352,7 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             # extension is csv but saved as ASCII
             csv = False
             ext = "csv"
-            csvseparator = ","        
+            csvseparator = ","
         else:
             csv = True
             ext = "csv"
@@ -356,22 +373,23 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
             x = self.xValues[index]
             y = self.spectrumList[index]
             filename = os.path.join(dirname, root + fmt % (index, ext))
-            ArraySave.saveXY(x, y, filename, ylabel=legend,
-                             csv=csv, csvseparator=csvseparator)
+            ArraySave.saveXY(
+                x, y, filename, ylabel=legend, csv=csv, csvseparator=csvseparator
+            )
 
     def setImageList(self, imagelist):
         self.imageList = imagelist
         self.spectrumList = None
         if imagelist is not None:
-            self.slider.setMaximum(len(self.imageList)-1)
+            self.slider.setMaximum(len(self.imageList) - 1)
             self.showImage(0)
 
     def _addAllImageClicked(self):
         ddict = {}
-        ddict['event'] = "addAllClicked"
-        ddict['images'] = self.imageList
-        ddict['titles'] = self.imageNames
-        ddict['id'] = id(self)
+        ddict["event"] = "addAllClicked"
+        ddict["images"] = self.imageList
+        ddict["titles"] = self.imageNames
+        ddict["id"] = id(self)
         self.emitMaskImageSignal(ddict)
 
     def showStatsWidget(self):
@@ -383,6 +401,7 @@ class StackPluginResultsWindow(MaskImageWidget.MaskImageWidget):
         self.statsWidget.setImageList(self.imageList, image_names=self.imageNames)
         self.statsWidget.show()
 
+
 def test():
     app = qt.QApplication([])
     app.lastWindowClosed.connect(app.quit)
@@ -390,18 +409,24 @@ def test():
     container = StackPluginResultsWindow()
     data = numpy.arange(20000)
     data.shape = 2, 100, 100
-    data[1, 0:100,0:50] = 100
-    container.setStackPluginResults(data, spectra=[numpy.arange(100.), numpy.arange(100.)+10],
-                                image_names=["I1", "I2"], spectra_names=["V1", "V2"])
+    data[1, 0:100, 0:50] = 100
+    container.setStackPluginResults(
+        data,
+        spectra=[numpy.arange(100.0), numpy.arange(100.0) + 10],
+        image_names=["I1", "I2"],
+        spectra_names=["V1", "V2"],
+    )
     container.show()
     container.showStatsWidget()
+
     def theSlot(ddict):
-        print(ddict['event'])
+        print(ddict["event"])
 
     container.sigMaskImageWidgetSignal.connect(theSlot)
     app.exec()
 
+
 if __name__ == "__main__":
     import numpy
-    test()
 
+    test()
diff --git a/PyMca5/PyMcaGui/pymca/XMCDWindow.py b/PyMca5/PyMcaGui/pymca/XMCDWindow.py
index 07cdb405..88aafe62 100644
--- a/PyMca5/PyMcaGui/pymca/XMCDWindow.py
+++ b/PyMca5/PyMcaGui/pymca/XMCDWindow.py
@@ -1,5 +1,5 @@
-#/*##########################################################################
-# Copyright (C) 2004-2022 European Synchrotron Radiation Facility
+# /*##########################################################################
+# Copyright (C) 2004-2023 European Synchrotron Radiation Facility
 #
 # This file is part of the PyMca X-ray Fluorescence Toolkit developed at
 # the ESRF.
@@ -52,9 +52,10 @@ _logger = logging.getLogger(__name__)
 if _logger.getEffectiveLevel() == logging.DEBUG:
     numpy.set_printoptions(threshold=50)
 
-NEWLINE = '\n'
-class TreeWidgetItem(qt.QTreeWidgetItem):
+NEWLINE = "\n"
+
 
+class TreeWidgetItem(qt.QTreeWidgetItem):
     __legendColumn = 1
 
     def __init__(self, parent, itemList):
@@ -62,96 +63,101 @@ class TreeWidgetItem(qt.QTreeWidgetItem):
 
     def __lt__(self, other):
         col = self.treeWidget().sortColumn()
-        val      = self.text(col)
+        val = self.text(col)
         valOther = other.text(col)
         if val == valOther:
             ret = False
-        elif val in ['---']:
+        elif val in ["---"]:
             ret = True
         elif col > self.__legendColumn:
             try:
-                ret  = (float(val) < float(valOther))
+                ret = float(val) < float(valOther)
             except ValueError:
-                ret  = val < valOther
+                ret = val < valOther
         else:
-            ret  = val < valOther
+            ret = val < valOther
         return ret
 
-class XMCDOptions(qt.QDialog):
 
+class XMCDOptions(qt.QDialog):
     def __init__(self, parent, mList, full=True):
         qt.QDialog.__init__(self, parent)
-        self.setWindowTitle('XLD/XMCD Options')
+        self.setWindowTitle("XLD/XMCD Options")
         self.setModal(True)
         self.motorList = mList
         self.saved = False
 
         # Buttons
-        buttonOK = qt.QPushButton('OK')
-        buttonOK.setToolTip('Accept the configuration')
-        buttonCancel = qt.QPushButton('Cancel')
-        buttonCancel.setToolTip('Return to XMCD Analysis\nwithout changes')
+        buttonOK = qt.QPushButton("OK")
+        buttonOK.setToolTip("Accept the configuration")
+        buttonCancel = qt.QPushButton("Cancel")
+        buttonCancel.setToolTip("Return to XMCD Analysis\nwithout changes")
         if full:
-            buttonSave = qt.QPushButton('Save')
-            buttonSave.setToolTip('Save configuration to *.cfg-File')
-        buttonLoad = qt.QPushButton('Load')
-        buttonLoad.setToolTip('Load existing configuration from *.cfg-File')
+            buttonSave = qt.QPushButton("Save")
+            buttonSave.setToolTip("Save configuration to *.cfg-File")
+        buttonLoad = qt.QPushButton("Load")
+        buttonLoad.setToolTip("Load existing configuration from *.cfg-File")
 
         # OptionLists and ButtonGroups
         # GroupBox can be generated from self.getGroupBox
-        normOpts = ['No &normalization',
-                    'Normalize &after average',
-                    'Normalize &before average']
-        xrangeOpts = ['&First curve in sequence',
-                      'Active &curve',
-                      '&Use equidistant x-range']
+        normOpts = [
+            "No &normalization",
+            "Normalize &after average",
+            "Normalize &before average",
+        ]
+        xrangeOpts = [
+            "&First curve in sequence",
+            "Active &curve",
+            "&Use equidistant x-range",
+        ]
         # ButtonGroups
-        normBG   = qt.QButtonGroup(self)
+        normBG = qt.QButtonGroup(self)
         xrangeBG = qt.QButtonGroup(self)
         # ComboBoxes
         normMeth = qt.QComboBox()
-        normMeth.addItems(['(y-min(y))/trapz(max(y)-min(y),x)',
-                           'y/max(y)',
-                           '(y-min(y))/(max(y)-min(y))',
-                           '(y-min(y))/sum(max(y)-min(y))'])
+        normMeth.addItems(
+            [
+                "(y-min(y))/trapz(max(y)-min(y),x)",
+                "y/max(y)",
+                "(y-min(y))/(max(y)-min(y))",
+                "(y-min(y))/sum(max(y)-min(y))",
+            ]
+        )
         normMeth.setEnabled(False)
         self.optsDict = {
-            'normalization' : normBG,
-            'normalizationMethod' : normMeth,
-            'xrange' : xrangeBG
+            "normalization": normBG,
+            "normalizationMethod": normMeth,
+            "xrange": xrangeBG,
         }
         for idx in range(5):
             # key: motor0, motor1, ...
-            key = 'motor%d'%idx
+            key = "motor%d" % idx
             tmp = qt.QComboBox()
             tmp.addItems(mList)
             self.optsDict[key] = tmp
         # Subdivide into GroupBoxes
-        normGroupBox = self.getGroupBox('Normalization',
-                                        normOpts,
-                                        normBG)
-        xrangeGroupBox = self.getGroupBox('Interpolation x-range',
-                                          xrangeOpts,
-                                          xrangeBG)
-        motorGroupBox = qt.QGroupBox('Motors')
+        normGroupBox = self.getGroupBox("Normalization", normOpts, normBG)
+        xrangeGroupBox = self.getGroupBox("Interpolation x-range", xrangeOpts, xrangeBG)
+        motorGroupBox = qt.QGroupBox("Motors")
 
         # Layouts
         mainLayout = qt.QVBoxLayout()
         buttonLayout = qt.QHBoxLayout()
         normLayout = qt.QHBoxLayout()
         motorLayout = qt.QGridLayout()
-        if full: buttonLayout.addWidget(buttonSave)
+        if full:
+            buttonLayout.addWidget(buttonSave)
         buttonLayout.addWidget(buttonLoad)
         buttonLayout.addWidget(qt.HorizontalSpacer())
         buttonLayout.addWidget(buttonOK)
         buttonLayout.addWidget(buttonCancel)
-        normLayout.addWidget(qt.QLabel('Method:'))
+        normLayout.addWidget(qt.QLabel("Method:"))
         normLayout.addWidget(normMeth)
         for idx in range(5):
-            label = qt.QLabel('Motor %d:'%(idx+1))
-            cbox  = self.optsDict['motor%d'%idx]
-            motorLayout.addWidget(label,idx,0)
-            motorLayout.addWidget(cbox,idx,1)
+            label = qt.QLabel("Motor %d:" % (idx + 1))
+            cbox = self.optsDict["motor%d" % idx]
+            motorLayout.addWidget(label, idx, 0)
+            motorLayout.addWidget(cbox, idx, 1)
         motorGroupBox.setLayout(motorLayout)
         normGroupBox.layout().addLayout(normLayout)
         mainLayout.addWidget(normGroupBox)
@@ -180,8 +186,8 @@ class XMCDOptions(qt.QDialog):
         qt.QDialog.showEvent(self, event)
 
     def updateMotorList(self, mList):
-        for (key, obj) in self.optsDict.items():
-            if key.startswith('motor') and isinstance(obj, qt.QComboBox):
+        for key, obj in self.optsDict.items():
+            if key.startswith("motor") and isinstance(obj, qt.QComboBox):
                 curr = obj.currentText()
                 obj.clear()
                 obj.addItems(mList)
@@ -209,7 +215,7 @@ class XMCDOptions(qt.QDialog):
         groupBox = qt.QGroupBox(title, None)
         gbLayout = qt.QVBoxLayout(None)
         gbLayout.addStretch(1)
-        for (idx, radioText) in enumerate(optionList):
+        for idx, radioText in enumerate(optionList):
             radio = qt.QRadioButton(radioText)
             gbLayout.addWidget(radio)
             if buttongroup:
@@ -223,12 +229,12 @@ class XMCDOptions(qt.QDialog):
     def normalizationMethod(self, ident):
         ret = None
         normDict = {
-            'toMaximum'       : r'y/max(y)',
-            'offsetAndMaximum': r'(y-min(y))/(max(y)-min(y))',
-            'offsetAndCounts' : r'(y-min(y))/sum(max(y)-min(y))',
-            'offsetAndArea'   : r'(y-min(y))/trapz(max(y)-min(y),x)'
+            "toMaximum": r"y/max(y)",
+            "offsetAndMaximum": r"(y-min(y))/(max(y)-min(y))",
+            "offsetAndCounts": r"(y-min(y))/sum(max(y)-min(y))",
+            "offsetAndArea": r"(y-min(y))/trapz(max(y)-min(y),x)",
         }
-        for (name, eq) in normDict.items():
+        for name, eq in normDict.items():
             if ident == name:
                 return eq
             if ident == eq:
@@ -249,15 +255,16 @@ class XMCDOptions(qt.QDialog):
 
     def saveOptions(self, filename=None):
         saveDir = PyMcaDirs.outputDir
-        filter = ['PyMca (*.cfg)']
+        filter = ["PyMca (*.cfg)"]
         if filename is None:
             try:
-                filename = PyMcaFileDialogs.\
-                            getFileList(parent=self,
-                                filetypelist=filter,
-                                message='Save XLD/XMCD Analysis Configuration',
-                                mode='SAVE',
-                                single=True)[0]
+                filename = PyMcaFileDialogs.getFileList(
+                    parent=self,
+                    filetypelist=filter,
+                    message="Save XLD/XMCD Analysis Configuration",
+                    mode="SAVE",
+                    single=True,
+                )[0]
             except IndexError:
                 # Returned list is empty
                 return
@@ -265,20 +272,20 @@ class XMCDOptions(qt.QDialog):
         if len(filename) == 0:
             self.saved = False
             return False
-        if not str(filename).endswith('.cfg'):
-            filename += '.cfg'
+        if not str(filename).endswith(".cfg"):
+            filename += ".cfg"
         confDict = ConfigDict.ConfigDict()
         tmp = self.getOptions()
-        for (key, value) in tmp.items():
-            if key.startswith('Motor') and len(value) == 0:
-                tmp[key] = 'None'
-        confDict['XMCDOptions'] = tmp
+        for key, value in tmp.items():
+            if key.startswith("Motor") and len(value) == 0:
+                tmp[key] = "None"
+        confDict["XMCDOptions"] = tmp
         try:
             confDict.write(filename)
         except IOError:
             msg = qt.QMessageBox()
-            msg.setWindowTitle('XLD/XMCD Options Error')
-            msg.setText('Unable to write configuration to \'%s\''%filename)
+            msg.setWindowTitle("XLD/XMCD Options Error")
+            msg.setText("Unable to write configuration to '%s'" % filename)
             msg.exec()
         self.saved = True
         return True
@@ -288,74 +295,76 @@ class XMCDOptions(qt.QDialog):
 
     def loadOptions(self):
         openDir = PyMcaDirs.outputDir
-        ffilter = 'PyMca (*.cfg)'
-        filename = qt.QFileDialog.\
-                    getOpenFileName(self,
-                                    'Load XLD/XMCD Analysis Configuration',
-                                    openDir,
-                                    ffilter)
+        ffilter = "PyMca (*.cfg)"
+        filename = qt.QFileDialog.getOpenFileName(
+            self, "Load XLD/XMCD Analysis Configuration", openDir, ffilter
+        )
         confDict = ConfigDict.ConfigDict()
         try:
             confDict.read(filename)
         except IOError:
             msg = qt.QMessageBox()
-            msg.setTitle('XMCD Options Error')
-            msg.setText('Unable to read configuration file \'%s\''%filename)
+            msg.setTitle("XMCD Options Error")
+            msg.setText("Unable to read configuration file '%s'" % filename)
             return
-        if 'XMCDOptions'not in confDict:
+        if "XMCDOptions" not in confDict:
             return
         try:
-            self.setOptions(confDict['XMCDOptions'])
+            self.setOptions(confDict["XMCDOptions"])
         except ValueError as e:
-            _logger.debug('loadOptions -- int conversion failed:\n'
-                          'Invalid value for option \'%s\'', e)
+            _logger.debug(
+                "loadOptions -- int conversion failed:\n"
+                "Invalid value for option '%s'",
+                e,
+            )
             msg = qt.QMessageBox()
-            msg.setWindowTitle('XMCD Options Error')
-            msg.setText('Configuration file \'%s\' corruted' % filename)
+            msg.setWindowTitle("XMCD Options Error")
+            msg.setText("Configuration file '%s' corruted" % filename)
             msg.exec()
             return
         except KeyError as e:
-            _logger.debug('loadOptions -- invalid identifier:\n'
-                          'option \'%s\' not found', e)
+            _logger.debug(
+                "loadOptions -- invalid identifier:\n" "option '%s' not found", e
+            )
 
             msg = qt.QMessageBox()
-            msg.setWindowTitle('XMCD Options Error')
-            msg.setText('Configuration file \'%s\' corruted' % filename)
+            msg.setWindowTitle("XMCD Options Error")
+            msg.setText("Configuration file '%s' corruted" % filename)
             msg.exec()
             return
         self.saved = True
 
     def getOptions(self):
         ddict = {}
-        for (option, obj) in self.optsDict.items():
+        for option, obj in self.optsDict.items():
             if isinstance(obj, qt.QButtonGroup):
                 ddict[option] = obj.checkedId()
             elif isinstance(obj, qt.QComboBox):
                 tmp = str(obj.currentText())
-                if option == 'normalizationMethod':
+                if option == "normalizationMethod":
                     tmp = self.normalizationMethod(tmp)
-                if option.startswith('motor') and (not len(tmp)):
-                    tmp = 'None'
+                if option.startswith("motor") and (not len(tmp)):
+                    tmp = "None"
                 ddict[option] = tmp
             else:
-                ddict[option] = 'None'
+                ddict[option] = "None"
         return ddict
 
     def getMotors(self):
-        motors = sorted([key for key in self.optsDict.keys()\
-                         if key.startswith('motor')])
-        return [str(self.optsDict[motor].currentText()) \
-                for motor in motors]
+        motors = sorted(
+            [key for key in self.optsDict.keys() if key.startswith("motor")]
+        )
+        return [str(self.optsDict[motor].currentText()) for motor in motors]
 
     def setOptions(self, ddict):
         for option in ddict.keys():
             obj = self.optsDict[option]
             if isinstance(obj, qt.QComboBox):
                 name = ddict[option]
-                if option == 'normalizationMethod':
+                if option == "normalizationMethod":
                     name = self.normalizationMethod(name)
-                if option.startswith('Motor') and name == 'None':
-                    name = ''
+                if option.startswith("Motor") and name == "None":
+                    name = ""
                 idx = obj.findText(QString(name))
                 obj.setCurrentIndex(idx)
             elif isinstance(obj, qt.QButtonGroup):
@@ -365,60 +374,60 @@ class XMCDOptions(qt.QDialog):
                     raise ValueError(option)
                 button = self.optsDict[option].button(idx)
                 if type(button) == type(qt.QRadioButton()):
-                        button.setChecked(True)
+                    button.setChecked(True)
 
-class XMCDScanWindow(ScanWindow.ScanWindow):
 
+class XMCDScanWindow(ScanWindow.ScanWindow):
     xmcdToolbarOptions = {
-        'logx': False,
-        'logy': False,
-        'flip': False,
-        'fit': False,
-        'roi': False,
+        "logx": False,
+        "logy": False,
+        "flip": False,
+        "fit": False,
+        "roi": False,
     }
 
     plotModifiedSignal = qt.pyqtSignal()
-    saveOptionsSignal  = qt.pyqtSignal('QString')
+    saveOptionsSignal = qt.pyqtSignal("QString")
 
-    def __init__(self,
-                 origin,
-                 parent=None):
+    def __init__(self, origin, parent=None):
         """
         :param origin: Plot window containing the data on which the analysis is performed
         :type origin: ScanWindow
         :param parent: Parent Widget, None per default
         :type parent: QWidget
         """
-        ScanWindow.ScanWindow.__init__(self,
-                               parent,
-                               name='XLD/XMCD Analysis',
-                               specfit=None,
-                               plugins=False,
-                               newplot=False,
-                               **self.xmcdToolbarOptions)
-        if hasattr(self, 'pluginsIconFlag'):
+        ScanWindow.ScanWindow.__init__(
+            self,
+            parent,
+            name="XLD/XMCD Analysis",
+            specfit=None,
+            plugins=False,
+            newplot=False,
+            **self.xmcdToolbarOptions
+        )
+        if hasattr(self, "pluginsIconFlag"):
             self.pluginsIconFlag = False
         self.plotWindow = origin
-        if hasattr(self, 'scanWindowInfoWidget'):
+        if hasattr(self, "scanWindowInfoWidget"):
             if self.scanWindowInfoWidget:
                 self.scanWindowInfoWidget.hide()
 
         # Buttons to push spectra to main Window
         buttonWidget = qt.QWidget()
-        buttonAdd = qt.QPushButton('Add', self)
-        buttonAdd.setToolTip('Add active curve to main window')
-        buttonReplace = qt.QPushButton('Replace', self)
+        buttonAdd = qt.QPushButton("Add", self)
+        buttonAdd.setToolTip("Add active curve to main window")
+        buttonReplace = qt.QPushButton("Replace", self)
         buttonReplace.setToolTip(
-            'Replace all curves in main window '
-           +'with active curve in analysis window')
-        buttonAddAll = qt.QPushButton('Add all', self)
-        buttonAddAll.setToolTip(
-            'Add all curves in analysis window '
-           +'to main window')
-        buttonReplaceAll = qt.QPushButton('Replace all', self)
+            "Replace all curves in main window "
+            + "with active curve in analysis window"
+        )
+        buttonAddAll = qt.QPushButton("Add all", self)
+        buttonAddAll.setToolTip("Add all curves in analysis window " + "to main window")
+        buttonReplaceAll = qt.QPushButton("Replace all", self)
         buttonReplaceAll.setToolTip(
-            'Replace all curves in main window '
-           +'with all curves from analysis window')
+            "Replace all curves in main window "
+            + "with all curves from analysis window"
+        )
         self.graphBottomLayout.addWidget(qt.HorizontalSpacer())
         self.graphBottomLayout.addWidget(buttonAdd)
         self.graphBottomLayout.addWidget(buttonAddAll)
@@ -431,14 +440,14 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         buttonReplaceAll.clicked.connect(self.replaceAll)
 
         # Copy spectra from origin
-        self.selectionDict = {'A':[], 'B':[]}
+        self.selectionDict = {"A": [], "B": []}
         self.curvesDict = {}
         self.optsDict = {
-            'normAfterAvg'  : False,
-            'normBeforeAvg' : False,
-            'useActive'     : False,
-            'equidistant'   : False,
-            'normalizationMethod' : self.NormOffsetAndArea
+            "normAfterAvg": False,
+            "normBeforeAvg": False,
+            "useActive": False,
+            "equidistant": False,
+            "normalizationMethod": self.NormOffsetAndArea,
         }
         self.xRange = None
 
@@ -446,84 +455,82 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         self.avgA = None
         self.avgB = None
         self.xmcd = None
-        self.xas  = None
+        self.xas = None
 
-        if hasattr(self, '_buildLegendWidget'):
+        if hasattr(self, "_buildLegendWidget"):
             self._buildLegendWidget()
 
     def sizeHint(self):
         if self.parent():
-            height = .5 * self.parent().height()
+            height = 0.5 * self.parent().height()
         else:
             height = self.height()
         return qt.QSize(self.width(), int(height))
 
     def processOptions(self, options):
-        tmp = { 'equidistant': False,
-                'useActive': False,
-                'normAfterAvg': False,
-                'normBeforeAvg': False,
-                'normalizationMethod': None
+        tmp = {
+            "equidistant": False,
+            "useActive": False,
+            "normAfterAvg": False,
+            "normBeforeAvg": False,
+            "normalizationMethod": None,
         }
-        xRange = options['xrange']
-        normalization = options['normalization']
-        normMethod = options['normalizationMethod']
+        xRange = options["xrange"]
+        normalization = options["normalization"]
+        normMethod = options["normalizationMethod"]
         # xRange Options. Default: Use first scan
         if xRange == 1:
-            tmp['useActive']   = True
+            tmp["useActive"] = True
         elif xRange == 2:
-            tmp['equidistant'] = True
+            tmp["equidistant"] = True
         # Normalization Options. Default: No Normalization
         if normalization == 1:
-            tmp['normAfterAvg']  = True
+            tmp["normAfterAvg"] = True
         elif normalization == 2:
-            tmp['normBeforeAvg'] = True
+            tmp["normBeforeAvg"] = True
         # Normalization Method. Default: offsetAndArea
-        tmp['normalizationMethod'] = self.setNormalizationMethod(normMethod)
+        tmp["normalizationMethod"] = self.setNormalizationMethod(normMethod)
         # Trigger reclaculation
         self.optsDict = tmp
-        groupA = self.selectionDict['A']
-        groupB = self.selectionDict['B']
+        groupA = self.selectionDict["A"]
+        groupB = self.selectionDict["B"]
         self.processSelection(groupA, groupB)
 
     def setNormalizationMethod(self, fname):
-        if fname == 'toMaximum':
+        if fname == "toMaximum":
             func = self.NormToMaximum
-        elif fname == 'offsetAndMaximum':
+        elif fname == "offsetAndMaximum":
             func = self.NormToOffsetAndMaximum
-        elif fname == 'offsetAndCounts':
+        elif fname == "offsetAndCounts":
             func = self.NormOffsetAndCounts
         else:
             func = self.NormOffsetAndArea
         return func
 
-    def NormToMaximum(self,x,y):
-        ymax  = numpy.max(y)
-        ynorm = y/ymax
+    def NormToMaximum(self, x, y):
+        ymax = numpy.max(y)
+        ynorm = y / ymax
         return ynorm
 
-    def NormToOffsetAndMaximum(self,x,y):
+    def NormToOffsetAndMaximum(self, x, y):
         ynorm = y - numpy.min(y)
-        ymax  = numpy.max(ynorm)
+        ymax = numpy.max(ynorm)
         ynorm /= ymax
         return ynorm
 
     def NormOffsetAndCounts(self, x, y):
         ynorm = y - numpy.min(y)
-        ymax  = numpy.sum(ynorm)
+        ymax = numpy.sum(ynorm)
         ynorm /= ymax
         return ynorm
 
-    def NormOffsetAndArea(self,  x, y):
+    def NormOffsetAndArea(self, x, y):
         ynorm = y - numpy.min(y)
-        ymax  = numpy.trapz(ynorm,  x)
+        ymax = numpy.trapz(ynorm, x)
         ynorm /= ymax
         return ynorm
 
-    def interpXRange(self,
-                     xRange=None,
-                     equidistant=False,
-                     xRangeList=None):
+    def interpXRange(self, xRange=None, equidistant=False, xRangeList=None):
         """
         Input
         -----
@@ -551,7 +558,7 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             keys = sorted(self.curvesDict.keys())
             xRangeList = [self.curvesDict[k].x[0] for k in keys]
         if not len(xRangeList):
-            _logger.debug('interpXRange -- Nothing to do')
+            _logger.debug("interpXRange -- Nothing to do")
             return None
 
         num = 0
@@ -562,14 +569,13 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             if x.max() < xmax:
                 xmax = x.max()
         if xmin >= xmax:
-            raise ValueError('No overlap between curves')
+            raise ValueError("No overlap between curves")
             pass
 
         if equidistant:
             for x in xRangeList:
-                curr = numpy.nonzero((x >= xmin) &
-                                     (x <= xmax))[0].size
-                num = curr if curr>num else num
+                curr = numpy.nonzero((x >= xmin) & (x <= xmax))[0].size
+                num = curr if curr > num else num
             num = int(num)
             # Exclude first and last point
             out = numpy.linspace(xmin, xmax, num, endpoint=False)[1:]
@@ -579,17 +585,16 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             else:
                 x = xRangeList[0]
             # Ensure monotonically increasing x-range
-            if not numpy.all(numpy.diff(x)>0.):
-                mask = numpy.nonzero(numpy.diff(x)>0.)[0]
+            if not numpy.all(numpy.diff(x) > 0.0):
+                mask = numpy.nonzero(numpy.diff(x) > 0.0)[0]
                 x = numpy.take(x, mask)
             # Exclude the endpoints
-            mask = numpy.nonzero((x > xmin) &
-                                 (x < xmax))[0]
+            mask = numpy.nonzero((x > xmin) & (x < xmax))[0]
             out = numpy.sort(numpy.take(x, mask))
-        _logger.debug('interpXRange -- Resulting xrange:')
-        _logger.debug('\tmin = %f', out.min())
-        _logger.debug('\tmax = %f', out.max())
-        _logger.debug('\tnum = %f', len(out))
+        _logger.debug("interpXRange -- Resulting xrange:")
+        _logger.debug("\tmin = %f", out.min())
+        _logger.debug("\tmax = %f", out.max())
+        _logger.debug("\tnum = %f", len(out))
         return out
 
     def processSelection(self, groupA, groupB):
@@ -604,35 +609,35 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         all = self.getAllCurves(just_legend=True)
         self.removeCurves(all)
         self.avgB, self.avgA = None, None
-        self.xas, self.xmcd  = None, None
+        self.xas, self.xmcd = None, None
 
-        self.selectionDict['A'] = groupA[:]
-        self.selectionDict['B'] = groupB[:]
+        self.selectionDict["A"] = groupA[:]
+        self.selectionDict["B"] = groupB[:]
         self.curvesDict = self.copyCurves(groupA + groupB)
 
-        if (len(self.curvesDict) == 0) or\
-                ((len(self.selectionDict['A']) == 0) and
-                 (len(self.selectionDict['B']) == 0)):
+        if (len(self.curvesDict) == 0) or (
+            (len(self.selectionDict["A"]) == 0) and (len(self.selectionDict["B"]) == 0)
+        ):
             # Nothing to do
             return
 
         # Make sure to use active curve when specified
-        if self.optsDict['useActive']:
+        if self.optsDict["useActive"]:
             # Get active curve
             active = self.plotWindow.getActiveCurve()
             if active:
-                _logger.debug('processSelection -- xrange: use active')
+                _logger.debug("processSelection -- xrange: use active")
                 x, y, leg, info = active[0:4]
                 xRange = self.interpXRange(xRange=x)
             else:
                 return
-        elif self.optsDict['equidistant']:
-            _logger.debug('processSelection -- xrange: use equidistant')
+        elif self.optsDict["equidistant"]:
+            _logger.debug("processSelection -- xrange: use equidistant")
             xRange = self.interpXRange(equidistant=True)
         else:
-            _logger.debug('processSelection -- xrange: use first')
+            _logger.debug("processSelection -- xrange: use first")
             xRange = self.interpXRange()
-        if hasattr(self.plotWindow, 'graph'):
+        if hasattr(self.plotWindow, "graph"):
             activeLegend = self.plotWindow.graph.getActiveCurve(justlegend=True)
         else:
             activeLegend = self.plotWindow.getActiveCurve(just_legend=True)
@@ -643,10 +648,10 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         xlabel, ylabel = self.extractLabels(active.info)
 
         # Calculate averages and add them to the plot
-        normalization = self.optsDict['normalizationMethod']
-        normBefore = self.optsDict['normBeforeAvg']
-        normAfter  = self.optsDict['normAfterAvg']
-        for idx in ['A','B']:
+        normalization = self.optsDict["normalizationMethod"]
+        normBefore = self.optsDict["normBeforeAvg"]
+        normAfter = self.optsDict["normAfterAvg"]
+        for idx in ["A", "B"]:
             sel = self.selectionDict[idx]
             if not len(sel):
                 continue
@@ -662,32 +667,33 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
                     yVals = tmp.y[0]
                 xvalList.append(xVals)
                 yvalList.append(yVals)
-            avg_x, avg_y = self.specAverage(xvalList,
-                                            yvalList,
-                                            xRange)
+            avg_x, avg_y = self.specAverage(xvalList, yvalList, xRange)
             if normAfter:
                 avg_y = normalization(avg_x, avg_y)
-            avgName = 'avg_' + idx
-            #info = {'xlabel': xlabel, 'ylabel': ylabel}
+            avgName = "avg_" + idx
+            # info = {'xlabel': xlabel, 'ylabel': ylabel}
             info = {}
-            if idx == 'A':
-                #info.update({'plot_color':'red'})
-                color="red"
+            if idx == "A":
+                # info.update({'plot_color':'red'})
+                color = "red"
             else:
-                #info.update({'plot_color':'blue'})
-                color="blue"
-            self.addCurve(avg_x, avg_y,
-                          legend=avgName,
-                          info=info,
-                          xlabel=xlabel,
-                          ylabel=ylabel,
-                          color=color)
-            if idx == 'A':
+                # info.update({'plot_color':'blue'})
+                color = "blue"
+            self.addCurve(
+                avg_x,
+                avg_y,
+                legend=avgName,
+                info=info,
+                xlabel=xlabel,
+                ylabel=ylabel,
+                color=color,
+            )
+            if idx == "A":
                 self.avgA = self.dataObjectsList[-1]
-            if idx == 'B':
+            if idx == "B":
                 self.avgB = self.dataObjectsList[-1]
 
-        if (self.avgA and self.avgB):
+        if self.avgA and self.avgB:
             self.performXMCD()
             self.performXAS()
 
@@ -717,11 +723,11 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             if tmp:
                 tmp = copy.deepcopy(tmp)
                 xarr, yarr = tmp.x, tmp.y
-                #if len(tmp.x) == len(tmp.y):
+                # if len(tmp.x) == len(tmp.y):
                 xprocArr, yprocArr = [], []
-                for (x,y) in zip(xarr,yarr):
+                for x, y in zip(xarr, yarr):
                     # Sort
-                    idx = numpy.argsort(x, kind='mergesort')
+                    idx = numpy.argsort(x, kind="mergesort")
                     xproc = numpy.take(x, idx)
                     yproc = numpy.take(y, idx)
                     # Ravel, Increase
@@ -762,10 +768,9 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             Average spectrum. In case of invalid input,
             (None, None) tuple is returned.
         """
-        if (len(xarr) != len(yarr)) or\
-           (len(xarr) == 0) or (len(yarr) == 0):
-            _logger.debug('specAverage -- invalid input!')
-            _logger.debug('Array lengths do not match or are 0')
+        if (len(xarr) != len(yarr)) or (len(xarr) == 0) or (len(yarr) == 0):
+            _logger.debug("specAverage -- invalid input!")
+            _logger.debug("Array lengths do not match or are 0")
             return None, None
 
         same = True
@@ -786,8 +791,8 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
 
         xsort = []
         ysort = []
-        for (x,y) in zip(xarr, yarr):
-            if numpy.all(numpy.diff(x) > 0.):
+        for x, y in zip(xarr, yarr):
+            if numpy.all(numpy.diff(x) > 0.0):
                 # All values sorted
                 xsort.append(x)
                 ysort.append(y)
@@ -813,41 +818,39 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
                 if xmax < xmax0:
                     xmax0 = xmax
             if xmax <= xmin:
-                _logger.debug('specAverage --\n'
-                              'No overlap between spectra!')
+                _logger.debug("specAverage --\n" "No overlap between spectra!")
                 return numpy.array([]), numpy.array([])
 
         # Clip xRange to maximal overlap in spectra
         if xRange is None:
             xRange = xsort[0]
-        mask = numpy.nonzero((xRange>=xmin0) &
-                             (xRange<=xmax0))[0]
+        mask = numpy.nonzero((xRange >= xmin0) & (xRange <= xmax0))[0]
         xnew = numpy.take(xRange, mask)
         ynew = numpy.zeros(len(xnew))
 
         # Perform average
-        for (x, y) in zip(xsort, ysort):
+        for x, y in zip(xsort, ysort):
             if same:
                 ynew += y
             else:
                 yinter = numpy.interp(xnew, x, y)
-                ynew   += numpy.asarray(yinter)
+                ynew += numpy.asarray(yinter)
         num = len(yarr)
         ynew /= num
         return xnew, ynew
 
     def extractLabels(self, info):
-        xlabel = 'X'
-        ylabel = 'Y'
-        sel = info.get('selection', None)
-        labelNames = info.get('LabelNames',[])
+        xlabel = "X"
+        ylabel = "Y"
+        sel = info.get("selection", None)
+        labelNames = info.get("LabelNames", [])
         if not len(labelNames):
             pass
         elif len(labelNames) == 2:
-                [xlabel, ylabel] = labelNames
+            [xlabel, ylabel] = labelNames
         elif sel:
-            xsel = sel.get('x',[])
-            ysel = sel.get('y',[])
+            xsel = sel.get("x", [])
+            ysel = sel.get("y", [])
             if len(xsel) > 0:
                 x = xsel[0]
                 xlabel = labelNames[x]
@@ -862,28 +865,29 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             a = self.dataObjectsDict[self.avgA]
             b = self.dataObjectsDict[self.avgB]
         else:
-            _logger.debug('performXAS -- Data not found: ')
-            _logger.debug('\tavg_m = %f', self.avgA)
-            _logger.debug('\tavg_p = %f', self.avgB)
+            _logger.debug("performXAS -- Data not found: ")
+            _logger.debug("\tavg_m = %f", self.avgA)
+            _logger.debug("\tavg_p = %f", self.avgB)
             return
-        if numpy.all( a.x[0] == b.x[0] ):
-            avg = .5*(b.y[0] + a.y[0])
+        if numpy.all(a.x[0] == b.x[0]):
+            avg = 0.5 * (b.y[0] + a.y[0])
         else:
-            _logger.debug('performXAS -- x ranges are not the same! ')
-            _logger.debug('Force interpolation')
-            avg = self.performAverage([a.x[0], b.x[0]],
-                                      [a.y[0], b.y[0]],
-                                       b.x[0])
-        xmcdLegend = 'XAS'
+            _logger.debug("performXAS -- x ranges are not the same! ")
+            _logger.debug("Force interpolation")
+            avg = self.performAverage([a.x[0], b.x[0]], [a.y[0], b.y[0]], b.x[0])
+        xmcdLegend = "XAS"
         xlabel, ylabel = self.extractLabels(a.info)
-        #info = {'xlabel': xlabel, 'ylabel': ylabel, 'plot_color': 'pink'}
+        # info = {'xlabel': xlabel, 'ylabel': ylabel, 'plot_color': 'pink'}
         info = {}
-        self.addCurve(a.x[0], avg,
-                      legend=xmcdLegend,
-                      info=info,
-                      xlabel=xlabel,
-                      ylabel=ylabel,
-                      color="pink")
+        self.addCurve(
+            a.x[0],
+            avg,
+            legend=xmcdLegend,
+            info=info,
+            xlabel=xlabel,
+            ylabel=ylabel,
+            color="pink",
+        )
         self.xas = self.dataObjectsList[-1]
 
     def performXMCD(self):
@@ -892,29 +896,32 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             a = self.dataObjectsDict[self.avgA]
             b = self.dataObjectsDict[self.avgB]
         else:
-            _logger.debug('performXMCD -- Data not found:')
+            _logger.debug("performXMCD -- Data not found:")
             return
-        if numpy.all( a.x[0] == b.x[0] ):
+        if numpy.all(a.x[0] == b.x[0]):
             diff = b.y[0] - a.y[0]
         else:
-            _logger.debug('performXMCD -- x ranges are not the same! ')
-            _logger.debug('Force interpolation using p Average xrange')
+            _logger.debug("performXMCD -- x ranges are not the same! ")
+            _logger.debug("Force interpolation using p Average xrange")
             # Use performAverage d = 2 * avg(y1, -y2)
             # and force interpolation on p-xrange
-            diff = 2. * self.performAverage([a.x[0], b.x[0]],
-                                            [-a.y[0], b.y[0]],
-                                            b.x[0])
-        xmcdLegend = 'XMCD'
+            diff = 2.0 * self.performAverage(
+                [a.x[0], b.x[0]], [-a.y[0], b.y[0]], b.x[0]
+            )
+        xmcdLegend = "XMCD"
         xlabel, ylabel = self.extractLabels(a.info)
-        #info = {'xlabel': xlabel, 'ylabel': ylabel, 'plot_yaxis': 'right', 'plot_color': 'green'}
-        info={}
-        self.addCurve(b.x[0], diff,
-                      legend=xmcdLegend,
-                      info=info,
-                      color="green",
-                      xlabel=xlabel,
-                      ylabel=ylabel,
-                      yaxis="right")
+        # info = {'xlabel': xlabel, 'ylabel': ylabel, 'plot_yaxis': 'right', 'plot_color': 'green'}
+        info = {}
+        self.addCurve(
+            b.x[0],
+            diff,
+            legend=xmcdLegend,
+            info=info,
+            color="green",
+            xlabel=xlabel,
+            ylabel=ylabel,
+            yaxis="right",
+        )
         # DELETE ME self.graph.mapToY2(' '.join([xmcdLegend, ylabel]))
         self._zoomReset()
         self.xmcd = self.dataObjectsList[-1]
@@ -926,22 +933,24 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         stored selectionDict.
         """
         sel = self.selectionDict[idx]
-        ret = '%s: '%idx
+        ret = "%s: " % idx
         for legend in sel:
             curr = self.curvesDict[legend]
             value = curr.info.get(key, None)
             if value:
-                ret = ' '.join([ret, value])
+                ret = " ".join([ret, value])
         return ret
 
-    def _saveIconSignal(self):
+    def _saveIconSignalReplacement(self, motors=None):
         saveDir = PyMcaDirs.outputDir
-        filter = 'spec File (*.spec);;Any File (*.*)'
+        filter = "spec File (*.spec);;Any File (*.*)"
         try:
-            (filelist, append, comment) = getSaveFileName(parent=self,
-                                                          caption='Save XMCD Analysis',
-                                                          filter=filter,
-                                                          directory=saveDir)
+            (filelist, append, comment) = getSaveFileName(
+                parent=self,
+                caption="Save XMCD Analysis",
+                filter=filter,
+                directory=saveDir,
+            )
             filename = filelist[0]
         except IndexError:
             # Returned list is empty
@@ -951,60 +960,60 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             return
 
         if append:
-            specf  = specfile.Specfile(filename)
+            specf = specfile.Specfile(filename)
             scanno = specf.scanno() + 1
         else:
             scanno = 1
 
         ext = splitext(filename)[1]
         if not len(ext):
-            ext = '.spec'
+            ext = ".spec"
             filename += ext
         try:
             if append:
                 sepFile = splitext(basename(filename))
-                sepFileName = sepFile[0] + '_%.2d'%scanno + sepFile[1]
-                sepFileName = pathjoin(dirname(filename),sepFileName)
+                sepFileName = sepFile[0] + "_%.2d" % scanno + sepFile[1]
+                sepFileName = pathjoin(dirname(filename), sepFileName)
                 if scanno == 2:
                     # Case: Scan appended to file containing
                     # a single scan. Make sure, that the first
-                    # scan is also written to seperate file and
+                    # scan is also written to separate file and
                     # the corresponding cfg-file is copied
                     # 1. Create filename of first scan
-                    sepFirstFileName = sepFile[0] + '_01' + sepFile[1]
-                    sepFirstFileName = pathjoin(dirname(filename),sepFirstFileName)
+                    sepFirstFileName = sepFile[0] + "_01" + sepFile[1]
+                    sepFirstFileName = pathjoin(dirname(filename), sepFirstFileName)
                     # 2. Guess filename of first config
-                    confname = sepFile[0] + '.cfg'
-                    confname = pathjoin(dirname(filename),confname)
+                    confname = sepFile[0] + ".cfg"
+                    confname = pathjoin(dirname(filename), confname)
                     # 3. Create new filename of first config
-                    sepFirstConfName = sepFile[0] + '_01' + '.cfg'
-                    sepFirstConfName = pathjoin(dirname(filename),sepFirstConfName)
+                    sepFirstConfName = sepFile[0] + "_01" + ".cfg"
+                    sepFirstConfName = pathjoin(dirname(filename), sepFirstConfName)
                     # Copy contents
-                    firstSeperateFile = open(sepFirstFileName, 'wb')
-                    firstSeperateConf = open(sepFirstConfName, 'wb')
-                    filehandle = open(filename, 'rb')
-                    confhandle = open(confname, 'rb')
+                    firstSeparateFile = open(sepFirstFileName, "wb")
+                    firstSeparateConf = open(sepFirstConfName, "wb")
+                    filehandle = open(filename, "rb")
+                    confhandle = open(confname, "rb")
                     firstFile = filehandle.read()
                     firstConf = confhandle.read()
-                    firstSeperateFile.write(firstFile)
-                    firstSeperateConf.write(firstConf)
-                    firstSeperateFile.close()
-                    firstSeperateConf.close()
-                filehandle = open(filename, 'ab')
-                seperateFile = open(sepFileName, 'wb')
+                    firstSeparateFile.write(firstFile)
+                    firstSeparateConf.write(firstConf)
+                    firstSeparateFile.close()
+                    firstSeparateConf.close()
+                filehandle = open(filename, "ab")
+                separateFile = open(sepFileName, "wb")
             else:
-                filehandle = open(filename, 'wb')
-                seperateFile = None
+                filehandle = open(filename, "wb")
+                separateFile = None
         except IOError:
-            msg = qt.QMessageBox(text="Unable to open '%s'"%filename)
+            msg = qt.QMessageBox(text="Unable to open '%s'" % filename)
             msg.exec()
             return
 
-        title = ''
+        title = ""
         legends = self.dataObjectsList
         tmpLegs = sorted(self.curvesDict.keys())
         if len(tmpLegs) > 0:
-            title += self.curvesDict[tmpLegs[0]].info.get('selectionlegend','')
+            title += self.curvesDict[tmpLegs[0]].info.get("selectionlegend", "")
             # Keep plots in the order they were added!
             curves = [self.dataObjectsDict[leg] for leg in legends]
             yVals = [curve.y[0] for curve in curves]
@@ -1020,52 +1029,83 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
             ncols = outArray.shape[1]
         else:
             ncols = 1
-        delim = ' '
-        title = 'XMCD Analysis ' + title
-        header  = '#S %d %s'%(scanno,title) + NEWLINE
-        header += ('#U00 Selected in Group ' +\
-                   self.selectionInfo('A', 'Key') + NEWLINE)
-        header += ('#U01 Selected in Group ' +\
-                   self.selectionInfo('B', 'Key') + NEWLINE)
+        delim = " "
+        title = "XMCD Analysis " + title
+        header = "#S %d %s" % (scanno, title) + NEWLINE
+        header += "#U00 Selected in Group " + self.selectionInfo("A", "Key") + NEWLINE
+        header += "#U01 Selected in Group " + self.selectionInfo("B", "Key") + NEWLINE
         # Write Comments
         if len(comment) > 0:
-            header += ('#U02 User commentary:' + NEWLINE)
+            header += "#U02 User commentary:" + NEWLINE
             lines = comment.splitlines()[:97]
-            for (idx, line) in enumerate(lines):
-                header += ('#U%.2d %s'%(idx+3, line) + NEWLINE)
-        header += '#N %d'%ncols + NEWLINE
-        if ext == '.spec':
-            if hasattr(self, 'getGraphXLabel'):
-                header += ('#L ' + self.getGraphXLabel() + '  ' + '  '.join(legends) + NEWLINE)
+            for idx, line in enumerate(lines):
+                header += "#U%.2d %s" % (idx + 3, line) + NEWLINE
+
+        # Write motor mnemonics and positions of first selected scan
+        if isinstance(motors, dict):
+            if len(motors):
+                motorNames = [x for x in motors]
+                motorValues = [motors[x] for x in motors]
+                motorNamesText = ""
+                motorValuesText = ""
+                motorsPerLine = 5
+                n = 0
+                while n < len(motors):
+                    idx = n // motorsPerLine
+                    motorNamesText += "#O%d" % idx
+                    motorValuesText += "#P%d" % idx
+                    for i in range(
+                        idx * motorsPerLine, min((idx + 1) * motorsPerLine, len(motors))
+                    ):
+                        motorNamesText += "  " + motorNames[i]
+                        motorValuesText += "  " + motorValues[i]
+                        n += 1
+                    motorNamesText += NEWLINE
+                    motorValuesText += NEWLINE
+                header += motorNamesText
+                header += motorValuesText
+
+        header += "#N %d" % ncols + NEWLINE
+        if ext == ".spec":
+            if hasattr(self, "getGraphXLabel"):
+                header += (
+                    "#L " + self.getGraphXLabel() + "  " + "  ".join(legends) + NEWLINE
+                )
             else:
-                header += ('#L ' + self.getGraphXTitle() + '  ' + '  '.join(legends) + NEWLINE)
+                header += (
+                    "#L " + self.getGraphXTitle() + "  " + "  ".join(legends) + NEWLINE
+                )
         else:
-            if hasattr(self, 'getGraphXLabel'):
-                header += ('#L ' + self.getGraphXLabel() + '  ' + '  '.join(legends) + NEWLINE)
+            if hasattr(self, "getGraphXLabel"):
+                header += (
+                    "#L " + self.getGraphXLabel() + "  " + "  ".join(legends) + NEWLINE
+                )
             else:
-                header += ('#L ' + self.getGraphXTitle() + '  ' + delim.join(legends) + NEWLINE)
+                header += (
+                    "#L " + self.getGraphXTitle() + "  " + delim.join(legends) + NEWLINE
+                )
 
-        for fh in [filehandle, seperateFile]:
+        for fh in [filehandle, separateFile]:
             if fh is not None:
                 if sys.version < "3.0":
                     fh.write(bytes(NEWLINE))
                     fh.write(bytes(header))
                     for line in outArray:
-                        tmp = delim.join(['%f'%num for num in line])
+                        tmp = delim.join(["%f" % num for num in line])
                         fh.write(bytes(tmp + NEWLINE))
                     fh.write(bytes(NEWLINE))
                 else:
-                    fh.write(bytes(NEWLINE, 'ascii'))
-                    fh.write(bytes(header, 'ascii'))
+                    fh.write(bytes(NEWLINE, "ascii"))
+                    fh.write(bytes(header, "ascii"))
                     for line in outArray:
-                        tmp = delim.join(['%f'%num for num in line])
-                        fh.write(bytes(tmp + NEWLINE, 'ascii'))
-                    fh.write(bytes(NEWLINE, 'ascii'))
+                        tmp = delim.join(["%f" % num for num in line])
+                        fh.write(bytes(tmp + NEWLINE, "ascii"))
+                    fh.write(bytes(NEWLINE, "ascii"))
                 fh.close()
 
         # Emit saveOptionsSignal to save config file
         self.saveOptionsSignal.emit(splitext(filename)[0])
-        if seperateFile is not None:
+        if separateFile is not None:
             self.saveOptionsSignal.emit(splitext(sepFileName)[0])
 
     def add(self):
@@ -1074,34 +1114,28 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         activeCurve = self.getActiveCurve()
         if activeCurve is None:
             return
-        (xVal,  yVal,  legend,  info) = activeCurve[0:4]
-        #if 'selectionlegend' in info:
+        (xVal, yVal, legend, info) = activeCurve[0:4]
+        # if 'selectionlegend' in info:
         #    newLegend = info['selectionlegend']
-        #elif 'operation' in info:
+        # elif 'operation' in info:
         #    newLegend = (str(operation) + ' ' + self.title)
-        #else:
+        # else:
         #    newLegend = (legend + ' ' + self.title)
         newLegend = legend
-        self.plotWindow.addCurve(xVal,
-                                 yVal,
-                                 legend=newLegend,
-                                 info=info)
+        self.plotWindow.addCurve(xVal, yVal, legend=newLegend, info=info)
         self.plotModifiedSignal.emit()
 
     def addAll(self):
         for curve in self.getAllCurves():
             (xVal, yVal, legend, info) = curve[0:4]
-            #if 'selectionlegend' in info:
+            # if 'selectionlegend' in info:
             #    newLegend = info['selectionlegend']
-            #elif 'operation' in info:
+            # elif 'operation' in info:
             #    newLegend = (str(operation) + ' ' + self.title)
-            #else:
+            # else:
             #    newLegend = (legend + ' ' + self.title)
             newLegend = legend
-            self.plotWindow.addCurve(xVal,
-                                     yVal,
-                                     legend=newLegend,
-                                     info=info)
+            self.plotWindow.addCurve(xVal, yVal, legend=newLegend, info=info)
         self.plotModifiedSignal.emit()
 
     def replace(self):
@@ -1110,46 +1144,38 @@ class XMCDScanWindow(ScanWindow.ScanWindow):
         activeCurve = self.getActiveCurve()
         if activeCurve is None:
             return
-        (xVal,  yVal,  legend,  info) = activeCurve[0:4]
-        if 'selectionlegend' in info:
-            newLegend = info['selectionlegend']
-        elif 'operation' in info:
-            newLegend = (str(info['operation']) + ' ' + self.title)
+        (xVal, yVal, legend, info) = activeCurve[0:4]
+        if "selectionlegend" in info:
+            newLegend = info["selectionlegend"]
+        elif "operation" in info:
+            newLegend = str(info["operation"]) + " " + self.title
         else:
-            newLegend = (legend + self.title)
-        self.plotWindow.addCurve(xVal,
-                                 yVal,
-                                 legend=newLegend,
-                                 info=info,
-                                 replace=True)
+            newLegend = legend + self.title
+        self.plotWindow.addCurve(xVal, yVal, legend=newLegend, info=info, replace=True)
         self.plotModifiedSignal.emit()
 
     def replaceAll(self):
         allCurves = self.getAllCurves()
-        for (idx, curve) in enumerate(allCurves):
+        for idx, curve in enumerate(allCurves):
             (xVal, yVal, legend, info) = curve[0:4]
-            if 'selectionlegend' in info:
-                newLegend = info['selectionlegend']
-            elif 'operation' in info:
-                newLegend = (str(info['operation']) + ' ' + self.title)
+            if "selectionlegend" in info:
+                newLegend = info["selectionlegend"]
+            elif "operation" in info:
+                newLegend = str(info["operation"]) + " " + self.title
             else:
-                newLegend = (legend + ' ' + self.title)
+                newLegend = legend + " " + self.title
             if idx == 0:
-                self.plotWindow.addCurve(xVal,
-                                         yVal,
-                                         legend=newLegend,
-                                         info=info,
-                                         replace=True)
+                self.plotWindow.addCurve(
+                    xVal, yVal, legend=newLegend, info=info, replace=True
+                )
             else:
-                self.plotWindow.addCurve(xVal,
-                                         yVal,
-                                         legend=newLegend,
-                                         info=info)
+                self.plotWindow.addCurve(xVal, yVal, legend=newLegend, info=info)
         self.plotModifiedSignal.emit()
 
+
 class XMCDMenu(qt.QMenu):
-    def __init__(self,  parent, title=None):
-        qt.QMenu.__init__(self,  parent)
+    def __init__(self, parent, title=None):
+        qt.QMenu.__init__(self, parent)
         if title:
             self.setTitle(title)
 
@@ -1161,52 +1187,52 @@ class XMCDMenu(qt.QMenu):
         """
         if not update:
             self.clear()
-        for (name, function) in actionList:
-            if name == '$SEPARATOR':
+        for name, function in actionList:
+            if name == "$SEPARATOR":
                 self.addSeparator()
                 continue
-            if name != '':
+            if name != "":
                 fName = name
             else:
                 fName = function.func_name
-            act = qt.QAction(fName,  self)
+            act = qt.QAction(fName, self)
             # Force triggered() instead of triggered(bool)
             # to ensure proper interaction with default parameters
             act.triggered.connect(function)
             self.addAction(act)
 
-class XMCDTreeWidget(qt.QTreeWidget):
 
-    __colGroup   = 0
-    __colLegend  = 1
-    __colScanNo  = 2
+class XMCDTreeWidget(qt.QTreeWidget):
+    __colGroup = 0
+    __colLegend = 1
+    __colScanNo = 2
     __colCounter = 3
     selectionModifiedSignal = qt.pyqtSignal()
 
-    def __init__(self,  parent, groups = ['B','A','D'], color=True):
-        qt.QTreeWidget.__init__(self,  parent)
+    def __init__(self, parent, groups=["B", "A", "D"], color=True):
+        qt.QTreeWidget.__init__(self, parent)
         # Last identifier in groups is the ignore instruction
         self.groupList = groups
-        self.actionList  = []
-        self.contextMenu = qt.QMenu('Perform',  self)
+        self.actionList = []
+        self.contextMenu = qt.QMenu("Perform", self)
         self.color = color
         self.colorDict = {
-            groups[0] : qt.QBrush(qt.QColor(220, 220, 255)),
-            groups[1] : qt.QBrush(qt.QColor(255, 210, 210)),
-            '': qt.QBrush(qt.QColor(255, 255, 255))
+            groups[0]: qt.QBrush(qt.QColor(220, 220, 255)),
+            groups[1]: qt.QBrush(qt.QColor(255, 210, 210)),
+            "": qt.QBrush(qt.QColor(255, 255, 255)),
         }
 
     def sizeHint(self):
         vscrollbar = self.verticalScrollBar()
         width = vscrollbar.width()
         for i in range(self.columnCount()):
-            width += (2 + self.columnWidth(i))
-        return qt.QSize( int(width), 20*22)
+            width += 2 + self.columnWidth(i)
+        return qt.QSize(int(width), 20 * 22)
 
     def setContextMenu(self, menu):
         self.contextMenu = menu
 
-    def contextMenuEvent(self,  event):
+    def contextMenuEvent(self, event):
         if event.reason() == event.Mouse:
             pos = event.globalPos()
             item = self.itemAt(event.pos())
@@ -1247,9 +1273,9 @@ class XMCDTreeWidget(qt.QTreeWidget):
         the items of selected rows are returned.
         """
         out = []
-        convert = (convertType != str)
-        if ncol > (self.columnCount()-1):
-            _logger.debug('getColum -- Selected column out of bounds')
+        convert = convertType != str
+        if ncol > (self.columnCount() - 1):
+            _logger.debug("getColum -- Selected column out of bounds")
             raise IndexError("Selected column '%d' out of bounds" % ncol)
         if selectedOnly:
             sel = self.selectedItems()
@@ -1263,14 +1289,14 @@ class XMCDTreeWidget(qt.QTreeWidget):
                     tmp = convertType(tmp)
                 except (TypeError, ValueError):
                     if convertType == float:
-                        tmp = float('NaN')
+                        tmp = float("NaN")
                     else:
-                        _logger.debug('getColum -- Conversion failed!')
+                        _logger.debug("getColum -- Conversion failed!")
                         raise TypeError
             out += [tmp]
         return out
 
-    def build(self,  items,  headerLabels):
+    def build(self, items, headerLabels):
         """
         (Re-) Builds the tree display
 
@@ -1282,7 +1308,7 @@ class XMCDTreeWidget(qt.QTreeWidget):
         self.clear()
         self.setHeaderLabels(headerLabels)
         for item in items:
-            treeItem = TreeWidgetItem(self,  item)
+            treeItem = TreeWidgetItem(self, item)
             if self.color:
                 idx = str(treeItem.text(self.__colGroup))
                 for i in range(self.columnCount()):
@@ -1296,12 +1322,12 @@ class XMCDTreeWidget(qt.QTreeWidget):
         the identifier given in idx.
         """
         if idx not in self.groupList:
-            raise ValueError('XMCDTreeWidget: invalid identifer \'%s\'' % idx)
+            raise ValueError("XMCDTreeWidget: invalid identifer '%s'" % idx)
         sel = self.selectedItems()
         if idx == self.groupList[-1]:
             # Last identifier in self.groupList
             # is the dummy identifier
-            idx = ''
+            idx = ""
         for item in sel:
             item.setText(self.__colGroup, idx)
             if self.color:
@@ -1329,35 +1355,36 @@ class XMCDTreeWidget(qt.QTreeWidget):
             root = self.invisibleRootItem()
             sel = [root.child(i) for i in range(root.childCount())]
         # Try to sort for scanNo
-        #self.sortItems(self.__colLegend, qt.Qt.AscendingOrder)
+        # self.sortItems(self.__colLegend, qt.Qt.AscendingOrder)
         self.sortItems(self.__colScanNo, qt.Qt.AscendingOrder)
         if not seq:
-            seq, chk = qt.QInputDialog.\
-                getText(None,
-                        'Sequence Dialog',
-                        'Valid identifiers are: ' + ', '.join(self.groupList),
-                        qt.QLineEdit.Normal,
-                        'Enter sequence')
+            seq, chk = qt.QInputDialog.getText(
+                None,
+                "Sequence Dialog",
+                "Valid identifiers are: " + ", ".join(self.groupList),
+                qt.QLineEdit.Normal,
+                "Enter sequence",
+            )
         seq = str(seq).upper()
         if not chk:
             return
         for idx in seq:
             if idx not in self.groupList:
                 invalidMsg = qt.QMessageBox(None)
-                invalidMsg.setText('Invalid identifier. Try again.')
+                invalidMsg.setText("Invalid identifier. Try again.")
                 invalidMsg.setStandardButtons(qt.QMessageBox.Ok)
                 invalidMsg.exec()
                 return
         if len(sel) != len(seq):
             # Assume pattern and repeat
-            seq = seq * (len(sel)//len(seq) + 1)
-            #invalidMsg = qt.QMessageBox(None)
-            #invalidMsg.setText('Sequence length does not match item count.')
-            #invalidMsg.setStandardButtons(qt.QMessageBox.Ok)
-            #invalidMsg.exec()
-        for (idx, item) in zip(seq, sel):
+            seq = seq * (len(sel) // len(seq) + 1)
+            # invalidMsg = qt.QMessageBox(None)
+            # invalidMsg.setText('Sequence length does not match item count.')
+            # invalidMsg.setStandardButtons(qt.QMessageBox.Ok)
+            # invalidMsg.exec()
+        for idx, item in zip(seq, sel):
             if idx == self.groupList[-1]:
-                idx = ''
+                idx = ""
             item.setText(self.__colGroup, idx)
             if self.color:
                 for i in range(self.columnCount()):
@@ -1380,10 +1407,10 @@ class XMCDTreeWidget(qt.QTreeWidget):
             root = self.invisibleRootItem()
             sel = [root.child(i) for i in range(root.childCount())]
         for item in sel:
-            item.setText(self.__colGroup,'')
+            item.setText(self.__colGroup, "")
             if self.color:
                 for i in range(self.columnCount()):
-                    item.setBackground(i, self.colorDict[''])
+                    item.setBackground(i, self.colorDict[""])
         self.selectionModifiedSignal.emit()
 
     def getSelection(self):
@@ -1397,11 +1424,11 @@ class XMCDTreeWidget(qt.QTreeWidget):
         out = dict((group, []) for group in self.groupList)
         root = self.invisibleRootItem()
         for i in range(root.childCount()):
-            item   = root.child(i)
-            group  = str(item.text(0))
+            item = root.child(i)
+            group = str(item.text(0))
             legend = str(item.text(1))
-            #nCols  = item.columnCount()
-            #legend = str(item.text(nCols-1))
+            # nCols  = item.columnCount()
+            # legend = str(item.text(nCols-1))
             if len(group) == 0:
                 group = self.groupList[-1]
             out[group] += [legend]
@@ -1409,21 +1436,13 @@ class XMCDTreeWidget(qt.QTreeWidget):
             value.sort()
         return out
 
-class XMCDWidget(qt.QWidget):
 
-    toolbarOptions = {
-        'logx': False,
-        'logy': False,
-        'flip': False,
-        'fit': False
-    }
+class XMCDWidget(qt.QWidget):
+    toolbarOptions = {"logx": False, "logy": False, "flip": False, "fit": False}
 
     setSelectionSignal = qt.pyqtSignal(object, object)
 
-    def __init__(self,  parent,
-                        plotWindow,
-                        beamline,
-                        nSelectors = 5):
+    def __init__(self, parent, plotWindow, beamline, nSelectors=5):
         """
         Input
         -----
@@ -1436,184 +1455,196 @@ class XMCDWidget(qt.QWidget):
             <Group> <Legend> <ScanNo> <Counter> <Motor 1> ... <Motor5>
         """
         qt.QWidget.__init__(self, parent)
-        self.setWindowIcon(qt.QIcon(qt.QPixmap(IconDict['peak'])))
+        self.setWindowIcon(qt.QIcon(qt.QPixmap(IconDict["peak"])))
         self.plotWindow = plotWindow
         self.legendList = []
         self.motorsList = []
-        self.infoList   = []
+        self.infoList = []
         # Set self.plotWindow before calling self._setLists!
         self._setLists()
-        self.motorNamesList = [''] + self._getAllMotorNames()
+        self.motorNamesList = [""] + self._getAllMotorNames()
         self.motorNamesList.sort()
         self.numCurves = len(self.legendList)
-        #self.cBoxList = []
-        self.analysisWindow = XMCDScanWindow(origin=plotWindow,
-                                             parent=None)
+        # self.cBoxList = []
+        self.analysisWindow = XMCDScanWindow(origin=plotWindow, parent=None)
+        self.analysisWindow.enableOwnSave(False)
         self.optsWindow = XMCDOptions(self, self.motorNamesList)
 
-        helpFileName = pathjoin(PyMcaDataDir.PYMCA_DOC_DIR,
-                                "HTML",
-                                "XMCDInfotext.html")
+        helpFileName = pathjoin(PyMcaDataDir.PYMCA_DOC_DIR, "HTML", "XMCDInfotext.html")
         self.helpFileBrowser = qt.QTextBrowser()
         self.helpFileBrowser.setWindowTitle("XMCD Help")
         self.helpFileBrowser.setLineWrapMode(qt.QTextEdit.FixedPixelWidth)
         self.helpFileBrowser.setLineWrapColumnOrWidth(500)
-        self.helpFileBrowser.resize(520,300)
+        self.helpFileBrowser.resize(520, 300)
         try:
             helpFileHandle = open(helpFileName)
             helpFileHTML = helpFileHandle.read()
             helpFileHandle.close()
             self.helpFileBrowser.setHtml(helpFileHTML)
         except IOError:
-            _logger.debug('XMCDWindow -- init: Unable to read help file')
+            _logger.debug("XMCDWindow -- init: Unable to read help file")
             self.helpFileBrowser = None
 
-        self.selectionDict = {'D': [],
-                              'B': [],
-                              'A': []}
-        self.setSizePolicy(qt.QSizePolicy.MinimumExpanding,
-                           qt.QSizePolicy.Expanding)
+        self.selectionDict = {"D": [], "B": [], "A": []}
+        self.setSizePolicy(qt.QSizePolicy.MinimumExpanding, qt.QSizePolicy.Expanding)
 
         self.setWindowTitle("XLD/XMCD Analysis")
 
-        buttonOptions = qt.QPushButton('Options', self)
+        buttonOptions = qt.QPushButton("Options", self)
         buttonOptions.setToolTip(
-            'Set normalization and interpolation\n'
-           +'method and motors shown')
+            "Set normalization and interpolation\n" + "method and motors shown"
+        )
 
-        buttonInfo = qt.QPushButton('Info')
+        buttonInfo = qt.QPushButton("Info")
         buttonInfo.setToolTip(
-            'Shows a describtion of the plugins features\n'
-           +'and gives instructions on how to use it')
+            "Shows a describtion of the plugins features\n"
+            + "and gives instructions on how to use it"
+        )
 
-        updatePixmap  = qt.QPixmap(IconDict["reload"])
-        buttonUpdate  = qt.QPushButton(
-                            qt.QIcon(updatePixmap), '', self)
-        buttonUpdate.setIconSize(qt.QSize(21,21))
+        updatePixmap = qt.QPixmap(IconDict["reload"])
+        buttonUpdate = qt.QPushButton(qt.QIcon(updatePixmap), "", self)
+        buttonUpdate.setIconSize(qt.QSize(21, 21))
         buttonUpdate.setToolTip(
-            'Update curves in XMCD Analysis\n'
-           +'by checking the plot window')
+            "Update curves in XMCD Analysis\n" + "by checking the plot window"
+        )
 
         self.list = XMCDTreeWidget(self)
-        labels = ['Group', 'Legend', 'S#','Counter']+\
-            (['']*nSelectors)
-        ncols  = len(labels)
+        labels = ["Group", "Legend", "S#", "Counter"] + ([""] * nSelectors)
+        ncols = len(labels)
         self.list.setColumnCount(ncols)
         self.list.setHeaderLabels(labels)
         self.list.setSortingEnabled(True)
-        self.list.setSelectionMode(
-            qt.QAbstractItemView.ExtendedSelection)
+        self.list.setSelectionMode(qt.QAbstractItemView.ExtendedSelection)
         listContextMenu = XMCDMenu(None)
         listContextMenu.setActionList(
-              [('Perform analysis', self.triggerXMCD),
-               ('$SEPARATOR', None),
-               ('Set as A', self.setAsA),
-               ('Set as B', self.setAsB),
-               ('Enter sequence', self.list._setSelectionToSequenceSlot),
-               ('Remove selection', self.list._clearSelectionSlot),
-               ('$SEPARATOR', None),
-               ('Invert selection', self.list.invertSelection),
-               ('Remove curve(s)', self.removeCurve_)])
+            [
+                ("Perform analysis", self.triggerXMCD),
+                ("$SEPARATOR", None),
+                ("Set as A", self.setAsA),
+                ("Set as B", self.setAsB),
+                ("Enter sequence", self.list._setSelectionToSequenceSlot),
+                ("Remove selection", self.list._clearSelectionSlot),
+                ("$SEPARATOR", None),
+                ("Invert selection", self.list.invertSelection),
+                ("Remove curve(s)", self.removeCurve_),
+            ]
+        )
         self.list.setContextMenu(listContextMenu)
         self.expCBox = qt.QComboBox(self)
-        self.expCBox.setToolTip('Select configuration of predefined\n'
-                               +'experiment or configure new experiment')
+        self.expCBox.setToolTip(
+            "Select configuration of predefined\n"
+            + "experiment or configure new experiment"
+        )
 
         self.experimentsDict = {
-            'Generic Dichroism': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': '',
-                  'motor1': '',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
+            "Generic Dichroism": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "",
+                "motor1": "",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
+            },
+            "ID08: XMCD 9 Tesla Magnet": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "phaseD",
+                "motor1": "magnet",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
+            },
+            "ID08: XMCD 5 Tesla Magnet": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "PhaseD",
+                "motor1": "oxPS",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID08: XMCD 9 Tesla Magnet': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'phaseD',
-                  'motor1': 'magnet',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
+            "ID08: XLD 5 Tesla Magnet": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "PhaseD",
+                "motor1": "",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID08: XMCD 5 Tesla Magnet': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'PhaseD',
-                  'motor1': 'oxPS',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
+            "ID08: XLD 9 Tesla Magnet": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "phaseD",
+                "motor1": "",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID08: XLD 5 Tesla Magnet': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'PhaseD',
-                  'motor1': '',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
+            "ID12: XMCD (Flipper)": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "BRUKER",
+                "motor1": "OXFORD",
+                "motor2": "CRYO",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID08: XLD 9 Tesla Magnet': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'phaseD',
-                  'motor1': '',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
+            "ID12: XMCD": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "Phase",
+                "motor1": "PhaseA",
+                "motor2": "BRUKER",
+                "motor3": "OXFORD",
+                "motor4": "CRYO",
             },
-            'ID12: XMCD (Flipper)': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'BRUKER',
-                  'motor1': 'OXFORD',
-                  'motor2': 'CRYO',
-                  'motor3': '',
-                  'motor4': ''
+            "ID12: XLD (quater wave plate)": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "",
+                "motor1": "",
+                "motor2": "",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID12: XMCD': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': 'Phase',
-                  'motor1': 'PhaseA',
-                  'motor2': 'BRUKER',
-                  'motor3': 'OXFORD',
-                  'motor4': 'CRYO'
+            "ID32: XMCD / XMLD": {
+                "xrange": 0,
+                "normalization": 0,
+                "normalizationMethod": "offsetAndArea",
+                "motor0": "HU88CP",
+                "motor1": "HU88AP",
+                "motor2": "magnet",
+                "motor3": "",
+                "motor4": "",
             },
-            'ID12: XLD (quater wave plate)': {
-                  'xrange': 0,
-                  'normalization': 0,
-                  'normalizationMethod': 'offsetAndArea',
-                  'motor0': '',
-                  'motor1': '',
-                  'motor2': '',
-                  'motor3': '',
-                  'motor4': ''
-            }
         }
         self.expCBox.addItems(
-                        ['Generic Dichroism',
-                         'ID08: XLD 9 Tesla Magnet',
-                         'ID08: XLD 5 Tesla Magnet',
-                         'ID08: XMCD 9 Tesla Magnet',
-                         'ID08: XMCD 5 Tesla Magnet',
-                         'ID12: XLD (quater wave plate)',
-                         'ID12: XMCD (Flipper)',
-                         'ID12: XMCD',
-                         'Add new configuration'])
+            [
+                "Generic Dichroism",
+                "ID08: XLD 9 Tesla Magnet",
+                "ID08: XLD 5 Tesla Magnet",
+                "ID08: XMCD 9 Tesla Magnet",
+                "ID08: XMCD 5 Tesla Magnet",
+                "ID12: XLD (quater wave plate)",
+                "ID12: XMCD (Flipper)",
+                "ID12: XMCD",
+                "ID32: XMCD / XMLD",
+                "Add new configuration",
+            ]
+        )
         self.expCBox.insertSeparator(len(self.experimentsDict))
 
-        topLayout  = qt.QHBoxLayout()
+        topLayout = qt.QHBoxLayout()
         topLayout.addWidget(buttonUpdate)
         topLayout.addWidget(buttonOptions)
         topLayout.addWidget(buttonInfo)
@@ -1628,44 +1659,44 @@ class XMCDWidget(qt.QWidget):
         leftWidget = qt.QWidget(self)
         leftWidget.setLayout(leftLayout)
 
-        self.analysisWindow.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum)
-        #self.splitter = qt.QSplitter(qt.Qt.Horizontal, self)
+        self.analysisWindow.setSizePolicy(
+            qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum
+        )
+        # self.splitter = qt.QSplitter(qt.Qt.Horizontal, self)
         self.splitter = qt.QSplitter(qt.Qt.Vertical, self)
         self.splitter.addWidget(leftWidget)
         self.splitter.addWidget(self.analysisWindow)
         stretch = int(leftWidget.width())
         # If window size changes, only the scan window size changes
-        self.splitter.setStretchFactor(
-                    self.splitter.indexOf(self.analysisWindow),1)
+        self.splitter.setStretchFactor(self.splitter.indexOf(self.analysisWindow), 1)
 
         mainLayout = qt.QVBoxLayout()
-        mainLayout.setContentsMargins(0,0,0,0)
+        mainLayout.setContentsMargins(0, 0, 0, 0)
         mainLayout.addWidget(self.splitter)
         self.setLayout(mainLayout)
 
         # Shortcuts
-        self.updateShortcut = qt.QShortcut(qt.QKeySequence('F5'), self)
+        self.updateShortcut = qt.QShortcut(qt.QKeySequence("F5"), self)
         self.updateShortcut.activated.connect(self.updatePlots)
-        self.optionsWindowShortcut = qt.QShortcut(qt.QKeySequence('Alt+O'), self)
+        self.optionsWindowShortcut = qt.QShortcut(qt.QKeySequence("Alt+O"), self)
         self.optionsWindowShortcut.activated.connect(self.showOptionsWindow)
-        self.helpFileShortcut = qt.QShortcut(qt.QKeySequence('F1'), self)
+        self.helpFileShortcut = qt.QShortcut(qt.QKeySequence("F1"), self)
         self.helpFileShortcut.activated.connect(self.showInfoWindow)
-        self.expSelectorShortcut = qt.QShortcut(qt.QKeySequence('Tab'), self)
+        self.expSelectorShortcut = qt.QShortcut(qt.QKeySequence("Tab"), self)
         self.expSelectorShortcut.activated.connect(self.activateExpCB)
-        self.saveShortcut = qt.QShortcut(qt.QKeySequence('Ctrl+S'), self)
-        self.saveShortcut.activated.connect(self.analysisWindow._saveIconSignal)
+        self.saveShortcut = qt.QShortcut(qt.QKeySequence("Ctrl+S"), self)
+        self.saveShortcut.activated.connect(self._saveIconSignal)
 
         # Connects
         self.expCBox.currentIndexChanged[int].connect(self.updateTree)
         if hasattr(self.expCBox, "textActivated"):
-            self.expCBox.textActivated[str].connect(\
-                                    self.selectExperiment)
+            self.expCBox.textActivated[str].connect(self.selectExperiment)
         else:
-            self.expCBox.currentIndexChanged['QString'].connect(\
-                                    self.selectExperiment)
+            self.expCBox.currentIndexChanged["QString"].connect(self.selectExperiment)
         self.list.selectionModifiedSignal.connect(self.updateSelectionDict)
         self.setSelectionSignal.connect(self.analysisWindow.processSelection)
         self.analysisWindow.saveOptionsSignal.connect(self.optsWindow.saveOptions)
+        self.analysisWindow.sigIconSignal.connect(self._saveIconSignal)
         self.optsWindow.accepted.connect(self.updateTree)
         buttonUpdate.clicked.connect(self.updatePlots)
         buttonOptions.clicked.connect(self.showOptionsWindow)
@@ -1677,16 +1708,34 @@ class XMCDWidget(qt.QWidget):
     def sizeHint(self):
         return self.list.sizeHint() + self.analysisWindow.sizeHint()
 
+    def _saveIconSignal(self, ddict):
+        key = ddict.get("key", None)
+        if key and key == "save":
+            if not len(self.selectionDict["A"]):
+                msg = qt.QMessageBox()
+                msg.setWindowTitle("XLD/XMCD Error")
+                msg.setText("At lease one scan needs to be  as A")
+                msg.exec()
+                return
+
+            # extract the motors from the first curve selected as A
+            legend = self.selectionDict["A"][0]
+            idx = self.legendList.index(legend)
+            motors = self.motorsList[idx]
+            print(motors)
+            self.analysisWindow._saveIconSignalReplacement(motors=motors)
+
     def activateExpCB(self):
         self.expCBox.setFocus(qt.Qt.TabFocusReason)
 
     def addExperiment(self):
-        exp, chk = qt.QInputDialog.\
-                        getText(self,
-                                'Configure new experiment',
-                                'Enter experiment title',
-                                qt.QLineEdit.Normal,
-                                'ID00: <Title>')
+        exp, chk = qt.QInputDialog.getText(
+            self,
+            "Configure new experiment",
+            "Enter experiment title",
+            qt.QLineEdit.Normal,
+            "ID00: <Title>",
+        )
         if chk and (not exp.isEmpty()):
             exp = str(exp)
             opts = XMCDOptions(self, self.motorNamesList, False)
@@ -1695,10 +1744,10 @@ class XMCDWidget(qt.QWidget):
                 cBox = self.expCBox
                 new = [cBox.itemText(i) for i in range(cBox.count())][0:-2]
                 new += [exp]
-                new.append('Add new configuration')
+                new.append("Add new configuration")
                 cBox.clear()
                 cBox.addItems(new)
-                cBox.insertSeparator(len(new)-1)
+                cBox.insertSeparator(len(new) - 1)
                 idx = cBox.findText([exp][0])
                 if idx < 0:
                     cBox.setCurrentIndex(0)
@@ -1718,19 +1767,18 @@ class XMCDWidget(qt.QWidget):
     def showInfoWindow(self):
         if self.helpFileBrowser is None:
             msg = qt.QMessageBox()
-            msg.setWindowTitle('XLD/XMCD Error')
-            msg.setText('No help file found.')
+            msg.setWindowTitle("XLD/XMCD Error")
+            msg.setText("No help file found.")
             msg.exec()
             return
         else:
             self.helpFileBrowser.show()
             self.helpFileBrowser.raise_()
 
-
-# Implement new assignment routines here BEGIN
+    # Implement new assignment routines here BEGIN
     def selectExperiment(self, exp):
         exp = str(exp)
-        if exp == 'Add new configuration':
+        if exp == "Add new configuration":
             self.addExperiment()
             self.updateTree()
         elif exp in self.experimentsDict:
@@ -1738,123 +1786,114 @@ class XMCDWidget(qt.QWidget):
                 # Sets motors 0 to 4 in optsWindow
                 self.optsWindow.setOptions(self.experimentsDict[exp])
             except ValueError:
-                self.optsWindow.setOptions(
-                        self.experimentsDict['Generic Dichroism'])
+                self.optsWindow.setOptions(self.experimentsDict["Generic Dichroism"])
                 return
             # Get motor values from tree
             self.updateTree()
-            values0 = numpy.array(
-                        self.list.getColumn(4, convertType=float))
-            values1 = numpy.array(
-                        self.list.getColumn(5, convertType=float))
-            values2 = numpy.array(
-                        self.list.getColumn(6, convertType=float))
-            values3 = numpy.array(
-                        self.list.getColumn(7, convertType=float))
-            values4 = numpy.array(
-                        self.list.getColumn(8, convertType=float))
+            values0 = numpy.array(self.list.getColumn(4, convertType=float))
+            values1 = numpy.array(self.list.getColumn(5, convertType=float))
+            values2 = numpy.array(self.list.getColumn(6, convertType=float))
+            values3 = numpy.array(self.list.getColumn(7, convertType=float))
+            values4 = numpy.array(self.list.getColumn(8, convertType=float))
             # Determine p/m selection
-            if exp.startswith('ID08: XLD'):
+            if exp.startswith("ID08: XLD"):
                 values = values0
                 mask = numpy.where(numpy.isfinite(values))[0]
                 minmax = values.take(mask)
                 if len(minmax):
                     vmin = minmax.min()
                     vmax = minmax.max()
-                    vpivot = .5 * (vmax + vmin)
+                    vpivot = 0.5 * (vmax + vmin)
                 else:
-                    vpivot = 0.
-                    values = numpy.array(
-                                [float('NaN')]*len(self.legendList))
-            elif exp.startswith('ID08: XMCD'):
+                    vpivot = 0.0
+                    values = numpy.array([float("NaN")] * len(self.legendList))
+            elif exp.startswith("ID08: XMCD"):
                 mask = numpy.where(numpy.isfinite(values0))[0]
                 polarization = values0.take(mask)
                 values1 = values1.take(mask)
                 signMagnets = numpy.sign(values1)
-                if len(polarization)==0:
-                    vpivot = 0.
-                    values = numpy.array(
-                                [float('NaN')]*len(self.legendList))
-                elif numpy.all(signMagnets>=0.) or\
-                   numpy.all(signMagnets<=0.) or\
-                   numpy.all(signMagnets==0.):
+                if len(polarization) == 0:
+                    vpivot = 0.0
+                    values = numpy.array([float("NaN")] * len(self.legendList))
+                elif (
+                    numpy.all(signMagnets >= 0.0)
+                    or numpy.all(signMagnets <= 0.0)
+                    or numpy.all(signMagnets == 0.0)
+                ):
                     vmin = polarization.min()
                     vmax = polarization.max()
-                    vpivot = .5 * (vmax + vmin)
+                    vpivot = 0.5 * (vmax + vmin)
                     values = polarization
                 else:
-                    vpivot = 0.
+                    vpivot = 0.0
                     values = polarization * signMagnets
-            elif exp.startswith('ID12: XLD (quater wave plate)'):
+            elif exp.startswith("ID12: XLD (quater wave plate)"):
                 # Extract counters from third column
                 counters = self.list.getColumn(3, convertType=str)
                 polarization = []
                 for counter in counters:
                     # Relevant counters Ihor, Iver resp. Ihor0, Iver0, etc.
-                    if 'hor' in counter:
-                        pol = -1.
-                    elif 'ver' in counter:
-                        pol =  1.
+                    if "hor" in counter:
+                        pol = -1.0
+                    elif "ver" in counter:
+                        pol = 1.0
                     else:
-                        pol = float('nan')
+                        pol = float("nan")
                     polarization += [pol]
                 values = numpy.asarray(polarization, dtype=float)
-                vpivot = 0.
-            elif exp.startswith('ID12: XMCD (Flipper)'):
+                vpivot = 0.0
+            elif exp.startswith("ID12: XMCD (Flipper)"):
                 # Extract counters from third column
                 counters = self.list.getColumn(1, convertType=str)
                 polarization = []
                 for counter in counters:
                     # Relevant counters: Fminus/Fplus resp. Rminus/Rplus
-                    if 'minus' in counter:
-                        pol = 1.
-                    elif 'plus' in counter:
-                        pol = -1.
+                    if "minus" in counter:
+                        pol = 1.0
+                    elif "plus" in counter:
+                        pol = -1.0
                     else:
-                        pol = float('nan')
+                        pol = float("nan")
                     polarization += [pol]
                 magnets = values0 + values1 + values2
-                values = numpy.asarray(polarization, dtype=float)*\
-                            magnets
-                vpivot = 0.
-            elif exp.startswith('ID12: XMCD'):
+                values = numpy.asarray(polarization, dtype=float) * magnets
+                vpivot = 0.0
+            elif exp.startswith("ID12: XMCD"):
                 # Sum over phases..
                 polarization = values0 + values1
                 # ..and magnets
                 magnets = values2 + values3 + values4
                 signMagnets = numpy.sign(magnets)
-                if numpy.all(signMagnets==0.):
+                if numpy.all(signMagnets == 0.0):
                     values = polarization
                 else:
-                    values = numpy.sign(polarization)*\
-                                numpy.sign(magnets)
-                vpivot = 0.
+                    values = numpy.sign(polarization) * numpy.sign(magnets)
+                vpivot = 0.0
             else:
-                values = numpy.array([float('NaN')]*len(self.legendList))
-                vpivot = 0.
+                values = numpy.array([float("NaN")] * len(self.legendList))
+                vpivot = 0.0
             # Sequence is generate according to values and vpivot
-            seq = ''
+            seq = ""
             for x in values:
-                if str(x) == 'nan':
-                    seq += 'D'
-                elif x<vpivot:
+                if str(x) == "nan":
+                    seq += "D"
+                elif x < vpivot:
                     # Minus group
-                    seq += 'A'
+                    seq += "A"
                 else:
                     # Plus group
-                    seq += 'B'
+                    seq += "B"
             self.list.setSelectionToSequence(seq)
-# Implement new assignment routines here END
+
+    # Implement new assignment routines here END
 
     def triggerXMCD(self):
-        groupA = self.selectionDict['A']
-        groupB = self.selectionDict['B']
+        groupA = self.selectionDict["A"]
+        groupB = self.selectionDict["B"]
         self.analysisWindow.processSelection(groupA, groupB)
 
     def removeCurve_(self):
-        sel = self.list.getColumn(1,
-                                  selectedOnly=True,
-                                  convertType=str)
+        sel = self.list.getColumn(1, selectedOnly=True, convertType=str)
         for legend in sel:
             self.plotWindow.removeCurve(legend)
             for selection in self.selectionDict.values():
@@ -1862,7 +1901,7 @@ class XMCDWidget(qt.QWidget):
                     selection.remove(legend)
             # Remove from XMCDScanWindow.curvesDict
             if legend in self.analysisWindow.curvesDict.keys():
-                del(self.analysisWindow.curvesDict[legend])
+                del self.analysisWindow.curvesDict[legend]
             # Remove from XMCDScanWindow.selectionDict
             for selection in self.analysisWindow.selectionDict.values():
                 if legend in selection:
@@ -1877,89 +1916,79 @@ class XMCDWidget(qt.QWidget):
         selDict = self.list.getSelection()
         # self.selectionDict -> Uses ScanNumbers instead of legends...
         newDict = {}
-        for (idx, selList) in selDict.items():
+        for idx, selList in selDict.items():
             if idx not in newDict.keys():
                 newDict[idx] = []
             for legend in selList:
                 newDict[idx] += [legend]
         self.selectionDict = newDict
-        self.setSelectionSignal.emit(self.selectionDict['A'],
-                                     self.selectionDict['B'])
-
-    def updatePlots(self,
-                    newLegends = None,
-                    newMotorValues = None):
-        # Check if curves in plotWindow changed..
-        curves = self.plotWindow.getAllCurves(just_legend=True)
-        if curves == self.legendList:
-            # ..if not, just replot to account for zoom
-            self.triggerXMCD()
-            return
-        self._setLists()
+        self.setSelectionSignal.emit(self.selectionDict["A"], self.selectionDict["B"])
 
-        self.motorNamesList = [''] + self._getAllMotorNames()
+    def updatePlots(self):
+        self._setLists()
+        self.motorNamesList = [""] + self._getAllMotorNames()
         self.motorNamesList.sort()
         self.optsWindow.updateMotorList(self.motorNamesList)
         self.updateTree()
         experiment = str(self.expCBox.currentText())
-        if experiment != 'Generic Dichroism':
+        if experiment != "Generic Dichroism":
             self.selectExperiment(experiment)
         return
 
     def updateTree(self):
-        mList  = self.optsWindow.getMotors()
-        labels = ["Group",'Legend','S#','Counter'] + mList
-        items  = []
+        mList = self.optsWindow.getMotors()
+        labels = ["Group", "Legend", "S#", "Counter"] + mList
+        items = []
         for i in range(len(self.legendList)):
             # Loop through rows
             # Each row is represented by QStringList
             legend = self.legendList[i]
             values = self.motorsList[i]
             info = self.infoList[i]
-            selection = ''
+            selection = ""
             # Determine Group from selectionDict
-            for (idx, v) in self.selectionDict.items():
-                if (legend in v) and (idx != 'D'):
+            for idx, v in self.selectionDict.items():
+                if (legend in v) and (idx != "D"):
                     selection = idx
                     break
             # Add filename, scanNo, counter
-            #sourceName = info.get('SourceName','')
-            #if isinstance(sourceName,list):
+            # sourceName = info.get('SourceName','')
+            # if isinstance(sourceName,list):
             #    filename = basename(sourceName[0])
-            #else:
+            # else:
             #    filename = basename(sourceName)
             filename = legend
-            scanNo = info.get('Key','')
-            counter = info.get('ylabel',None)
+            scanNo = info.get("Key", "")
+            counter = info.get("ylabel", None)
             if counter is None:
-                selDict = info.get('selection',{})
+                selDict = info.get("selection", {})
                 if len(selDict) == 0:
-                    counter = ''
+                    counter = ""
                 else:
                     # When do multiple selections occur?
                     try:
-                        yIdx = selDict['y'][0]
-                        cntList = selDict['cnt_list']
+                        yIdx = selDict["y"][0]
+                        cntList = selDict["cnt_list"]
                         counter = cntList[yIdx]
                     except Exception:
-                        counter = ''
+                        counter = ""
             tmp = QStringList([selection, filename, scanNo, counter])
             # Determine value for each motor
             for m in mList:
                 if len(m) == 0:
-                    tmp.append('')
+                    tmp.append("")
                 else:
-                    tmp.append(str(values.get(m, '---')))
+                    tmp.append(str(values.get(m, "---")))
             items.append(tmp)
-        self.list.build(items,  labels)
+        self.list.build(items, labels)
         for idx in range(self.list.columnCount()):
             self.list.resizeColumnToContents(idx)
 
     def setAsA(self):
-        self.list.setSelectionAs('A')
+        self.list.setSelectionAs("A")
 
     def setAsB(self):
-        self.list.setSelectionAs('B')
+        self.list.setSelectionAs("B")
 
     def _getAllMotorNames(self):
         names = []
@@ -1970,10 +1999,10 @@ class XMCDWidget(qt.QWidget):
         names.sort()
         return names
 
-    def _convertInfoDictionary(self,  infosList):
+    def _convertInfoDictionary(self, infosList):
         ret = []
-        for info in infosList :
-            motorNames = info.get('MotorNames',  None)
+        for info in infosList:
+            motorNames = info.get("MotorNames", None)
             if motorNames is not None:
                 if type(motorNames) == str:
                     namesList = motorNames.split()
@@ -1983,7 +2012,7 @@ class XMCDWidget(qt.QWidget):
                     namesList = []
             else:
                 namesList = []
-            motorValues = info.get('MotorValues',  None)
+            motorValues = info.get("MotorValues", None)
             if motorNames is not None:
                 if type(motorValues) == str:
                     valuesList = motorValues.split()
@@ -1994,9 +2023,10 @@ class XMCDWidget(qt.QWidget):
             else:
                 valuesList = []
             if len(namesList) == len(valuesList):
-                ret.append(dict(zip(namesList,  valuesList)))
+                ret.append(dict(zip(namesList, valuesList)))
             else:
                 _logger.warning("Number of motors and values does not match!")
+                ret.append({})
         return ret
 
     def _setLists(self):
@@ -2016,15 +2046,17 @@ class XMCDWidget(qt.QWidget):
         if self.plotWindow is not None:
             curves = self.plotWindow.getAllCurves()
         else:
-            _logger.debug('_setLists -- Set self.plotWindow before calling self._setLists')
+            _logger.debug(
+                "_setLists -- Set self.plotWindow before calling self._setLists"
+            )
             return
         # nCurves = len(curves)
-        self.legendList = [leg for (xvals, yvals,  leg,  info) in curves]
-        self.infoList   = [info for (xvals, yvals,  leg,  info) in curves]
+        self.legendList = [leg for (xvals, yvals, leg, info) in curves]
+        self.infoList = [info for (xvals, yvals, leg, info) in curves]
         # Try to recover the scan number from the legend, if not set
         # Requires additional import:
-        #from re import search as regexpSearch
-        #for ddict in self.infoList:
+        # from re import search as regexpSearch
+        # for ddict in self.infoList:
         #    key = ddict.get('Key','')
         #    if len(key)== 0:
         #        selectionlegend = ddict['selectionlegend']
@@ -2034,13 +2066,14 @@ class XMCDWidget(qt.QWidget):
         #            ddict['Key'] = scanNo
         self.motorsList = self._convertInfoDictionary(self.infoList)
 
+
 class XMCDFileDialog(qt.QFileDialog):
     def __init__(self, parent, caption, directory, filter):
         qt.QFileDialog.__init__(self, parent, caption, directory, filter)
 
-        saveOptsGB = qt.QGroupBox('Save options', self)
-        self.appendBox = qt.QCheckBox('Append to existing file', self)
-        self.commentBox = qt.QTextEdit('Enter comment', self)
+        saveOptsGB = qt.QGroupBox("Save options", self)
+        self.appendBox = qt.QCheckBox("Append to existing file", self)
+        self.commentBox = qt.QTextEdit("Enter comment", self)
 
         mainLayout = self.layout()
         optsLayout = qt.QGridLayout()
@@ -2053,12 +2086,19 @@ class XMCDFileDialog(qt.QFileDialog):
 
     def appendChecked(self, state):
         if state == qt.Qt.Unchecked:
-            self.setConfirmOverwrite(True)
+            if hasattr(self, "setConfirmOverwrite"):
+                self.setConfirmOverwrite(True)
+            else:
+                self.setOption(qt.QFileDialog.DontConfirmOverwrite, False)
             self.setFileMode(qt.QFileDialog.AnyFile)
         else:
-            self.setConfirmOverwrite(False)
+            if hasattr(self, "setConfirmOverwrite"):
+                self.setConfirmOverwrite(False)
+            else:
+                self.setOption(qt.QFileDialog.DontConfirmOverwrite, True)
             self.setFileMode(qt.QFileDialog.ExistingFile)
 
+
 def getSaveFileName(parent, caption, directory, filter):
     dial = XMCDFileDialog(parent, caption, directory, filter)
     dial.setAcceptMode(qt.QFileDialog.AcceptSave)
@@ -2066,42 +2106,89 @@ def getSaveFileName(parent, caption, directory, filter):
     comment = None
     files = []
     if dial.exec():
-        append  = dial.appendBox.isChecked()
+        append = dial.appendBox.isChecked()
         comment = str(dial.commentBox.toPlainText())
-        if comment == 'Enter comment':
-            comment = ''
+        if comment == "Enter comment":
+            comment = ""
         files = [qt.safe_str(fn) for fn in dial.selectedFiles()]
     return (files, append, comment)
 
+
 def main():
     # Create dummy ScanWindow
     swin = ScanWindow.ScanWindow()
-    info0 = {'xlabel': 'foo',
-             'ylabel': 'arb',
-             'MotorNames': 'oxPS PhaseA Phase BRUKER CRYO OXFORD',
-             'MotorValues': '1 -6.27247094 -3.11222732 6.34150808 -34.75892563 21.99607165'}
-    info1 = {'MotorNames': 'PhaseD oxPS PhaseA Phase BRUKER CRYO OXFORD',
-             'MotorValues': '0.470746882688 0.25876374531 -0.18515967 -28.31216591 18.54513221 -28.09735532 -26.78833172'}
-    info2 = {'MotorNames': 'PhaseD oxPS PhaseA Phase BRUKER CRYO OXFORD',
-             'MotorValues': '-9.45353059 -25.37448851 24.37665651 18.88048044 -0.26018745 2 0.901968648111 '}
-    x = numpy.arange(100.,1100.)
-    y0 =  10*x + 10000.*numpy.exp(-0.5*(x-500)**2/400) + 1500*numpy.random.random(1000)
-    y1 =  10*x + 10000.*numpy.exp(-0.5*(x-600)**2/400) + 1500*numpy.random.random(1000)
-    y2 =  10*x + 10000.*numpy.exp(-0.5*(x-400)**2/400) + 1500*numpy.random.random(1000)
-
-    swin.newCurve(x, y2, legend="Curve2", xlabel='ene_st2', ylabel='Ihor', info=info2, replot=False, replace=False)
-    swin.newCurve(x, y0, legend="Curve0", xlabel='ene_st0', ylabel='Iver', info=info0, replot=False, replace=False)
-    swin.newCurve(x, y1, legend="Curve1", xlabel='ene_st1', ylabel='Ihor', info=info1, replot=False, replace=False)
+    info0 = {
+        "xlabel": "foo",
+        "ylabel": "arb",
+        "MotorNames": "oxPS PhaseA Phase BRUKER CRYO OXFORD",
+        "MotorValues": "1 -6.27247094 -3.11222732 6.34150808 -34.75892563 21.99607165",
+    }
+    info1 = {
+        "MotorNames": "PhaseD oxPS PhaseA Phase BRUKER CRYO OXFORD",
+        "MotorValues": "0.470746882688 0.25876374531 -0.18515967 -28.31216591 18.54513221 -28.09735532 -26.78833172",
+    }
+    info2 = {
+        "MotorNames": "PhaseD oxPS PhaseA Phase BRUKER CRYO OXFORD",
+        "MotorValues": "-9.45353059 -25.37448851 24.37665651 18.88048044 -0.26018745 2 0.901968648111 ",
+    }
+    x = numpy.arange(100.0, 1100.0)
+    y0 = (
+        10 * x
+        + 10000.0 * numpy.exp(-0.5 * (x - 500) ** 2 / 400)
+        + 1500 * numpy.random.random(1000)
+    )
+    y1 = (
+        10 * x
+        + 10000.0 * numpy.exp(-0.5 * (x - 600) ** 2 / 400)
+        + 1500 * numpy.random.random(1000)
+    )
+    y2 = (
+        10 * x
+        + 10000.0 * numpy.exp(-0.5 * (x - 400) ** 2 / 400)
+        + 1500 * numpy.random.random(1000)
+    )
+
+    swin.addCurve(
+        x,
+        y2,
+        legend="Curve2",
+        xlabel="ene_st2",
+        ylabel="Ihor",
+        info=info2,
+        replot=False,
+        replace=True,
+    )
+    swin.addCurve(
+        x,
+        y0,
+        legend="Curve0",
+        xlabel="ene_st0",
+        ylabel="Iver",
+        info=info0,
+        replot=False,
+        replace=False,
+    )
+    swin.addCurve(
+        x,
+        y1,
+        legend="Curve1",
+        xlabel="ene_st1",
+        ylabel="Ihor",
+        info=info1,
+        replot=True,
+        replace=False,
+    )
 
     # info['Key'] is overwritten when using newCurve
-    swin.dataObjectsDict['Curve2 Ihor'].info['Key'] = '1.1'
-    swin.dataObjectsDict['Curve0 Iver'].info['Key'] = '34.1'
-    swin.dataObjectsDict['Curve1 Ihor'].info['Key'] = '123.1'
+    swin.dataObjectsDict["Curve2 Ihor"].info["Key"] = "1.1"
+    swin.dataObjectsDict["Curve0 Iver"].info["Key"] = "34.1"
+    swin.dataObjectsDict["Curve1 Ihor"].info["Key"] = "123.1"
 
-    w = XMCDWidget(None, swin, 'ID08', nSelectors = 5)
+    w = XMCDWidget(None, swin, "ID08", nSelectors=5)
     w.show()
     return w
 
+
 #    helpFileBrowser = qt.QTextBrowser()
 #    helpFileBrowser.setLineWrapMode(qt.QTextEdit.FixedPixelWidth)
 #    helpFileBrowser.setLineWrapColumnOrWidth(500)
@@ -2113,7 +2200,7 @@ def main():
 #    helpFileBrowser.show()
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     app = qt.QApplication([])
     w = main()
     app.exec()
diff --git a/PyMca5/PyMcaIO/TiffIO.py b/PyMca5/PyMcaIO/TiffIO.py
index 6ddb7634..75503eb0 100644
--- a/PyMca5/PyMcaIO/TiffIO.py
+++ b/PyMca5/PyMcaIO/TiffIO.py
@@ -30,7 +30,7 @@ __author__ = "V.A. Sole - ESRF"
 __contact__ = "sole@esrf.fr"
 __license__ = "MIT"
 __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
-__date__ = "16/12/2022"
+__date__ = "25/10/2023"
 
 import sys
 import os
@@ -143,13 +143,13 @@ class TiffIO(object):
             self.fd = fd
         # read the order
         fd.seek(0)
-        order = fd.read(2).decode()
+        order = fd.read(2)
         if len(order):
-            if order == "II":
+            if order == b"II":
                 # intel, little endian
                 fileOrder = "little"
                 self._structChar = '<'
-            elif order == "MM":
+            elif order == b"MM":
                 # motorola, high endian
                 fileOrder = "big"
                 self._structChar = '>'
@@ -1114,9 +1114,7 @@ class TiffIO(object):
         info["date"] = date
         info["sampleFormat"] = sampleFormat
 
-        outputIFD = ""
-        if sys.version > '2.6':
-            outputIFD = eval('b""')
+        outputIFD = b""
 
         fmt = st + "H"
         outputIFD += struct.pack(fmt, nDirectoryEntries)
diff --git a/PyMca5/PyMcaIO/specfilewrapper.py b/PyMca5/PyMcaIO/specfilewrapper.py
index 51bb3b7b..41e4b63e 100644
--- a/PyMca5/PyMcaIO/specfilewrapper.py
+++ b/PyMca5/PyMcaIO/specfilewrapper.py
@@ -186,7 +186,7 @@ class specfilewrapper(object):
                 f = open(filename, 'rb')
                 raw_content = f.read()
                 f.close()
-                expr = '([-+]?\d+)\t\r\n'
+                expr = r'([-+]?\d+)\t\r\n'
                 self.data = [float(i) for i in re.split(expr,raw_content) if i != '']
                 self.data = numpy.array(self.data, numpy.float32)
             else:
diff --git a/PyMca5/PyMcaMath/mva/NNMAModule.py b/PyMca5/PyMcaMath/mva/NNMAModule.py
index d8c0eb10..6c7a267c 100644
--- a/PyMca5/PyMcaMath/mva/NNMAModule.py
+++ b/PyMca5/PyMcaMath/mva/NNMAModule.py
@@ -63,7 +63,7 @@ The common parameters when calling such a function are:
                              from numpy or sparse from scipy.sparse
                              package
 
-            k           --   number of componnets to estimate
+            k           --   number of components to estimate
 
             Astart
             Xstart      --   matrices to start iterations. Maybe None
@@ -185,17 +185,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 import numpy
 import logging
-try:
-    import os
-    os.environ["MDP_DISABLE_SKLEARN"] = "yes"
-    import mdp
-    if mdp.__version__ >= '2.6':
-        MDP = True
-    else:
-        MDP = False
-except Exception:
-    MDP = False
-
 from . import py_nnma
 
 
@@ -223,8 +212,8 @@ def nnma(stack, ncomponents, binning=None,
          mask=None, spectral_mask=None,
          function=None, eps=5e-5, verbose=VERBOSE,
          maxcount=1000, kmeans=False):
-    if kmeans and (not MDP):
-        raise ValueError("K Means not supported")
+    if kmeans:
+        raise ValueError("K Means not supported by this module")
     #I take the defaults for the other parameters
     param = dict(alpha=.1, tau=2, regul=1e-2, sparse_par=1e-1, psi=1e-3)
     if function is None:
@@ -384,13 +373,9 @@ def nnma(stack, ncomponents, binning=None,
     original_intensity = numpy.sum(data)
 
     #final values
-    if kmeans:
-        n_more = 1
-    else:
-        n_more = 0
-    new_images  = numpy.zeros((ncomponents + n_more, r*c), numpy.float32)
-    new_vectors = numpy.zeros((X.shape[0]+n_more, X.shape[1]), numpy.float32)
-    values      = numpy.zeros((ncomponents+n_more,), numpy.float32)
+    new_images  = numpy.zeros((ncomponents, r*c), numpy.float32)
+    new_vectors = numpy.zeros((X.shape[0], X.shape[1]), numpy.float32)
+    values      = numpy.zeros((ncomponents,), numpy.float32)
     for i in range(ncomponents):
         idx = sorted_idx[i]
         if 1:
@@ -407,17 +392,7 @@ def nnma(stack, ncomponents, binning=None,
             new_images[i, maskview] = numpy.sum(numpy.dot(Atmp, Xtmp), axis=1)
         new_vectors[i,:] = X[idx,:]
         values[i] = 100.*total_nnma_intensity[idx][0]/original_intensity
-    new_images.shape = ncomponents + n_more, r, c
-    if kmeans:
-        classifier = mdp.nodes.KMeansClassifier(ncomponents)
-        for i in range(ncomponents):
-            classifier.train(new_vectors[i:i+1])
-        k = 0
-        for i in range(r):
-            for j in range(c):
-                spectrum = data[k:k+1,:]
-                new_images[-1, i,j] = classifier.label(spectrum)[0]
-                k += 1
+    new_images.shape = ncomponents, r, c
     return new_images, values, new_vectors
 
 if __name__ == "__main__":
diff --git a/PyMca5/PyMcaPhysics/xrf/ClassMcaTheory.py b/PyMca5/PyMcaPhysics/xrf/ClassMcaTheory.py
index 9fb97542..6ef4d59f 100644
--- a/PyMca5/PyMcaPhysics/xrf/ClassMcaTheory.py
+++ b/PyMca5/PyMcaPhysics/xrf/ClassMcaTheory.py
@@ -227,6 +227,8 @@ class McaTheory(object):
             energyscatter = []
 
             for i in range(len(self.config['fit']['energy'])):
+                if self.config['fit']['energy'][i] == "None":
+                    self.config['fit']['energy'][i] = None
                 if self.config['fit']['energyflag'][i]:
                     if self.config['fit']['energy'][i] is not None:
                         energyflag.append(self.config['fit']['energyflag'][i])
diff --git a/PyMca5/PyMcaPhysics/xrf/FastXRFLinearFit.py b/PyMca5/PyMcaPhysics/xrf/FastXRFLinearFit.py
index cbda7532..1eb68ea6 100644
--- a/PyMca5/PyMcaPhysics/xrf/FastXRFLinearFit.py
+++ b/PyMca5/PyMcaPhysics/xrf/FastXRFLinearFit.py
@@ -736,10 +736,13 @@ class FastXRFLinearFit(object):
     @staticmethod
     def _fitDtypeResult(data):
         if data.dtype not in [numpy.float32, numpy.float64]:
-            if data.itemsize < 5:
-                return numpy.float32
-            else:
-                return numpy.float64
+            if hasattr(data, "itemsize"):
+                if data.itemsize < 5:
+                    return numpy.float32
+            elif hasattr(data, "nbytes"):
+                if (data.nbytes / data.size) < 5:
+                    return numpy.float32
+            return numpy.float64
         else:
             return data.dtype
 
diff --git a/PyMca5/PyMcaPhysics/xrf/McaAdvancedFitBatch.py b/PyMca5/PyMcaPhysics/xrf/McaAdvancedFitBatch.py
index 8258496d..bd075916 100644
--- a/PyMca5/PyMcaPhysics/xrf/McaAdvancedFitBatch.py
+++ b/PyMca5/PyMcaPhysics/xrf/McaAdvancedFitBatch.py
@@ -2,7 +2,7 @@
 #
 # The PyMca X-Ray Fluorescence Toolkit
 #
-# Copyright (c) 2004-2022 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2023 European Synchrotron Radiation Facility
 #
 # This file is part of the PyMca X-ray Fluorescence Toolkit developed at
 # the ESRF.
@@ -802,6 +802,8 @@ class McaAdvancedFitBatch(object):
                     concentrations = None
                     _logger.error("error in concentrations")
                     _logger.error(str(sys.exc_info()[0:-1]))
+                    self._restoreFitConfig(filename,
+                                           'calculating concentrations')
             else:
                 #just images
                 fitresult = self.mcafit.startfit(digest=0)
diff --git a/PyMca5/PyMcaPhysics/xrf/SingleLayerStrategy.py b/PyMca5/PyMcaPhysics/xrf/SingleLayerStrategy.py
index c077c136..69719896 100644
--- a/PyMca5/PyMcaPhysics/xrf/SingleLayerStrategy.py
+++ b/PyMca5/PyMcaPhysics/xrf/SingleLayerStrategy.py
@@ -2,7 +2,7 @@
 #
 # The PyMca X-Ray Fluorescence Toolkit
 #
-# Copyright (c) 2004-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2023 European Synchrotron Radiation Facility
 #
 # This file is part of the PyMca X-ray Fluorescence Toolkit developed at
 # the ESRF by the Software group.
@@ -118,6 +118,7 @@ class SingleLayerStrategy(object):
         CompoundList = []
         CompoundFraction = []
         materialCounter = -1
+        previousElements = []
         for group in strategyConfiguration["peaks"]:
             materialCounter += 1
             if "-" in group:
@@ -126,6 +127,12 @@ class SingleLayerStrategy(object):
                 _logger.debug("ignoring %s", group)
                 continue
             ele = group.split()[0]
+            if ele in previousElements:
+                print("Strategy element %s considered twice! Ignoring second entry" % ele)
+                _logger.critical("Strategy element %s considered twice. Ignoring second entry" % ele)
+                continue
+            else:
+                previousElements.append(ele)
             material = strategyConfiguration["materials"][materialCounter]
             if material in ["-", ele, ele + "1"]:
                 CompoundList.append(ele)
diff --git a/PyMca5/PyMcaPlugins/AdvancedAlignmentScanPlugin.py b/PyMca5/PyMcaPlugins/AdvancedAlignmentScanPlugin.py
index b7666940..7958aa03 100644
--- a/PyMca5/PyMcaPlugins/AdvancedAlignmentScanPlugin.py
+++ b/PyMca5/PyMcaPlugins/AdvancedAlignmentScanPlugin.py
@@ -61,7 +61,7 @@ The table shows three columns:
 
 While columns one and two can not be edited, shift values
 can be entered by hand. Another way of setting the shift values is to load them from a existing
-\*.shift file using the Load button.
+*.shift file using the Load button.
 
 Once the shift values are set, they can either be directly applied to the data present in the
 plot window, using the *Apply* button, or the data can be stored in memory. The latter options allow
diff --git a/PyMca5/PyMcaPlugins/ImageAlignmentStackPlugin.py b/PyMca5/PyMcaPlugins/ImageAlignmentStackPlugin.py
index 7d7364b9..901f4211 100644
--- a/PyMca5/PyMcaPlugins/ImageAlignmentStackPlugin.py
+++ b/PyMca5/PyMcaPlugins/ImageAlignmentStackPlugin.py
@@ -156,9 +156,10 @@ class ImageAlignmentStackPlugin(StackPluginBase.StackPluginBase):
                 # result[0] contains the string "Exception" in case
                 # of error. However, direct comparison will raise an
                 # error
-                if type(result[0]) == type('Exception'):
-                    # exception occurred
-                    raise Exception(result[1], result[2], result[3])
+                if isinstance(result[0], str):
+                    if result[0] == 'Exception':
+                        # exception occurred
+                        raise Exception(result[1], result[2], result[3])
                 else:
                     shifts = result
                 result = self.__shiftStack(stack,
@@ -306,9 +307,10 @@ class ImageAlignmentStackPlugin(StackPluginBase.StackPluginBase):
                                                     crop=crop, filename=filename)
                 if result is not None:
                     if len(result):
-                        if result[0] == 'Exception':
-                            # exception occurred
-                            raise Exception(result[1], result[2], result[3])
+                        if isinstance(result[0], str):
+                            if result[0] == 'Exception':
+                                # exception occurred
+                                raise Exception(result[1], result[2], result[3])
             if filename is None:
                 self.setStack(stack)
 
diff --git a/PyMca5/PyMcaPlugins/NNMAStackPlugin.py b/PyMca5/PyMcaPlugins/NNMAStackPlugin.py
index 76b5dfe2..86b748da 100644
--- a/PyMca5/PyMcaPlugins/NNMAStackPlugin.py
+++ b/PyMca5/PyMcaPlugins/NNMAStackPlugin.py
@@ -138,8 +138,8 @@ class NNMAStackPlugin(StackPluginBase.StackPluginBase):
         mcaIndex = stack.info.get('McaIndex')
         shape = stack.data.shape
         stack = None
-        if mcaIndex not in [-1, len(shape) - 1]:
-            raise IndexError("NNMA does not support stacks of images yet")
+        if mcaIndex not in [0, -1, len(shape) - 1]:
+            raise IndexError("NNMA only support stacks of images or spectra")
             return
         if self.configurationWidget is None:
             self.configurationWidget = NNMAParametersDialog(None, regions=True)
@@ -225,9 +225,23 @@ class NNMAStackPlugin(StackPluginBase.StackPluginBase):
                 self._status.setText(text)
 
         oldShape = stack.data.shape
-        result = function(stack, **ddict)
-        if stack.data.shape != oldShape:
-            stack.data.shape = oldShape
+        mcaIndex = stack.info.get('McaIndex')
+        if mcaIndex == 0:
+            # image stack. We need a copy
+            _logger.info("NNMAStackPlugin converting to stack of spectra")
+            data = numpy.zeros(oldShape[1:] + oldShape[0:1], dtype=numpy.float32)
+            data.shape = -1, oldShape[0]
+            for i in range(oldShape[0]):
+                tmpData = stack.data[i]
+                tmpData.shape = -1
+                data[:, i] = tmpData
+            data.shape = oldShape[1:] + oldShape[0:1]
+            result = function(data, **ddict)
+            data = None
+        else:
+            result = function(stack, **ddict)
+            if stack.data.shape != oldShape:
+                stack.data.shape = oldShape
         return result
 
     def threadFinished(self):
@@ -237,12 +251,11 @@ class NNMAStackPlugin(StackPluginBase.StackPluginBase):
         if type(result) == type((1,)):
             #if we receive a tuple there was an error
             if len(result):
-                if type(result[0]) == type("Exception"):
-                    if result[0] == "Exception":
-                        self._status.setText("Ready after calculation error")
-                        self.configurationWidget.setEnabled(True)
-                        raise Exception(result[1], result[2])
-                        return
+                if isinstance(result[0], str) and result[0] == "Exception":
+                    self._status.setText("Ready after calculation error")
+                    self.configurationWidget.setEnabled(True)
+                    raise Exception(result[1], result[2])
+                    return
         self._status.setText("Ready")
         curve = self.configurationWidget.getSpectrum(binned=True)
         if curve not in [None, []]:
diff --git a/PyMca5/PyMcaPlugins/PCAStackPlugin.py b/PyMca5/PyMcaPlugins/PCAStackPlugin.py
index 1c24c4bd..7540e92c 100644
--- a/PyMca5/PyMcaPlugins/PCAStackPlugin.py
+++ b/PyMca5/PyMcaPlugins/PCAStackPlugin.py
@@ -259,7 +259,7 @@ class PCAStackPlugin(StackPluginBase.StackPluginBase):
         if type(result) == type((1,)):
             #if we receive a tuple there was an error
             if len(result):
-                if result[0] == "Exception":
+                if isinstance(result[0], str) and result[0] == "Exception":
                     self._status.setText("Ready after calculation error")
                     self.configurationWidget.setEnabled(True)
                     raise Exception(result[1], result[2])
diff --git a/PyMca5/PyMcaPlugins/StackROIBatchPlugin.py b/PyMca5/PyMcaPlugins/StackROIBatchPlugin.py
index eecf18bf..1e8627c6 100644
--- a/PyMca5/PyMcaPlugins/StackROIBatchPlugin.py
+++ b/PyMca5/PyMcaPlugins/StackROIBatchPlugin.py
@@ -209,7 +209,7 @@ class StackROIBatchPlugin(StackPluginBase.StackPluginBase):
         if type(result) == type((1,)):
             #if we receive a tuple there was an error
             if len(result):
-                if result[0] == "Exception":
+                if isinstance(result[0], str) and result[0] == "Exception":
                     # somehow this exception is not caught
                     raise Exception(result[1], result[2])#, result[3])
                     return
diff --git a/PyMca5/PyMcaPlugins/XASStackBatchPlugin.py b/PyMca5/PyMcaPlugins/XASStackBatchPlugin.py
index 1e03596b..099d81fa 100644
--- a/PyMca5/PyMcaPlugins/XASStackBatchPlugin.py
+++ b/PyMca5/PyMcaPlugins/XASStackBatchPlugin.py
@@ -209,7 +209,7 @@ class XASStackBatchPlugin(StackPluginBase.StackPluginBase):
         if type(result) == type((1,)):
             #if we receive a tuple there was an error
             if len(result):
-                if result[0] == "Exception":
+                if isinstance(result[0], str) and result[0] == "Exception":
                     # somehow this exception is not caught
                     raise Exception(result[1], result[2])#, result[3])
                     return
diff --git a/PyMca5/PyMcaPlugins/XASStackNormalizationPlugin.py b/PyMca5/PyMcaPlugins/XASStackNormalizationPlugin.py
index 2215a691..80f249bb 100644
--- a/PyMca5/PyMcaPlugins/XASStackNormalizationPlugin.py
+++ b/PyMca5/PyMcaPlugins/XASStackNormalizationPlugin.py
@@ -187,8 +187,8 @@ class XASStackNormalizationPlugin(StackPluginBase.StackPluginBase):
                                             post_edge_regions=post_edge_regions,
                                             algorithm=algorithm,
                                             algorithm_parameters=algorithm_parameters)
-            if result[0] == 'Exception':
-                # exception occurred
+            if isinstance(result[0], str) and result[0] == 'Exception':
+                # handled exception occurred
                 raise Exception(result[1], result[2], result[3])
             else:
                 edges, jumps, errors = result
diff --git a/PyMca5/__init__.py b/PyMca5/__init__.py
index 22ac966c..eb68f953 100644
--- a/PyMca5/__init__.py
+++ b/PyMca5/__init__.py
@@ -27,7 +27,7 @@ __author__ = "V.A. Sole"
 __contact__ = "sole@esrf.fr"
 __license__ = "MIT"
 __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
-__version__ = "5.8.7"
+__version__ = "5.9.2"
 
 import os
 import sys
diff --git a/PyMca5/tests/FastXRFLinearFitTest.py b/PyMca5/tests/FastXRFLinearFitTest.py
new file mode 100644
index 00000000..6b807634
--- /dev/null
+++ b/PyMca5/tests/FastXRFLinearFitTest.py
@@ -0,0 +1,174 @@
+# /*##########################################################################
+#
+# The PyMca X-Ray Fluorescence Toolkit
+#
+# Copyright (c) 2023 European Synchrotron Radiation Facility
+#
+# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
+# the ESRF.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+#############################################################################*/
+__author__ = "V.A. Sole"
+__contact__ = "sole@esrf.eu"
+__license__ = "MIT"
+__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
+import unittest
+import sys
+import os
+import numpy
+import tempfile
+import shutil
+from PyMca5.tests import XrfData
+from PyMca5.PyMcaPhysics.xrf import FastXRFLinearFit
+from PyMca5.PyMcaPhysics.xrf.XRFBatchFitOutput import OutputBuffer
+
+try:
+    import h5py
+
+    HAS_H5PY = True
+except ImportError:
+    HAS_H5PY = False
+
+class testFastXRFLinearFit(unittest.TestCase):
+    _rtolLegacy = 1e-5
+
+    def setUp(self):
+        self.path = tempfile.mkdtemp(prefix="pymca")
+        super(testFastXRFLinearFit, self).setUp()
+
+    def tearDown(self):
+        shutil.rmtree(self.path)
+
+    @unittest.skipUnless(HAS_H5PY, "h5py not installed")
+    def testCommand(self):
+        from PyMca5.PyMcaIO import HDF5Stack1D
+
+        # generate the data
+        data, livetime = XrfData.generateXRFData()
+        configuration = XrfData.generateXRFConfig()
+        configuration["fit"]["stripalgorithm"] = 1
+
+        # create HDF5 file
+        fname = os.path.join(self.path, "FastXRF.h5")
+        h5 = h5py.File(fname, "w")
+        h5["/data"] = data
+        h5["/data_int32"] = (data * 1000).astype(numpy.int32)
+        h5.flush()
+        h5.close()
+
+        fastFit = FastXRFLinearFit.FastXRFLinearFit()
+        fastFit.setFitConfiguration(configuration)
+
+        outputDir = None
+        outputRoot = ""
+        fileEntry = ""
+        fileProcess = ""
+        refit = None
+        filepattern = None
+        begin = None
+        end = None
+        increment = None
+        backend = None
+        weight = 0
+        tif = 0
+        edf = 0
+        csv = 0
+        h5 = 1
+        dat = 0
+        concentrations = 0
+        diagnostics = 0
+        debug = 0
+        overwrite = 1
+        multipage = 0
+
+        outbuffer = OutputBuffer(
+            outputDir=outputDir,
+            outputRoot=outputRoot,
+            fileEntry=fileEntry,
+            fileProcess=fileProcess,
+            diagnostics=diagnostics,
+            tif=tif,
+            edf=edf,
+            csv=csv,
+            h5=h5,
+            dat=dat,
+            multipage=multipage,
+            overwrite=overwrite,
+        )
+
+        # test standard reading
+        scanlist = None
+        selection = {"y": "/data"}
+        dataStack = HDF5Stack1D.HDF5Stack1D([fname], selection, scanlist=scanlist)
+        with outbuffer.saveContext():
+            fastFit.fitMultipleSpectra(
+                y=dataStack,
+                weight=weight,
+                refit=refit,
+                concentrations=concentrations,
+                outbuffer=outbuffer,
+            )
+        # test dynamic reading
+        h5 = h5py.File(fname, "r")
+        with outbuffer.saveContext():
+            fastFit.fitMultipleSpectra(
+                y=h5["/data"],
+                weight=weight,
+                refit=refit,
+                concentrations=concentrations,
+                outbuffer=outbuffer,
+            )
+        # test dynamic reading of integer data
+        with outbuffer.saveContext():
+            fastFit.fitMultipleSpectra(
+                y=h5["/data_int32"],
+                weight=weight,
+                refit=refit,
+                concentrations=concentrations,
+                outbuffer=outbuffer,
+            )
+
+        h5.close()
+        h5 = None
+
+def getSuite(auto=True):
+    testSuite = unittest.TestSuite()
+    if auto:
+        testSuite.addTest(
+            unittest.TestLoader().loadTestsFromTestCase(testFastXRFLinearFit)
+        )
+    else:
+        # use a predefined order
+        testSuite.addTest(testPyMcaBatch("testCommand"))
+    return testSuite
+
+
+def test(auto=False):
+    return unittest.TextTestRunner(verbosity=2).run(getSuite(auto=auto))
+
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        auto = False
+    else:
+        auto = True
+    result = test(auto)
+    sys.exit(not result.wasSuccessful())
diff --git a/PyMca5/tests/SpecfileTest.py b/PyMca5/tests/SpecfileTest.py
index 04375597..925dac22 100644
--- a/PyMca5/tests/SpecfileTest.py
+++ b/PyMca5/tests/SpecfileTest.py
@@ -45,7 +45,19 @@ for l in ['de_DE.utf8', 'fr_FR.utf8']:
     else:
         other_locale = l
         break
-locale.setlocale(locale.LC_ALL, current_locale)
+
+try:
+    locale.setlocale(locale.LC_ALL, current_locale)
+except locale.Error:
+    # cleanup python 3.12 issue on same machines
+    if isinstance(current_locale, tuple):
+        # if the returned tuple is (None, 'UTF-8') it cannot restore the locale
+        current_as_list = list(current_locale)
+        for i in range(len(current_as_list)):
+            if current_as_list[i] is None:
+                print(f"Returned locale <{current_locale}> reset to None")
+                current_locale = None
+        locale.setlocale(locale.LC_ALL, current_locale)
 
 class testSpecfile(unittest.TestCase):
     def setUp(self):
@@ -90,8 +102,8 @@ class testSpecfile(unittest.TestCase):
         # this should free the handle
         gc.collect()
         # restore saved locale
-        locale.setlocale(locale.LC_ALL, current_locale)  
-
+        locale.setlocale(locale.LC_ALL, current_locale)
+        
         if self.specfileClass is not None:
             if os.path.exists(self.fname):
                 os.remove(self.fname)
diff --git a/PyMca5/tests/__init__.py b/PyMca5/tests/__init__.py
index e352fe6a..d9af8578 100644
--- a/PyMca5/tests/__init__.py
+++ b/PyMca5/tests/__init__.py
@@ -2,10 +2,10 @@
 #
 # The PyMca X-Ray Fluorescence Toolkit
 #
-# Copyright (c) 2004-2019 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2023 European Synchrotron Radiation Facility
 #
 # This file is part of the PyMca X-ray Fluorescence Toolkit developed at
-# the ESRF by the Software group.
+# the ESRF.
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -26,13 +26,14 @@
 # THE SOFTWARE.
 #
 #############################################################################*/
-__author__ = "V. Armando Sole - ESRF Data Analysis"
+__author__ = "V. Armando Sole"
 __contact__ = "sole@esrf.fr"
 __license__ = "MIT"
 __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
 import os
 
 from PyMca5.tests.ConfigDictTest import test as testConfigDict
+from PyMca5.tests.DataTest import test as testDataTest
 from PyMca5.tests.EdfFileTest import test as testEdfFile
 from PyMca5.tests.ROIBatchTest import test as testROIBatch
 from PyMca5.tests.ElementsTest import test as testElements
@@ -44,6 +45,7 @@ from PyMca5.tests.XrfTest import test as testXrf
 from PyMca5.tests.McaStackViewTest import test as testMcaStackView
 from PyMca5.tests.NexusUtilsTest import test as testNexusUtils
 from PyMca5.tests.StackInfoTest import test as testStackInfo
+from PyMca5.tests.FastXRFLinearFitTest import test as testFastXRFLinearFit
 
 def testAll():
     from PyMca5.tests.TestAll import main as testAll
diff --git a/README.rst b/README.rst
index 4c605a2d..6bfc23fc 100644
--- a/README.rst
+++ b/README.rst
@@ -57,7 +57,7 @@ Examples of source installation
 
 You will need the following dependencies installed:
 
--  `python <https://www.python.org/>`_ (3.7 or higher
+-  `python <https://www.python.org/>`_ (3.8 or higher
    recommended)
 -  `numpy <https://www.numpy.org/>`_
 -  `fisx <https://github.com/vasole/fisx>`_
@@ -75,9 +75,6 @@ running python installation with one of the following combinations:
 -  ``PySide6`` + ``matplotlib`` (PyMca license will be
    `MIT <https://tldrlegal.com/license/mit-license>`_ because PySide6 is
    `LGPL <https://www.gnu.org/licenses/lgpl-3.0.en.html>`_)
--  ``PySide2`` + ``matplotlib`` (PyMca license will be
-   `MIT <https://tldrlegal.com/license/mit-license>`_ because PySide2 is
-   `LGPL <https://www.gnu.org/licenses/lgpl-3.0.en.html>`_)
 
 If you want to embed ``PyMca`` in your own graphical applications, I
 recommend you to use the
diff --git a/build-pyinstaller.py b/build-pyinstaller.py
index 9c4b2044..456a0899 100644
--- a/build-pyinstaller.py
+++ b/build-pyinstaller.py
@@ -7,6 +7,17 @@ cmd = r"cd %s; pyinstaller pyinstaller.spec --noconfirm --workpath %s --distpath
                os.path.join(".", "build-" + sys.platform),
                os.path.join(".", "dist-" + sys.platform))
 
+if sys.platform.startswith("darwin"):
+    if "arm64" in sys.argv:
+        os.putenv("PYMCA_PYINSTALLER_TARGET_ARCH", "arm64")
+    elif "universal2" in sys.argv:
+        os.putenv("PYMCA_PYINSTALLER_TARGET_ARCH", "universal2")
+    elif "x86_64" in sys.argv:
+        os.putenv("PYMCA_PYINSTALLER_TARGET_ARCH", "x86_64")
+    else:
+        # let PyInstaller choose according to platform
+        pass
+
 if sys.platform.startswith("win"):
     cmd = cmd.replace(";", "&")    
 result = os.system(cmd)
diff --git a/changelog.txt b/changelog.txt
index 046e46d1..e05861e6 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,42 @@
+VERSION 5.9.2
+-------------
+
+- XRF. Prevent considering twice the same element when using the SingleLayerStrategy.
+
+- Packaging. Compatibility with PyInstaller 6.x
+
+- GUI. Improved PyQt6 compatibility.
+
+- GUI. Compatibility with current silx master branch.
+
+- GUI. Add histogram of pixel intensities to image views (requires silx).
+
+VERSION 5.9.1
+-------------
+
+- ROI Imaging. It is now possible to apply NNMA on stacks of images.
+
+- ROI Imaging. HDF5 stacks of images could not be selected due to a bug introduced in 5.8.2.
+
+- MCA. Correct ID18 calibration issues.
+
+VERSION 5.9.0
+-------------
+
+- IO. Compatibility with h5py 2.10 (Ubuntu 20.04)
+
+- GUI. Compatibility with Matplotlib 3.8
+
+VERSION 5.8.9
+-------------
+
+- FastXRF. Restore compatibility with dynamically loaded HDF5 data
+
+VERSION 5.8.8
+-------------
+
+- GUI. Compatibility with matplotlib 3.7.x
+
 VERSION 5.8.7
 -------------
 
diff --git a/debian/changelog b/debian/changelog
index a3eb02fa..88d2afc5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pymca (5.9.2+dfsg-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 26 Jan 2024 21:31:08 -0000
+
 pymca (5.8.7+dfsg-2) unstable; urgency=medium
 
   * d/rules: Forced python3 shebang
diff --git a/debian/patches/0002-Replace-assert_array_equal-by-assert_allclose-in-tes.patch b/debian/patches/0002-Replace-assert_array_equal-by-assert_allclose-in-tes.patch
index d56523ac..ef94d19a 100644
--- a/debian/patches/0002-Replace-assert_array_equal-by-assert_allclose-in-tes.patch
+++ b/debian/patches/0002-Replace-assert_array_equal-by-assert_allclose-in-tes.patch
@@ -7,11 +7,11 @@ Subject: Replace assert_array_equal by assert_allclose in tests to avoid
  PyMca5/tests/McaStackViewTest.py | 26 +++++++++++++-------------
  1 file changed, 13 insertions(+), 13 deletions(-)
 
-diff --git a/PyMca5/tests/McaStackViewTest.py b/PyMca5/tests/McaStackViewTest.py
-index 156a161..4f564bd 100644
---- a/PyMca5/tests/McaStackViewTest.py
-+++ b/PyMca5/tests/McaStackViewTest.py
-@@ -215,7 +215,7 @@ class testMcaStackView(unittest.TestCase):
+Index: pymca.git/PyMca5/tests/McaStackViewTest.py
+===================================================================
+--- pymca.git.orig/PyMca5/tests/McaStackViewTest.py
++++ pymca.git/PyMca5/tests/McaStackViewTest.py
+@@ -215,7 +215,7 @@ class testMcaStackView(unittest.TestCase
              shape = range(6, 6+ndim)
              data = numpy.random.uniform(size=shape)
              self._assertFullView(data)
@@ -20,7 +20,7 @@ index 156a161..4f564bd 100644
      @unittest.skipIf(McaStackView is None,
                       'PyMca5.PyMcaCore.McaStackView cannot be imported')
      @unittest.skipIf(h5py is None,
-@@ -274,14 +274,14 @@ class testMcaStackView(unittest.TestCase):
+@@ -274,14 +274,14 @@ class testMcaStackView(unittest.TestCase
                      for (key, chunk), (addKey, add) in chunks:
                          chunk += add
                      for idxFullComplement in dataView.idxFullComplement:
@@ -41,7 +41,7 @@ index 156a161..4f564bd 100644
  
      def _assertMaskedView(self, data):
          mcaSlice = slice(2, -1)
-@@ -316,14 +316,14 @@ class testMcaStackView(unittest.TestCase):
+@@ -316,14 +316,14 @@ class testMcaStackView(unittest.TestCase
                      else:
                          _data = data
                      for idxFullComplement in dataView.idxFullComplement:
diff --git a/debian/patches/0002-use-the-local-mathjax.patch b/debian/patches/0002-use-the-local-mathjax.patch
index 17b765d6..4a150069 100644
--- a/debian/patches/0002-use-the-local-mathjax.patch
+++ b/debian/patches/0002-use-the-local-mathjax.patch
@@ -6,10 +6,10 @@ Subject: use the local mathjax
  doc/source/conf.py | 3 ++-
  1 file changed, 2 insertions(+), 1 deletion(-)
 
-diff --git a/doc/source/conf.py b/doc/source/conf.py
-index 157758d..edb90dc 100644
---- a/doc/source/conf.py
-+++ b/doc/source/conf.py
+Index: pymca.git/doc/source/conf.py
+===================================================================
+--- pymca.git.orig/doc/source/conf.py
++++ pymca.git/doc/source/conf.py
 @@ -39,6 +39,8 @@ import sys, os
  extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode',
                'sphinx.ext.mathjax']
diff --git a/package/cxfreeze/cx_setup.py b/package/cxfreeze/cx_setup.py
index f929f2b8..e72a5f30 100644
--- a/package/cxfreeze/cx_setup.py
+++ b/package/cxfreeze/cx_setup.py
@@ -543,7 +543,7 @@ if not sys.platform.startswith("win"):
 
 
 #  generation of the NSIS executable
-nsis = os.path.join("\Program Files (x86)", "NSIS", "makensis.exe")
+nsis = os.path.join(r"\Program Files (x86)", "NSIS", "makensis.exe")
 if sys.platform.startswith("win") and os.path.exists(nsis):
     # check if we can perform the packaging
     outFile = "nsisscript.nsi"
diff --git a/package/pyinstaller/PyMca.txt b/package/pyinstaller/PyMca.txt
index 50fd6926..fe0a0eea 100644
--- a/package/pyinstaller/PyMca.txt
+++ b/package/pyinstaller/PyMca.txt
@@ -3,7 +3,7 @@
 
  Copyright (C) 2004-2023 European Synchrotron Radiation Facility
 
- The PyMca X-ray Fluorescence Toolkit is developed at the ESRF by the Software group.
+ The PyMca X-ray Fluorescence Toolkit is developed at the ESRF.
 
  PyMca follows the very permissive MIT license. However, this frozen binary
 uses GPL-licensed code and therefore this binary follows the GPL license that follows.
diff --git a/package/pyinstaller/pyinstaller.spec b/package/pyinstaller/pyinstaller.spec
index fb007247..09e3bb0f 100644
--- a/package/pyinstaller/pyinstaller.spec
+++ b/package/pyinstaller/pyinstaller.spec
@@ -1,12 +1,13 @@
 # -*- mode: python -*-
 import sys
-import os.path
+import os
 from pathlib import Path
 import shutil
 import subprocess
 import time
 import logging
 
+import PyInstaller
 from PyInstaller.utils.hooks import collect_data_files, collect_submodules
 from PyInstaller.config import CONF
 
@@ -154,21 +155,40 @@ for i in range(len(script_a)):
                           script_a[i].zipped_data,
                           cipher=block_cipher))
 
-    script_exe.append(
-        EXE(
-            script_pyz[i],
-            script_a[i].scripts,
-            script_a[i].dependencies,
-            [],
-            exclude_binaries=True,
-            name=script_n[i],
-            debug=False,
-            bootloader_ignore_signals=False,
-            strip=False,
-            upx=False,
-            console=True,
-            icon=icon)
-        )
+    arch = os.getenv("PYMCA_PYINSTALLER_TARGET_ARCH") 
+    if arch:
+        script_exe.append(
+            EXE(
+                script_pyz[i],
+                script_a[i].scripts,
+                script_a[i].dependencies,
+                [],
+                exclude_binaries=True,
+                name=script_n[i],
+                debug=False,
+                bootloader_ignore_signals=False,
+                strip=False,
+                upx=False,
+                console=True,
+                icon=icon,
+                target_arch=arch)
+            )
+    else:
+        script_exe.append(
+            EXE(
+                script_pyz[i],
+                script_a[i].scripts,
+                script_a[i].dependencies,
+                [],
+                exclude_binaries=True,
+                name=script_n[i],
+                debug=False,
+                bootloader_ignore_signals=False,
+                strip=False,
+                upx=False,
+                console=True,
+                icon=icon)
+            )
     script_col.append(
         COLLECT(
             script_exe[i],
@@ -379,7 +399,13 @@ def cleanup_cache(topdir):
 
 def replace_module(name):
     dest = os.path.join(DISTDIR, script_n[0])
-    target = os.path.join(dest, os.path.basename(name))
+    if sys.platform.startswith("darwin") and PyInstaller.__version__ >= '6.0.0':
+        target = os.path.join(dest, "special_modules")
+        if not os.path.exists(target):
+            os.mkdir(target)
+        target = os.path.join(target, os.path.basename(name))
+    else:
+        target = os.path.join(dest, os.path.basename(name))
     print("source = ", name)
     print("dest = ", target)
     if os.path.exists(target):
@@ -407,19 +433,34 @@ for fname in script_n:
 
 # patch silx
 if SILX:
-    fname = os.path.join(DISTDIR, script_n[0], "silx", "gui","qt","_qt.py")
-    if os.path.exists(fname):
-        logger.info("###################################################################")
-        logger.info("Patching silx")
-        logger.info(fname)
-        logger.info("###################################################################")
-        f = open(fname, "r")
-        content = f.readlines()
-        f.close()
-        f = open(fname, "w")
-        for line in content:
-            f.write(line.replace("from PyQt5.uic import loadUi", "pass"))
-        f.close()
+    if sys.platform.startswith("darwin") and PyInstaller.__version__ >= '6.0.0':
+        fname_dir = os.path.join(DISTDIR, script_n[0], "special_modules", "silx", "gui","qt")
+    else:
+        fname_dir = os.path.join(DISTDIR, script_n[0], "silx", "gui","qt")
+    for name in ["_qt.py", "__init__.py"]:
+        fname = os.path.join(fname_dir, name)
+        if os.path.exists(fname):
+            logger.info("###################################################################")
+            logger.info("Patching silx")
+            logger.info(fname)
+            logger.info("###################################################################")
+            f = open(fname, "r")
+            content = f.readlines()
+            f.close()
+            f = open(fname, "w")
+            for line in content:
+                #f.write(line.replace("from PyQt5.uic import loadUi", "pass"))
+                #f.write(line.replace("from PyQt6.uic import loadUi", "pass"))
+                if "import loadUi" in line:
+                    f.write(line.replace("from ", "pass #"))
+                else:
+                    f.write(line)
+            f.close()
+        else:
+            logger.info("###################################################################")
+            logger.info("Cannot patch silx. File not found")
+            logger.info(fname)
+            logger.info("###################################################################")
 
 # patch OpenCL
 if OPENCL:
@@ -518,6 +559,24 @@ if sys.platform.startswith("darwin"):
         shutil.rmtree(dest)
     os.rename(source, dest)
 
+    # relocate the special modules
+    special_modules_dir = os.path.join(dest, "Contents", "MacOS", "special_modules")
+    if os.path.exists(special_modules_dir):
+        source = os.path.join(special_modules_dir, "*")
+        dest = os.path.join(dest, "Contents", "Frameworks")
+        cmd = "cp -Rf %s %s" % (source, dest)
+        print(cmd)
+        os.system(cmd)
+        source = source[:-1]
+        print("deleting %s" % source)
+        shutil.rmtree(source)
+        # remove the duplicated _internal directory
+        internal_modules_dir = os.path.join( \
+            DISTDIR, "PyMca%s.app" % version, "Contents", "MacOS", "_internal")
+        if os.path.exists(internal_modules_dir):
+             print("deleting %s" % internal_modules_dir)
+             shutil.rmtree(internal_modules_dir)
+
     # Pack the application
     destination = os.path.join(SPECPATH, "artifacts")
     if os.path.exists(destination):
@@ -610,7 +669,7 @@ else:
     frozenDir = target
 
     #  generation of the NSIS executable
-    nsis = os.path.join("\Program Files (x86)", "NSIS", "makensis.exe")
+    nsis = os.path.join(r"\Program Files (x86)", "NSIS", "makensis.exe")
     if sys.platform.startswith("win") and os.path.exists(nsis):
         # check if we can perform the packaging
         outFile = os.path.join(SPECPATH, "nsisscript.nsi")

More details

Full run details

Historical runs