From 320823da5ed71903e232c0b39046d7ab5c9f2663 Mon Sep 17 00:00:00 2001 From: "V. Armando Sole" Date: Sat, 15 Feb 2025 18:04:03 +0100 Subject: [PATCH 1/2] Do not crash on MacOS when doing the PCA calculation --- src/PyMca5/PyMcaPlugins/PCAStackPlugin.py | 72 ++++++++++++++--------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py b/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py index 7540e92cc..de4516d50 100644 --- a/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py +++ b/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py @@ -1,5 +1,5 @@ #/*########################################################################## -# Copyright (C) 2004-2023 European Synchrotron Radiation Facility +# Copyright (C) 2004-2025 European Synchrotron Radiation Facility # # This file is part of the PyMca X-ray Fluorescence Toolkit developed at # the ESRF. @@ -181,25 +181,12 @@ def calculate(self): self._kMeansWidget = None self._executeFunctionAndParameters() + def _executeFunctionAndParameters(self): self.widget = None self.configurationWidget.show() - if _logger.getEffectiveLevel() == logging.DEBUG: - self.thread = CalculationThread.CalculationThread(\ - calculation_method=self.actualCalculation) - self.thread.result = self.actualCalculation() - self.threadFinished() - else: - self.thread = CalculationThread.CalculationThread(\ - calculation_method=self.actualCalculation) - self.thread.start() - message = "Please wait. PCA Calculation going on." - CalculationThread.waitingMessageDialog(self.thread, - message=message, - parent=self.configurationWidget) - self.threadFinished() - - def actualCalculation(self): + + #obtain the parameters for the calculation pcaParameters = self.configurationWidget.getParameters() self._status.setText("Calculation going on") self.configurationWidget.setEnabled(False) @@ -235,10 +222,7 @@ def actualCalculation(self): oldShapes = [] for stack in stackList: oldShapes.append(stack.data.shape) - result = function(stackList, **pcaParameters) - for i in range(len(stackList)): - stackList[i].data.shape = oldShapes[i] - return result + inputStack = stackList else: stack = self.getStackDataObject() if isinstance(stack, numpy.ndarray): @@ -248,14 +232,46 @@ def actualCalculation(self): text += " WARNING: Non floating point data." self._status.setText(text) oldShape = stack.data.shape - result = function(stack, **pcaParameters) - if stack.data.shape != oldShape: - stack.data.shape = oldShape - return result + inputStack = stack - def threadFinished(self): - result = self.thread.getResult() - self.thread = None + try: + if _logger.getEffectiveLevel() == logging.DEBUG: + result = function(inputStack, **pcaParameters) + self.threadFinished(result) + else: + thread = CalculationThread.CalculationThread(\ + calculation_method=function, + calculation_vars=inputStack, + calculation_kw=pcaParameters, + expand_vars=False, + expand_kw=True) + thread.start() + message = "Please wait. PCA Calculation going on." + CalculationThread.waitingMessageDialog(thread, + message=message, + parent=self.configurationWidget, + modal=True, + update_callback=None, + frameless=True) + result = thread.getResult() + self.threadFinished(result) + except Exception: + msg = qt.QMessageBox(self) + msg.setIcon(qt.QMessageBox.Critical) + msg.setWindowTitle("Configuration error") + msg.setText("Error configuring fit:") + msg.setInformativeText(str(sys.exc_info()[1])) + msg.setDetailedText(traceback.format_exc()) + msg.exec() + finally: + if "Multiple" in self.__methodlabel: + for i in range(len(stackList)): + stackList[i].data.shape = oldShapes[i] + else: + if stack.data.shape != oldShape: + stack.data.shape = oldShape + + def threadFinished(self, result): if type(result) == type((1,)): #if we receive a tuple there was an error if len(result): From 0e54070e9c84c61fc8150767f1a1579d863aedef Mon Sep 17 00:00:00 2001 From: "V. Armando Sole" Date: Mon, 17 Feb 2025 11:29:28 +0100 Subject: [PATCH 2/2] Rework threading mechanism of PCA and NNMA plugins. --- src/PyMca5/PyMcaPlugins/NNMAStackPlugin.py | 85 +++++++++++++++------- src/PyMca5/PyMcaPlugins/PCAStackPlugin.py | 24 ++++-- 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/PyMca5/PyMcaPlugins/NNMAStackPlugin.py b/src/PyMca5/PyMcaPlugins/NNMAStackPlugin.py index 86b748da5..1409125b5 100644 --- a/src/PyMca5/PyMcaPlugins/NNMAStackPlugin.py +++ b/src/PyMca5/PyMcaPlugins/NNMAStackPlugin.py @@ -1,5 +1,5 @@ #/*########################################################################## -# Copyright (C) 2004-2023 European Synchrotron Radiation Facility +# Copyright (C) 2004-2025 European Synchrotron Radiation Facility # # This file is part of the PyMca X-ray Fluorescence Toolkit developed at # the ESRF. @@ -178,23 +178,10 @@ def calculate(self): if ret: self._executeFunctionAndParameters() - def _executeFunctionAndParameters(self): - _logger.debug("NNMAStackPlugin _executeFunctionAndParameters") - self.widget = None - self.thread = CalculationThread.CalculationThread(\ - calculation_method=self.actualCalculation) - self.configurationWidget.show() - message = "Please wait. NNMA Calculation going on." - _logger.debug("NNMAStackPlugin starting thread") - self.thread.start() - _logger.debug("NNMAStackPlugin waitingMessageDialog") - CalculationThread.waitingMessageDialog(self.thread, - message=message, - parent=self.configurationWidget) - _logger.debug("NNMAStackPlugin waitingMessageDialog passed") - self.threadFinished() - - def actualCalculation(self): + def _getFunctionAndParameters(self): + """ + Get the function, vars and kw for the calculation thread + """ _logger.debug("NNMAStackPlugin actualCalculation") nnmaParameters = self.configurationWidget.getParameters() self._status.setText("Calculation going on") @@ -216,6 +203,16 @@ def actualCalculation(self): spatial_mask = numpy.isfinite(self.getStackOriginalImage()) ddict['mask'] = spatial_mask del nnmaParameters + return function, None, ddict + + def _executeFunctionAndParameters(self): + _logger.debug("_executeFunctionAndParameters") + self.widget = None + self.configurationWidget.show() + function, dummy, ddict = self._getFunctionAndParameters() + _logger.info("NNMA function %s" % function.__name__) + _logger.info("NNMA parameters %s" % ddict) + stack = self.getStackDataObject() if isinstance(stack, numpy.ndarray): if stack.data.dtype not in [numpy.float64, numpy.float32]: @@ -236,18 +233,48 @@ def actualCalculation(self): 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 + data = stack + try: + if _logger.getEffectiveLevel() == logging.DEBUG: + result = function(inputStack, **ddict) + self.threadFinished(result) + else: + thread = CalculationThread.CalculationThread(\ + calculation_method=function, + calculation_vars=data, + calculation_kw=ddict, + expand_vars=False, + expand_kw=True) + thread.start() + message = "Please wait. NNMA Calculation going on." + _logger.debug("NNMAStackPlugin waitingMessageDialog") + CalculationThread.waitingMessageDialog(thread, + message=message, + parent=self.configurationWidget, + modal=True, + update_callback=None, + frameless=False) + _logger.debug("NNMAStackPlugin waitingMessageDialog passed") + result = thread.getResult() + self.threadFinished(result) + except Exception: + msg = qt.QMessageBox(self) + msg.setIcon(qt.QMessageBox.Critical) + msg.setWindowTitle("Calculation error") + msg.setText("Error on NNMA calculation") + msg.setInformativeText(str(sys.exc_info()[1])) + msg.setDetailedText(traceback.format_exc()) + msg.exec() + finally: + if mcaIndex == 0: + data = None + else: + if stack.data.shape != oldShape: + stack.data.shape = oldShape - def threadFinished(self): - _logger.debug("NNMAStackPlugin threadFinished") - result = self.thread.result - self.thread = None + def threadFinished(self, result): + _logger.info("threadFinished") if type(result) == type((1,)): #if we receive a tuple there was an error if len(result): @@ -258,6 +285,8 @@ def threadFinished(self): return self._status.setText("Ready") curve = self.configurationWidget.getSpectrum(binned=True) + + if curve not in [None, []]: xValues = curve[0] else: diff --git a/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py b/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py index de4516d50..3c889fda8 100644 --- a/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py +++ b/src/PyMca5/PyMcaPlugins/PCAStackPlugin.py @@ -181,11 +181,10 @@ def calculate(self): self._kMeansWidget = None self._executeFunctionAndParameters() - - def _executeFunctionAndParameters(self): - self.widget = None - self.configurationWidget.show() - + def _getFunctionAndParameters(self): + """ + Get the function, vars and kw for the calculation thread + """ #obtain the parameters for the calculation pcaParameters = self.configurationWidget.getParameters() self._status.setText("Calculation going on") @@ -215,6 +214,13 @@ def _executeFunctionAndParameters(self): spatial_mask = numpy.isfinite(self.getStackOriginalImage()) pcaParameters['mask'] = spatial_mask pcaParameters["legacy"] = False + return function, None, pcaParameters + + def _executeFunctionAndParameters(self): + _logger.debug("_executeFunctionAndParameters") + self.widget = None + self.configurationWidget.show() + function, dummy, pcaParameters = self._getFunctionAndParameters() _logger.info("PCA function %s" % function.__name__) _logger.info("PCA parameters %s" % pcaParameters) if "Multiple" in self.__methodlabel: @@ -252,14 +258,14 @@ def _executeFunctionAndParameters(self): parent=self.configurationWidget, modal=True, update_callback=None, - frameless=True) + frameless=False) result = thread.getResult() self.threadFinished(result) except Exception: msg = qt.QMessageBox(self) msg.setIcon(qt.QMessageBox.Critical) - msg.setWindowTitle("Configuration error") - msg.setText("Error configuring fit:") + msg.setWindowTitle("Calculation error") + msg.setText("Error on PCA calculation") msg.setInformativeText(str(sys.exc_info()[1])) msg.setDetailedText(traceback.format_exc()) msg.exec() @@ -272,6 +278,7 @@ def _executeFunctionAndParameters(self): stack.data.shape = oldShape def threadFinished(self, result): + _logger.info("threadFinished") if type(result) == type((1,)): #if we receive a tuple there was an error if len(result): @@ -281,6 +288,7 @@ def threadFinished(self, result): raise Exception(result[1], result[2]) return self._status.setText("Ready") + curve = self.configurationWidget.getSpectrum(binned=True) if curve not in [None, []]: xValues = curve[0]