From 5c4c02486f1aa4e9118b48975d8823d56570f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 27 Jan 2023 12:31:31 +0100 Subject: [PATCH 01/21] Alternative visulalization of geometry --- pyFAI/gui/tasks/IntegrationTask.py | 244 ++++++++++++-- pyFAI/resources/gui/calibration-result.ui | 394 +++++++++++++++++++++- 2 files changed, 600 insertions(+), 38 deletions(-) diff --git a/pyFAI/gui/tasks/IntegrationTask.py b/pyFAI/gui/tasks/IntegrationTask.py index 6a266574e..f770d8b62 100644 --- a/pyFAI/gui/tasks/IntegrationTask.py +++ b/pyFAI/gui/tasks/IntegrationTask.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "05/05/2022" +__date__ = "27/01/2023" import logging import numpy @@ -40,6 +40,8 @@ from pyFAI.azimuthalIntegrator import AzimuthalIntegrator from ..utils import unitutils from ..model.DataModel import DataModel +from ..model.GeometryModel import GeometryModel +from ..model.Fit2dGeometryModel import Fit2dGeometryModel from ..widgets.QuantityLabel import QuantityLabel from ..CalibrationContext import CalibrationContext from ... import units as core_units @@ -56,6 +58,7 @@ from pyFAI import method_registry from ..dialog import MessageBox from pyFAI.io import ponifile +import pyFAI.geometry _logger = logging.getLogger(__name__) @@ -100,13 +103,13 @@ def setValue(self, value): class IntegrationProcess(object): - def __init__(self, model): - self.__isValid = self._init(model) + def __init__(self, model, altGeometry=None): + self.__isValid = self._init(model, altGeometry) self.__resetZoomPolicy = None self.__method = None self.__errorMessage = None - def _init(self, model): + def _init(self, model, altGeometry=None): self.__isValid = True if model is None: return False @@ -117,7 +120,10 @@ def _init(self, model): detector = model.experimentSettingsModel().detector() if detector is None: return - geometry = model.fittedGeometry() + if altGeometry: + geometry = altGeometry + else: + geometry = model.fittedGeometry() if not geometry.isValid(): return False self.__nPointsAzimuthal = model.integrationSettingsModel().nPointsAzimuthal().value() @@ -927,6 +933,81 @@ def _initGui(self): self._savePoniButton.clicked.connect(self.__saveAsPoni) + # For the + self.__geometry = GeometryModel() + self.__fit2dGeometry = Fit2dGeometryModel() + self.__detector = None + self.__originalGeometry = None + self.__updatingModel = False + + # Create shared units + angleUnit = DataModel() + angleUnit.setValue(units.Unit.RADIAN) + lengthUnit = DataModel() + lengthUnit.setValue(units.Unit.METER) + pixelUnit = DataModel() + pixelUnit.setValue(units.Unit.PIXEL) + + # Connect pyFAI widgets to units + self._pyfaiDistance.setDisplayedUnitModel(lengthUnit) + self._pyfaiDistance.setModelUnit(units.Unit.METER) + self._pyfaiDistanceUnit.setUnitModel(lengthUnit) + self._pyfaiDistanceUnit.setUnitEditable(True) + self._pyfaiPoni1.setDisplayedUnitModel(lengthUnit) + self._pyfaiPoni1.setModelUnit(units.Unit.METER) + self._pyfaiPoni1Unit.setUnitModel(lengthUnit) + self._pyfaiPoni1Unit.setUnitEditable(True) + self._pyfaiPoni2.setDisplayedUnitModel(lengthUnit) + self._pyfaiPoni2.setModelUnit(units.Unit.METER) + self._pyfaiPoni2Unit.setUnitModel(lengthUnit) + self._pyfaiPoni2Unit.setUnitEditable(True) + self._pyfaiRotation1.setDisplayedUnitModel(angleUnit) + self._pyfaiRotation1.setModelUnit(units.Unit.RADIAN) + self._pyfaiRotation1Unit.setUnitModel(angleUnit) + self._pyfaiRotation1Unit.setUnitEditable(True) + self._pyfaiRotation2.setDisplayedUnitModel(angleUnit) + self._pyfaiRotation2.setModelUnit(units.Unit.RADIAN) + self._pyfaiRotation2Unit.setUnitModel(angleUnit) + self._pyfaiRotation2Unit.setUnitEditable(True) + self._pyfaiRotation3.setDisplayedUnitModel(angleUnit) + self._pyfaiRotation3.setModelUnit(units.Unit.RADIAN) + self._pyfaiRotation3Unit.setUnitModel(angleUnit) + self._pyfaiRotation3Unit.setUnitEditable(True) + + # Connect fit2d widgets to units + self._fit2dDistance.setDisplayedUnit(units.Unit.MILLIMETER) + self._fit2dDistance.setModelUnit(units.Unit.MILLIMETER) + self._fit2dDistanceUnit.setUnit(units.Unit.MILLIMETER) + self._fit2dCenterX.setDisplayedUnitModel(pixelUnit) + self._fit2dCenterX.setModelUnit(units.Unit.PIXEL) + self._fit2dCenterXUnit.setUnit(units.Unit.PIXEL) + self._fit2dCenterY.setDisplayedUnitModel(pixelUnit) + self._fit2dCenterY.setModelUnit(units.Unit.PIXEL) + self._fit2dCenterYUnit.setUnit(units.Unit.PIXEL) + self._fit2dTilt.setDisplayedUnit(units.Unit.DEGREE) + self._fit2dTilt.setModelUnit(units.Unit.DEGREE) + self._fit2dTiltUnit.setUnit(units.Unit.DEGREE) + self._fit2dTiltPlan.setDisplayedUnit(units.Unit.DEGREE) + self._fit2dTiltPlan.setModelUnit(units.Unit.DEGREE) + self._fit2dTiltPlanUnit.setUnit(units.Unit.DEGREE) + + # Connect fit2d model-widget + self._fit2dDistance.setModel(self.__fit2dGeometry.distance()) + self._fit2dCenterX.setModel(self.__fit2dGeometry.centerX()) + self._fit2dCenterY.setModel(self.__fit2dGeometry.centerY()) + self._fit2dTilt.setModel(self.__fit2dGeometry.tilt()) + self._fit2dTiltPlan.setModel(self.__fit2dGeometry.tiltPlan()) + + self._pyfaiDistance.setModel(self.__geometry.distance()) + self._pyfaiPoni1.setModel(self.__geometry.poni1()) + self._pyfaiPoni2.setModel(self.__geometry.poni2()) + self._pyfaiRotation1.setModel(self.__geometry.rotation1()) + self._pyfaiRotation2.setModel(self.__geometry.rotation2()) + self._pyfaiRotation3.setModel(self.__geometry.rotation3()) + + self.__geometry.changed.connect(self.__updateFit2dFromPyfai) + self.__fit2dGeometry.changed.connect(self.__updatePyfaiFromFit2d) + super()._initGui() def __customIntegrationMethod(self): @@ -985,7 +1066,7 @@ def __widgetShow(self): self._integrateButton.executeCallable() def __integrate(self): - self.__integrationProcess = IntegrationProcess(self.model()) + self.__integrationProcess = IntegrationProcess(self.model(), self.__geometry) self.__integrationProcess.setMethod(self.__method) if self.__integrationResetZoomPolicy is not None: @@ -1058,6 +1139,8 @@ def __fittedGeometryChanged(self): poniFile = self.model().experimentSettingsModel().poniFile() with poniFile.lockContext(): poniFile.setSynchronized(False) + # Update displayed data: + self.__updateDisplayedGeometry() def __saveAsPoni(self): # FIXME test the validity of the geometry before opening the dialog @@ -1080,21 +1163,16 @@ def __saveAsPoni(self): with poniFile.lockContext(): poniFile.setValue(filename) - pyfaiGeometry = pyFAI.geometry.Geometry() - - geometry = self.model().fittedGeometry() - pyfaiGeometry.dist = geometry.distance().value() - pyfaiGeometry.poni1 = geometry.poni1().value() - pyfaiGeometry.poni2 = geometry.poni2().value() - pyfaiGeometry.rot1 = geometry.rotation1().value() - pyfaiGeometry.rot2 = geometry.rotation2().value() - pyfaiGeometry.rot3 = geometry.rotation3().value() - pyfaiGeometry.wavelength = geometry.wavelength().value() - - experimentSettingsModel = self.model().experimentSettingsModel() - detector = experimentSettingsModel.detector() - pyfaiGeometry.detector = detector - + pyfaiGeometry = pyFAI.geometry.Geometry( + dist=self.__geometry.distance().value(), + poni1=self.__geometry.poni1().value(), + poni2=self.__geometry.poni2().value(), + rot1=self.__geometry.rotation1().value(), + rot2=self.__geometry.rotation2().value(), + rot3=self.__geometry.rotation3().value(), + wavelength=self.__geometry.wavelength().value(), + detector=self.__detector + ) try: writer = ponifile.PoniFile(pyfaiGeometry) with open(filename, "wt") as fd: @@ -1104,3 +1182,127 @@ def __saveAsPoni(self): poniFile.setSynchronized(True) except Exception as e: MessageBox.exception(self, "Error while saving poni file", e, _logger) + + def __updateDisplayedGeometry(self): + experimentSettingsModel = self.model().experimentSettingsModel() + self.__detector = experimentSettingsModel.detector() + self.__geometry.setFrom(self.model().fittedGeometry()) + + def __createPyfaiGeometry(self): + geometry = self.__geometry + if not geometry.isValid(checkWaveLength=False): + raise RuntimeError("The geometry is not valid") + dist = geometry.distance().value() + poni1 = geometry.poni1().value() + poni2 = geometry.poni2().value() + rot1 = geometry.rotation1().value() + rot2 = geometry.rotation2().value() + rot3 = geometry.rotation3().value() + wavelength = geometry.wavelength().value() + result = pyFAI.geometry.Geometry(dist=dist, + poni1=poni1, + poni2=poni2, + rot1=rot1, + rot2=rot2, + rot3=rot3, + detector=self.__detector, + wavelength=wavelength) + return result + + def __updatePyfaiFromFit2d(self): + if self.__updatingModel: + return + self.__updatingModel = True + geometry = self.__fit2dGeometry + error = None + distance = None + poni1 = None + poni2 = None + rotation1 = None + rotation2 = None + rotation3 = None + + if geometry is None: + error = "No geometry to compute pyFAI geometry." + pass + elif self.__detector is None: + error = "No detector defined. It is needed to compute the pyFAI geometry." + elif not geometry.isValid(): + error = "The current geometry is not valid to compute the pyFAI one." + else: + pyFAIGeometry = pyFAI.geometry.Geometry(detector=self.__detector) + try: + f2d_distance = geometry.distance().value() + f2d_centerX = geometry.centerX().value() + f2d_centerY = geometry.centerY().value() + f2d_tiltPlan = geometry.tiltPlan().value() + f2d_tilt = geometry.tilt().value() + pyFAIGeometry.setFit2D(directDist=f2d_distance, + centerX=f2d_centerX, + centerY=f2d_centerY, + tilt=f2d_tilt, + tiltPlanRotation=f2d_tiltPlan) + except Exception: + error = "This geometry can't be modelized with pyFAI." + else: + distance = pyFAIGeometry.dist + poni1 = pyFAIGeometry.poni1 + poni2 = pyFAIGeometry.poni2 + rotation1 = pyFAIGeometry.rot1 + rotation2 = pyFAIGeometry.rot2 + rotation3 = pyFAIGeometry.rot3 + + self._fit2dError.setVisible(error is not None) + self._fit2dError.setText(error) + self.__geometry.lockSignals() + self.__geometry.distance().setValue(distance) + self.__geometry.poni1().setValue(poni1) + self.__geometry.poni2().setValue(poni2) + self.__geometry.rotation1().setValue(rotation1) + self.__geometry.rotation2().setValue(rotation2) + self.__geometry.rotation3().setValue(rotation3) + self.__geometry.unlockSignals() + self.__updatingModel = False + + def __updateFit2dFromPyfai(self): + if self.__updatingModel: + return + self.__updatingModel = True + geometry = self.__geometry + error = None + distance = None + centerX = None + centerY = None + tiltPlan = None + tilt = None + + if geometry is None: + error = "No geometry to compute Fit2D geometry." + pass + elif self.__detector is None: + error = "No detector defined. It is needed to compute the Fit2D geometry." + elif not geometry.isValid(checkWaveLength=False): + error = "The current geometry is not valid to compute the Fit2D one." + else: + pyFAIGeometry = self.__createPyfaiGeometry() + try: + result = pyFAIGeometry.getFit2D() + except Exception: + error = "This geometry can't be modelized with Fit2D." + else: + distance = result["directDist"] + centerX = result["centerX"] + centerY = result["centerY"] + tilt = result["tilt"] + tiltPlan = result["tiltPlanRotation"] + + self._fit2dError.setVisible(error is not None) + self._fit2dError.setText(error) + self.__fit2dGeometry.lockSignals() + self.__fit2dGeometry.distance().setValue(distance) + self.__fit2dGeometry.centerX().setValue(centerX) + self.__fit2dGeometry.centerY().setValue(centerY) + self.__fit2dGeometry.tilt().setValue(tilt) + self.__fit2dGeometry.tiltPlan().setValue(tiltPlan) + self.__fit2dGeometry.unlockSignals() + self.__updatingModel = False diff --git a/pyFAI/resources/gui/calibration-result.ui b/pyFAI/resources/gui/calibration-result.ui index ff73d1912..e34be4181 100644 --- a/pyFAI/resources/gui/calibration-result.ui +++ b/pyFAI/resources/gui/calibration-result.ui @@ -7,7 +7,7 @@ 0 0 715 - 618 + 663 @@ -32,7 +32,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -63,11 +72,7 @@ - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + @@ -80,9 +85,6 @@ 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - @@ -100,9 +102,6 @@ 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - @@ -120,9 +119,6 @@ 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - @@ -168,6 +164,366 @@ Geometry + + + + 0 + + + + pyFAI + + + + + + PONI 2: + + + + + + + + + + + + + + PONI 1: + + + + + + + + 0 + 0 + + + + m + + + + + + + + 0 + 0 + + + + rad + + + + + + + + 0 + 0 + + + + rad + + + + + + + Rotation 1: + + + + + + + Rotation 3: + + + + + + + + 0 + 0 + + + + m + + + + + + + Rotation 2: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Distance: + + + + + + + + 0 + 0 + + + + m + + + + + + + + 0 + 0 + + + + rad + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Fit2D + + + + + + + + + + + + + Direct distance: + + + + + + + + + + + + + + Center Y: + + + + + + + + 0 + 0 + + + + px + + + + + + + + 0 + 0 + + + + px + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + ° + + + + + + + Tilt plan rotation: + + + + + + + + 0 + 0 + + + + ° + + + + + + + Center X: + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + m + + + + + + + Tilt: + + + + + + + QLabel {color: red} + + + + + + true + + + + + + + @@ -240,6 +596,11 @@
pyFAI.gui.tasks.IntegrationTask
1 + + UnitLabel + QLabel +
pyFAI.gui.widgets.UnitLabel
+
_radialUnit @@ -250,7 +611,6 @@ _customMethodButton _displayMask _integrateButton - _savePoniButton _nextStep From 689c622e2ed401fd7461e2304bda53ee5b74c31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 11:27:46 +0100 Subject: [PATCH 02/21] CSC fall off ... --- pyFAI/azimuthalIntegrator.py | 4 ++-- pyFAI/gui/dialog/IntegrationMethodDialog.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index 24801f34d..6238f6260 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -30,7 +30,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "06/01/2023" +__date__ = "31/01/2023" __status__ = "stable" __docformat__ = 'restructuredtext' @@ -2218,7 +2218,7 @@ def integrate2d_ng(self, data, npt_rad, npt_azim=360, norm2d = None var2d = None - if method.algo_lower in ("csr", "lut"): + if method.algo_lower in ("csr", "csc", "lut"): intpl = None cython_method = IntegrationMethod.select_method(method.dimension, method.split_lower, method.algo_lower, "cython")[0] if cython_method not in self.engines: diff --git a/pyFAI/gui/dialog/IntegrationMethodDialog.py b/pyFAI/gui/dialog/IntegrationMethodDialog.py index 906e4d887..4f29bf65d 100644 --- a/pyFAI/gui/dialog/IntegrationMethodDialog.py +++ b/pyFAI/gui/dialog/IntegrationMethodDialog.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "16/10/2020" +__date__ = "31/01/2023" from silx.gui import qt from silx.gui import icons @@ -46,6 +46,7 @@ class IntegrationMethodWidget(qt.QWidget): "histogram": "Histogram", "lut": "LUT", "csr": "CSR", + "csc": "CSC", "python": "Python", "cython": "Cython", "opencl": "OpenCL", @@ -66,6 +67,7 @@ class IntegrationMethodWidget(qt.QWidget): "histogram": "Direct integration method with the lowest memory footprint but slower", "lut": "Sparse matrix based integration using a look-up table. Long initalization time and highest memory usage. Often slower than CSR", "csr": "Sparse matrix based integration using a a CSR (compressed sparse row) representation. Long initalization time and high memory usage, but the fastest for processing", + "csc": "Sparse matrix based integration using a a CSC (compressed sparse column) representation. Long initalization time and high memory usage. Single-threaded but faster processsing than histogram", "python": "Use a pure Python/numpy/scipy implementation. Slow but portable", "cython": "Use a Cython/C/C++ implementation. Fast and reliable default methods", "opencl": "Use an OpenCL implementation based on hardware accelerators. Fastest but hardware/driver dependant", From 2a7251e7539346de23a3b89be4c9b05c66ef2d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 11:45:10 +0100 Subject: [PATCH 03/21] Fix CSC integrator in 2D --- pyFAI/ext/splitBBoxCSC.pyx | 4 ++-- pyFAI/ext/splitPixelFullCSC.pyx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyFAI/ext/splitBBoxCSC.pyx b/pyFAI/ext/splitBBoxCSC.pyx index 6db984a1e..8ef51bf6a 100644 --- a/pyFAI/ext/splitBBoxCSC.pyx +++ b/pyFAI/ext/splitBBoxCSC.pyx @@ -37,7 +37,7 @@ Serial implementation based on a sparse CSC matrix multiplication __author__ = "Jérôme Kieffer" __contact__ = "Jerome.kieffer@esrf.fr" -__date__ = "08/09/2022" +__date__ = "31/01/2023" __status__ = "stable" __license__ = "MIT" @@ -184,7 +184,7 @@ class HistoBBox2d(CscIntegrator, SplitBBoxIntegrator): self.bin_centers1 = numpy.linspace(self.pos1_min + 0.5 * self.delta1, self.pos1_max - 0.5 * self.delta1, self.bins[1]) - csc = self.calc_lut_2d().to_csr() + csc = sparse.csr_matrix(self.calc_lut_2d().to_csr()).tocsc() #Call the constructor of the parent class CscIntegrator.__init__(self, (csc.data, csc.indices, csc.indptr), self.size, numpy.prod(bins), empty or 0.0) self.lut_checksum = crc32(self.data) diff --git a/pyFAI/ext/splitPixelFullCSC.pyx b/pyFAI/ext/splitPixelFullCSC.pyx index fafa2c69b..13f06d4b4 100644 --- a/pyFAI/ext/splitPixelFullCSC.pyx +++ b/pyFAI/ext/splitPixelFullCSC.pyx @@ -35,7 +35,7 @@ Sparse matrix represented using the CompressedSparseColumn. __author__ = "Jérôme Kieffer" __contact__ = "Jerome.kieffer@esrf.fr" -__date__ = "09/09/2022" +__date__ = "31/01/2023" __status__ = "stable" __license__ = "MIT" @@ -169,7 +169,7 @@ class FullSplitCSC_2d(CscIntegrator, FullSplitIntegrator): csc = sparse.csr_matrix(self.calc_lut_2d().to_csr()).tocsc() #Call the constructor of the parent class - CscIntegrator.__init__(self, (csc.data, csc.indices, csc.indptr), self.pos.shape[0], self.bins, empty or 0.0) + CscIntegrator.__init__(self, (csc.data, csc.indices, csc.indptr), self.pos.shape[0], numpy.prod(bins), empty or 0.0) self.lut_checksum = crc32(self.data) self.lut_nbytes = sum([i.nbytes for i in self.lut]) From 8a2e08db599c4d751f8a27f0c0ce0be54f9e98bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 14:00:28 +0100 Subject: [PATCH 04/21] leave more room for plot --- pyFAI/resources/gui/calibration-result.ui | 100 +++++++++++----------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/pyFAI/resources/gui/calibration-result.ui b/pyFAI/resources/gui/calibration-result.ui index e34be4181..daa9e8f15 100644 --- a/pyFAI/resources/gui/calibration-result.ui +++ b/pyFAI/resources/gui/calibration-result.ui @@ -6,8 +6,8 @@ 0 0 - 715 - 663 + 993 + 785
@@ -49,36 +49,26 @@ Integration parameters - - - + + + - Integrate + Radial unit: - - - - Azimuthal points: - - + + - - + + - ... + Radial points: - - - - - - - - + + 0 @@ -87,15 +77,15 @@ - - + + - Pixel splitting: + Azimuthal points: - - + + 0 @@ -104,15 +94,15 @@ - + Polarization factor: - - + + 0 @@ -121,31 +111,38 @@ - - + + - Display mask overlay - - - true + Pixel splitting: - - - - Radial points: - - + + + + + + + + + ... + + + + - - + + - Radial unit: + Display mask overlay + + + true - + color:red; @@ -155,6 +152,13 @@ + + + + Integrate + + + @@ -167,7 +171,7 @@ - 0 + 1 From 83143f8eda562e1a6dd6f80b1b143bb4b1de1da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 14:01:05 +0100 Subject: [PATCH 05/21] shorten label --- pyFAI/units.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyFAI/units.py b/pyFAI/units.py index 4f0a2fcaa..22acb1cd6 100644 --- a/pyFAI/units.py +++ b/pyFAI/units.py @@ -37,7 +37,7 @@ __contact__ = "picca@synchrotron-soleil.fr" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "22/01/2021" +__date__ = "31/01/2023" __status__ = "production" __docformat__ = 'restructuredtext' @@ -251,7 +251,7 @@ def eq_q(x, y, z, wavelength): center="rd2Array", delta="deltaRd2", scale=0.01, - label=r"Reciprocal spacing squared $d^{*2}$ ($\AA^{-2}$)", + label=r"Recip. spacing sq. $^{*2}$ ($\AA^{-2}$)", equation=lambda x, y, z, wavelength: (eq_q(x, y, z, wavelength) / (2.0 * numpy.pi)) ** 2, formula=formula_d2, short_name="d^{*2}", @@ -261,7 +261,7 @@ def eq_q(x, y, z, wavelength): center="rd2Array", delta="deltaRd2", scale=1.0, - label=r"Reciprocal spacing squared $d^{*2}$ ($nm^{-2}$)", + label=r"Recip. spacing sq. $^{*2}$ ($nm^{-2}$)", equation=lambda x, y, z, wavelength: (eq_q(x, y, z, wavelength) / (2.0 * numpy.pi)) ** 2, formula=formula_d2, short_name="d^{*2}", From f2c04e0561e3aafaa8920bb8bd4be06ab5378ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 14:37:10 +0100 Subject: [PATCH 06/21] typo !!! --- pyFAI/utils/stringutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyFAI/utils/stringutil.py b/pyFAI/utils/stringutil.py index ba26098de..04fd71641 100644 --- a/pyFAI/utils/stringutil.py +++ b/pyFAI/utils/stringutil.py @@ -28,7 +28,7 @@ __author__ = "valentin.valls@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "22/03/2021" +__date__ = "31/01/2023" __status__ = "development" __docformat__ = 'restructuredtext' @@ -87,7 +87,7 @@ def latex_to_unicode(string): string = string.replace("^{o}", u"°") string = string.replace("\\AA", u"Å") string = string.replace("log10", u"log₁₀") - string = string.replace("^{*2}", u"d*²") + string = string.replace("^{*2}", u"*²") return string From 7a5413307888599e5d9f68606bf7f1728a8547ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 14:38:45 +0100 Subject: [PATCH 07/21] revert modification (error was elsewhere) --- pyFAI/units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyFAI/units.py b/pyFAI/units.py index 22acb1cd6..74e3f7e9b 100644 --- a/pyFAI/units.py +++ b/pyFAI/units.py @@ -251,7 +251,7 @@ def eq_q(x, y, z, wavelength): center="rd2Array", delta="deltaRd2", scale=0.01, - label=r"Recip. spacing sq. $^{*2}$ ($\AA^{-2}$)", + label=r"Recip. spacing sq. $d^{*2}$ ($\AA^{-2}$)", equation=lambda x, y, z, wavelength: (eq_q(x, y, z, wavelength) / (2.0 * numpy.pi)) ** 2, formula=formula_d2, short_name="d^{*2}", @@ -261,7 +261,7 @@ def eq_q(x, y, z, wavelength): center="rd2Array", delta="deltaRd2", scale=1.0, - label=r"Recip. spacing sq. $^{*2}$ ($nm^{-2}$)", + label=r"Recip. spacing sq. $d^{*2}$ ($nm^{-2}$)", equation=lambda x, y, z, wavelength: (eq_q(x, y, z, wavelength) / (2.0 * numpy.pi)) ** 2, formula=formula_d2, short_name="d^{*2}", From 3bccd7e5406ee4ff97a8c601250f9cbde5eca776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Tue, 31 Jan 2023 14:39:21 +0100 Subject: [PATCH 08/21] consistency --- pyFAI/gui/tasks/IntegrationTask.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyFAI/gui/tasks/IntegrationTask.py b/pyFAI/gui/tasks/IntegrationTask.py index f770d8b62..ff9f6a9ba 100644 --- a/pyFAI/gui/tasks/IntegrationTask.py +++ b/pyFAI/gui/tasks/IntegrationTask.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "27/01/2023" +__date__ = "31/01/2023" import logging import numpy @@ -919,7 +919,7 @@ def _initGui(self): self._radialPoints.setValidator(positiveValidator) self._azimuthalPoints.setValidator(positiveValidator) - self._radialUnit.setUnits(pyFAI.units.RADIAL_UNITS.values()) + self._radialUnit.setUnits(core_units.RADIAL_UNITS.values()) self.__polarizationModel = None self._polarizationFactorCheck.clicked[bool].connect(self.__polarizationFactorChecked) self.widgetShow.connect(self.__widgetShow) From 23c1baac9ff25f00adb4f083ca8d5b30b3f365e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 11:25:51 +0100 Subject: [PATCH 09/21] fix deprecation warning --- pyFAI/app/calib2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyFAI/app/calib2.py b/pyFAI/app/calib2.py index 034b9a9ae..9ed2bb15c 100755 --- a/pyFAI/app/calib2.py +++ b/pyFAI/app/calib2.py @@ -28,7 +28,7 @@ __contact__ = "valentin.valls@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "21/01/2020" +__date__ = "24/02/2023" __status__ = "production" import os @@ -569,7 +569,7 @@ def main(): silx.config.DEFAULT_PLOT_BACKEND = "opengl" # Make sure matplotlib is loaded first by silx - import silx.gui.plot.matplotlib + import silx.gui.utils.matplotlib from pyFAI.gui.CalibrationWindow import CalibrationWindow from pyFAI.gui.CalibrationContext import CalibrationContext From 035736c5b4610c32b71c898b1dc5e16ee8c9faa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 11:42:12 +0100 Subject: [PATCH 10/21] close #1808 --- pyFAI/gui/dialog/GeometryDialog.py | 4 +- pyFAI/gui/tasks/IntegrationTask.py | 233 ++------------ pyFAI/resources/gui/calibration-result.ui | 366 +--------------------- 3 files changed, 29 insertions(+), 574 deletions(-) diff --git a/pyFAI/gui/dialog/GeometryDialog.py b/pyFAI/gui/dialog/GeometryDialog.py index 0dcc1e7d3..3a9dea5a8 100644 --- a/pyFAI/gui/dialog/GeometryDialog.py +++ b/pyFAI/gui/dialog/GeometryDialog.py @@ -23,9 +23,9 @@ # # ###########################################################################*/ -__authors__ = ["V. Valls"] +__authors__ = ["V. Valls", "J. Kieffer"] __license__ = "MIT" -__date__ = "03/02/2023" +__date__ = "24/02/2023" from silx.gui import qt from ..model.GeometryModel import GeometryModel diff --git a/pyFAI/gui/tasks/IntegrationTask.py b/pyFAI/gui/tasks/IntegrationTask.py index ff9f6a9ba..ba02a6faf 100644 --- a/pyFAI/gui/tasks/IntegrationTask.py +++ b/pyFAI/gui/tasks/IntegrationTask.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (C) 2016-2018 European Synchrotron Radiation Facility +# Copyright (C) 2016-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 @@ -23,9 +23,9 @@ # # ###########################################################################*/ -__authors__ = ["V. Valls"] +__authors__ = ["V. Valls", "J. Kieffer"] __license__ = "MIT" -__date__ = "31/01/2023" +__date__ = "24/02/2023" import logging import numpy @@ -40,8 +40,8 @@ from pyFAI.azimuthalIntegrator import AzimuthalIntegrator from ..utils import unitutils from ..model.DataModel import DataModel -from ..model.GeometryModel import GeometryModel -from ..model.Fit2dGeometryModel import Fit2dGeometryModel +# from ..model.GeometryModel import GeometryModel +# from ..model.Fit2dGeometryModel import Fit2dGeometryModel from ..widgets.QuantityLabel import QuantityLabel from ..CalibrationContext import CalibrationContext from ... import units as core_units @@ -933,81 +933,6 @@ def _initGui(self): self._savePoniButton.clicked.connect(self.__saveAsPoni) - # For the - self.__geometry = GeometryModel() - self.__fit2dGeometry = Fit2dGeometryModel() - self.__detector = None - self.__originalGeometry = None - self.__updatingModel = False - - # Create shared units - angleUnit = DataModel() - angleUnit.setValue(units.Unit.RADIAN) - lengthUnit = DataModel() - lengthUnit.setValue(units.Unit.METER) - pixelUnit = DataModel() - pixelUnit.setValue(units.Unit.PIXEL) - - # Connect pyFAI widgets to units - self._pyfaiDistance.setDisplayedUnitModel(lengthUnit) - self._pyfaiDistance.setModelUnit(units.Unit.METER) - self._pyfaiDistanceUnit.setUnitModel(lengthUnit) - self._pyfaiDistanceUnit.setUnitEditable(True) - self._pyfaiPoni1.setDisplayedUnitModel(lengthUnit) - self._pyfaiPoni1.setModelUnit(units.Unit.METER) - self._pyfaiPoni1Unit.setUnitModel(lengthUnit) - self._pyfaiPoni1Unit.setUnitEditable(True) - self._pyfaiPoni2.setDisplayedUnitModel(lengthUnit) - self._pyfaiPoni2.setModelUnit(units.Unit.METER) - self._pyfaiPoni2Unit.setUnitModel(lengthUnit) - self._pyfaiPoni2Unit.setUnitEditable(True) - self._pyfaiRotation1.setDisplayedUnitModel(angleUnit) - self._pyfaiRotation1.setModelUnit(units.Unit.RADIAN) - self._pyfaiRotation1Unit.setUnitModel(angleUnit) - self._pyfaiRotation1Unit.setUnitEditable(True) - self._pyfaiRotation2.setDisplayedUnitModel(angleUnit) - self._pyfaiRotation2.setModelUnit(units.Unit.RADIAN) - self._pyfaiRotation2Unit.setUnitModel(angleUnit) - self._pyfaiRotation2Unit.setUnitEditable(True) - self._pyfaiRotation3.setDisplayedUnitModel(angleUnit) - self._pyfaiRotation3.setModelUnit(units.Unit.RADIAN) - self._pyfaiRotation3Unit.setUnitModel(angleUnit) - self._pyfaiRotation3Unit.setUnitEditable(True) - - # Connect fit2d widgets to units - self._fit2dDistance.setDisplayedUnit(units.Unit.MILLIMETER) - self._fit2dDistance.setModelUnit(units.Unit.MILLIMETER) - self._fit2dDistanceUnit.setUnit(units.Unit.MILLIMETER) - self._fit2dCenterX.setDisplayedUnitModel(pixelUnit) - self._fit2dCenterX.setModelUnit(units.Unit.PIXEL) - self._fit2dCenterXUnit.setUnit(units.Unit.PIXEL) - self._fit2dCenterY.setDisplayedUnitModel(pixelUnit) - self._fit2dCenterY.setModelUnit(units.Unit.PIXEL) - self._fit2dCenterYUnit.setUnit(units.Unit.PIXEL) - self._fit2dTilt.setDisplayedUnit(units.Unit.DEGREE) - self._fit2dTilt.setModelUnit(units.Unit.DEGREE) - self._fit2dTiltUnit.setUnit(units.Unit.DEGREE) - self._fit2dTiltPlan.setDisplayedUnit(units.Unit.DEGREE) - self._fit2dTiltPlan.setModelUnit(units.Unit.DEGREE) - self._fit2dTiltPlanUnit.setUnit(units.Unit.DEGREE) - - # Connect fit2d model-widget - self._fit2dDistance.setModel(self.__fit2dGeometry.distance()) - self._fit2dCenterX.setModel(self.__fit2dGeometry.centerX()) - self._fit2dCenterY.setModel(self.__fit2dGeometry.centerY()) - self._fit2dTilt.setModel(self.__fit2dGeometry.tilt()) - self._fit2dTiltPlan.setModel(self.__fit2dGeometry.tiltPlan()) - - self._pyfaiDistance.setModel(self.__geometry.distance()) - self._pyfaiPoni1.setModel(self.__geometry.poni1()) - self._pyfaiPoni2.setModel(self.__geometry.poni2()) - self._pyfaiRotation1.setModel(self.__geometry.rotation1()) - self._pyfaiRotation2.setModel(self.__geometry.rotation2()) - self._pyfaiRotation3.setModel(self.__geometry.rotation3()) - - self.__geometry.changed.connect(self.__updateFit2dFromPyfai) - self.__fit2dGeometry.changed.connect(self.__updatePyfaiFromFit2d) - super()._initGui() def __customIntegrationMethod(self): @@ -1066,7 +991,7 @@ def __widgetShow(self): self._integrateButton.executeCallable() def __integrate(self): - self.__integrationProcess = IntegrationProcess(self.model(), self.__geometry) + self.__integrationProcess = IntegrationProcess(self.model(), self.geometryModel()) self.__integrationProcess.setMethod(self.__method) if self.__integrationResetZoomPolicy is not None: @@ -1147,7 +1072,8 @@ def __saveAsPoni(self): dialog = createSaveDialog(self, "Save as PONI file", poni=True) # Disable the warning as the data is append to the file dialog.setOption(qt.QFileDialog.DontConfirmOverwrite, True) - poniFile = self.model().experimentSettingsModel().poniFile() + model = self.model() + poniFile = model.experimentSettingsModel().poniFile() previousPoniFile = poniFile.value() if previousPoniFile is not None: dialog.selectFile(previousPoniFile) @@ -1163,15 +1089,18 @@ def __saveAsPoni(self): with poniFile.lockContext(): poniFile.setValue(filename) + geometry = self._geometryTabs.geometryModel() + experimentSettingsModel = model.experimentSettingsModel() + detector = experimentSettingsModel.detector() pyfaiGeometry = pyFAI.geometry.Geometry( - dist=self.__geometry.distance().value(), - poni1=self.__geometry.poni1().value(), - poni2=self.__geometry.poni2().value(), - rot1=self.__geometry.rotation1().value(), - rot2=self.__geometry.rotation2().value(), - rot3=self.__geometry.rotation3().value(), - wavelength=self.__geometry.wavelength().value(), - detector=self.__detector + dist=geometry.distance().value(), + poni1=geometry.poni1().value(), + poni2=geometry.poni2().value(), + rot1=geometry.rotation1().value(), + rot2=geometry.rotation2().value(), + rot3=geometry.rotation3().value(), + wavelength=geometry.wavelength().value(), + detector=detector ) try: writer = ponifile.PoniFile(pyfaiGeometry) @@ -1184,125 +1113,7 @@ def __saveAsPoni(self): MessageBox.exception(self, "Error while saving poni file", e, _logger) def __updateDisplayedGeometry(self): + "Called after the fit" experimentSettingsModel = self.model().experimentSettingsModel() - self.__detector = experimentSettingsModel.detector() - self.__geometry.setFrom(self.model().fittedGeometry()) - - def __createPyfaiGeometry(self): - geometry = self.__geometry - if not geometry.isValid(checkWaveLength=False): - raise RuntimeError("The geometry is not valid") - dist = geometry.distance().value() - poni1 = geometry.poni1().value() - poni2 = geometry.poni2().value() - rot1 = geometry.rotation1().value() - rot2 = geometry.rotation2().value() - rot3 = geometry.rotation3().value() - wavelength = geometry.wavelength().value() - result = pyFAI.geometry.Geometry(dist=dist, - poni1=poni1, - poni2=poni2, - rot1=rot1, - rot2=rot2, - rot3=rot3, - detector=self.__detector, - wavelength=wavelength) - return result - - def __updatePyfaiFromFit2d(self): - if self.__updatingModel: - return - self.__updatingModel = True - geometry = self.__fit2dGeometry - error = None - distance = None - poni1 = None - poni2 = None - rotation1 = None - rotation2 = None - rotation3 = None - - if geometry is None: - error = "No geometry to compute pyFAI geometry." - pass - elif self.__detector is None: - error = "No detector defined. It is needed to compute the pyFAI geometry." - elif not geometry.isValid(): - error = "The current geometry is not valid to compute the pyFAI one." - else: - pyFAIGeometry = pyFAI.geometry.Geometry(detector=self.__detector) - try: - f2d_distance = geometry.distance().value() - f2d_centerX = geometry.centerX().value() - f2d_centerY = geometry.centerY().value() - f2d_tiltPlan = geometry.tiltPlan().value() - f2d_tilt = geometry.tilt().value() - pyFAIGeometry.setFit2D(directDist=f2d_distance, - centerX=f2d_centerX, - centerY=f2d_centerY, - tilt=f2d_tilt, - tiltPlanRotation=f2d_tiltPlan) - except Exception: - error = "This geometry can't be modelized with pyFAI." - else: - distance = pyFAIGeometry.dist - poni1 = pyFAIGeometry.poni1 - poni2 = pyFAIGeometry.poni2 - rotation1 = pyFAIGeometry.rot1 - rotation2 = pyFAIGeometry.rot2 - rotation3 = pyFAIGeometry.rot3 - - self._fit2dError.setVisible(error is not None) - self._fit2dError.setText(error) - self.__geometry.lockSignals() - self.__geometry.distance().setValue(distance) - self.__geometry.poni1().setValue(poni1) - self.__geometry.poni2().setValue(poni2) - self.__geometry.rotation1().setValue(rotation1) - self.__geometry.rotation2().setValue(rotation2) - self.__geometry.rotation3().setValue(rotation3) - self.__geometry.unlockSignals() - self.__updatingModel = False - - def __updateFit2dFromPyfai(self): - if self.__updatingModel: - return - self.__updatingModel = True - geometry = self.__geometry - error = None - distance = None - centerX = None - centerY = None - tiltPlan = None - tilt = None - - if geometry is None: - error = "No geometry to compute Fit2D geometry." - pass - elif self.__detector is None: - error = "No detector defined. It is needed to compute the Fit2D geometry." - elif not geometry.isValid(checkWaveLength=False): - error = "The current geometry is not valid to compute the Fit2D one." - else: - pyFAIGeometry = self.__createPyfaiGeometry() - try: - result = pyFAIGeometry.getFit2D() - except Exception: - error = "This geometry can't be modelized with Fit2D." - else: - distance = result["directDist"] - centerX = result["centerX"] - centerY = result["centerY"] - tilt = result["tilt"] - tiltPlan = result["tiltPlanRotation"] - - self._fit2dError.setVisible(error is not None) - self._fit2dError.setText(error) - self.__fit2dGeometry.lockSignals() - self.__fit2dGeometry.distance().setValue(distance) - self.__fit2dGeometry.centerX().setValue(centerX) - self.__fit2dGeometry.centerY().setValue(centerY) - self.__fit2dGeometry.tilt().setValue(tilt) - self.__fit2dGeometry.tiltPlan().setValue(tiltPlan) - self.__fit2dGeometry.unlockSignals() - self.__updatingModel = False + self._geometryTabs.setDetector(experimentSettingsModel.detector()) + self._geometryTabs.setGeometryModel(self.model().fittedGeometry()) diff --git a/pyFAI/resources/gui/calibration-result.ui b/pyFAI/resources/gui/calibration-result.ui index daa9e8f15..c48ac9339 100644 --- a/pyFAI/resources/gui/calibration-result.ui +++ b/pyFAI/resources/gui/calibration-result.ui @@ -169,364 +169,7 @@ - - - 1 - - - - pyFAI - - - - - - PONI 2: - - - - - - - - - - - - - - PONI 1: - - - - - - - - 0 - 0 - - - - m - - - - - - - - 0 - 0 - - - - rad - - - - - - - - 0 - 0 - - - - rad - - - - - - - Rotation 1: - - - - - - - Rotation 3: - - - - - - - - 0 - 0 - - - - m - - - - - - - Rotation 2: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Distance: - - - - - - - - 0 - 0 - - - - m - - - - - - - - 0 - 0 - - - - rad - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Fit2D - - - - - - - - - - - - - Direct distance: - - - - - - - - - - - - - - Center Y: - - - - - - - - 0 - 0 - - - - px - - - - - - - - 0 - 0 - - - - px - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - ° - - - - - - - Tilt plan rotation: - - - - - - - - 0 - 0 - - - - ° - - - - - - - Center X: - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - m - - - - - - - Tilt: - - - - - - - QLabel {color: red} - - - - - - true - - - - - - + @@ -601,9 +244,10 @@ 1 - UnitLabel - QLabel -
pyFAI.gui.widgets.UnitLabel
+ GeometryTabs + QWidget +
pyFAI.gui.widgets.GeometryTabs
+ 1
From 31af5ee9dbab20eb101ec1de41fe007bf292cf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 11:54:19 +0100 Subject: [PATCH 11/21] Revert 91e89486f3ccec1d0773dea76892a04a6cf7cf48 --- pyFAI/azimuthalIntegrator.py | 62 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index d20f6f394..e2a6155e2 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -30,7 +30,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "22/02/2023" +__date__ = "24/02/2023" __status__ = "stable" __docformat__ = 'restructuredtext' @@ -2218,7 +2218,7 @@ def integrate2d_ng(self, data, npt_rad, npt_azim=360, norm2d = None var2d = None - if method.algo_lower in ("csr", "lut"): + if method.algo_lower in ("csr", "csc", "lut"): intpl = None cython_method = IntegrationMethod.select_method(method.dimension, method.split_lower, method.algo_lower, "cython")[0] if cython_method not in self.engines: @@ -2898,22 +2898,22 @@ def medfilt1d(self, data, npt_rad=1024, npt_azim=512, result._set_normalization_factor(normalization_factor) return result - def _sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, - correctSolidAngle=True, polarization_factor=None, - radial_range=None, azimuth_range=None, - dark=None, flat=None, - method="splitpixel", unit=units.Q, - thres=3, max_iter=5, dummy=None, delta_dummy=None, - mask=None, normalization_factor=1.0, metadata=None): - """Perform the 2D integration and perform a sigm-clipping iterative - filter along each row. see the doc of scipy.stats.sigmaclip for the - options. + def sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, + correctSolidAngle=True, polarization_factor=None, + radial_range=None, azimuth_range=None, + dark=None, flat=None, + method=("full", "histogram", "cython"), unit=units.Q, + thres=3, max_iter=5, dummy=None, delta_dummy=None, + mask=None, normalization_factor=1.0, metadata=None, + safe=True, **kwargs): + """Perform first a 2D integration and then an iterative sigma-clipping + filter along each row. See the doc of scipy.stats.sigmaclip for the + options `thres` and `max_iter`. :param data: input image as numpy array - :param npt_rad: number of radial points + :param npt_rad: number of radial points (alias: npt) :param npt_azim: number of azimuthal points - :param bool correctSolidAngle: correct for solid angle of each pixel - if True + :param bool correctSolidAngle: correct for solid angle of each pixel when set :param float polarization_factor: polarization factor between -1 (vertical) and +1 (horizontal). @@ -2928,13 +2928,18 @@ def _sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, :param ndarray flat: flat field image :param unit: unit to be used for integration :param method: pathway for integration and sort - :param thres: cut-off for n*sigma: discard any values with (I-)/sigma > thres. + :param thres: cut-off for n*sigma: discard any values with `|I-| > thres*σ`. The threshold can be a 2-tuple with sigma_low and sigma_high. - :param max_iter: maximum number of iterations :param mask: masked out pixels array + :param max_iter: maximum number of iterations + :param mask: masked out pixels array :param float normalization_factor: Value of a normalization monitor :param metadata: any other metadata, :type metadata: JSON serializable dict + :param safe: unset to save some checks on sparse matrix shape/content. + :kwargs: unused, just for signature compatibility when used within Worker. :return: Integrate1D like result like + + Nota: The initial 2D-integration requires pixel splitting """ # We use NaN as dummies if dummy is None: @@ -2963,7 +2968,8 @@ def _sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, dummy=dummy, delta_dummy=delta_dummy, correctSolidAngle=correctSolidAngle, polarization_factor=polarization_factor, - normalization_factor=normalization_factor) + normalization_factor=normalization_factor, + safe=safe) image = res2d.intensity if (method.impl_lower == "opencl"): if (method.algo_lower == "csr") and \ @@ -3046,6 +3052,8 @@ def _sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, result._set_normalization_factor(normalization_factor) return result + _sigma_clip_legacy = sigma_clip_legacy + def sigma_clip_ng(self, data, npt=1024, correctSolidAngle=True, @@ -3076,8 +3084,9 @@ def sigma_clip_ng(self, data, ``|I - | < thres * std(I)`` - This enforces a gaussian distibution and is very good at extracting - background or amorphous isotropic scattering out of Bragg peaks. + This enforces a symmetric, bell-shaped distibution (i.e. gaussian-like) + and is very good at extracting background or amorphous isotropic scattering + out of Bragg peaks. :param data: input image as numpy array :param npt_rad: number of radial points @@ -3108,9 +3117,11 @@ def sigma_clip_ng(self, data, :param safe: set to False to skip some tests :return: Integrate1D like result like - The difference with the previous version is that there is no 2D regrouping, hence this is faster. - The standard deviation is usually smaller than previously and the signal cleaner. It is also slightly faster. - + The difference with the previous `sigma_clip_legacy` implementation is that there is no 2D regrouping. + Pixel splitting should be avoided with this implementation. + The standard deviation is usually smaller than previously and the signal cleaner. + It is also slightly faster. + The case neither `error_model`, nor `variance` is provided, fall-back on a poissonian model. """ @@ -3319,10 +3330,7 @@ def sigma_clip_ng(self, data, result._set_error_model(error_model) return result - @deprecated(reason="will be replaced by `sigma_clip_ng` in version 0.23.0. Please use either `_sigma_clip_legacy` for full compatibility or upgrade your code to accomodate the new API", - replacement="sigma_clip_ng", since_version="0.21.0", only_once=True, skip_backtrace_count=1, deprecated_since="0.22.0") - def sigma_clip(self, *args, **kwargs): - return self._sigma_clip_legacy(*args, **kwargs) + sigma_clip = self._sigma_clip_ng def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="splitpixel", percentile=50, mask=None, restore_mask=True): From a9a445e8596a7fb880bd2d92525c9adc3b46317d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:54:57 +0000 Subject: [PATCH 12/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyFAI/azimuthalIntegrator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index e2a6155e2..7f68c9cde 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -2930,7 +2930,7 @@ def sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, :param method: pathway for integration and sort :param thres: cut-off for n*sigma: discard any values with `|I-| > thres*σ`. The threshold can be a 2-tuple with sigma_low and sigma_high. - :param max_iter: maximum number of iterations + :param max_iter: maximum number of iterations :param mask: masked out pixels array :param float normalization_factor: Value of a normalization monitor :param metadata: any other metadata, @@ -2938,7 +2938,7 @@ def sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, :param safe: unset to save some checks on sparse matrix shape/content. :kwargs: unused, just for signature compatibility when used within Worker. :return: Integrate1D like result like - + Nota: The initial 2D-integration requires pixel splitting """ # We use NaN as dummies @@ -3084,8 +3084,8 @@ def sigma_clip_ng(self, data, ``|I - | < thres * std(I)`` - This enforces a symmetric, bell-shaped distibution (i.e. gaussian-like) - and is very good at extracting background or amorphous isotropic scattering + This enforces a symmetric, bell-shaped distibution (i.e. gaussian-like) + and is very good at extracting background or amorphous isotropic scattering out of Bragg peaks. :param data: input image as numpy array @@ -3121,7 +3121,7 @@ def sigma_clip_ng(self, data, Pixel splitting should be avoided with this implementation. The standard deviation is usually smaller than previously and the signal cleaner. It is also slightly faster. - + The case neither `error_model`, nor `variance` is provided, fall-back on a poissonian model. """ From ff43fc5812e34b3423e32ac59798d4725a87e627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 11:56:02 +0100 Subject: [PATCH 13/21] typo --- pyFAI/azimuthalIntegrator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index e2a6155e2..6ca2d379a 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -3330,7 +3330,7 @@ def sigma_clip_ng(self, data, result._set_error_model(error_model) return result - sigma_clip = self._sigma_clip_ng + sigma_clip = _sigma_clip_ng def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="splitpixel", percentile=50, mask=None, restore_mask=True): From c0d1e7069eaaaa90d095209ba107d85d7a2ce86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 11:58:26 +0100 Subject: [PATCH 14/21] revert minor regressions --- pyFAI/azimuthalIntegrator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index dab975e30..222f11bd2 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -2911,7 +2911,7 @@ def sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, options `thres` and `max_iter`. :param data: input image as numpy array - :param npt_rad: number of radial points (alias: npt) + :param npt_rad: number of radial points (alias: npt) :param npt_azim: number of azimuthal points :param bool correctSolidAngle: correct for solid angle of each pixel when set :param float polarization_factor: polarization factor between -1 (vertical) @@ -2941,6 +2941,9 @@ def sigma_clip_legacy(self, data, npt_rad=1024, npt_azim=512, Nota: The initial 2D-integration requires pixel splitting """ + # compatibility layer with sigma_clip_ng + if "npt" in kwargs: + npt_rad = kwargs["npt"] # We use NaN as dummies if dummy is None: dummy = numpy.NaN From 44d7c5cf52909ba3fcd290f2114b548a6ba3ce6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 12:02:12 +0100 Subject: [PATCH 15/21] typos --- pyFAI/azimuthalIntegrator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyFAI/azimuthalIntegrator.py b/pyFAI/azimuthalIntegrator.py index 222f11bd2..31fb0fdbd 100644 --- a/pyFAI/azimuthalIntegrator.py +++ b/pyFAI/azimuthalIntegrator.py @@ -3085,7 +3085,7 @@ def sigma_clip_ng(self, data, Keep only pixels with intensty: - ``|I - | < thres * std(I)`` + ``|I - | < thres * σ(I)`` This enforces a symmetric, bell-shaped distibution (i.e. gaussian-like) and is very good at extracting background or amorphous isotropic scattering @@ -3333,7 +3333,7 @@ def sigma_clip_ng(self, data, result._set_error_model(error_model) return result - sigma_clip = _sigma_clip_ng + sigma_clip = sigma_clip_ng def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="splitpixel", percentile=50, mask=None, restore_mask=True): From 287de96442a84bf4581282c41638ea1f65df8aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 14:57:17 +0100 Subject: [PATCH 16/21] fix regression --- pyFAI/gui/tasks/IntegrationTask.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyFAI/gui/tasks/IntegrationTask.py b/pyFAI/gui/tasks/IntegrationTask.py index ba02a6faf..f4e958ee0 100644 --- a/pyFAI/gui/tasks/IntegrationTask.py +++ b/pyFAI/gui/tasks/IntegrationTask.py @@ -991,7 +991,8 @@ def __widgetShow(self): self._integrateButton.executeCallable() def __integrate(self): - self.__integrationProcess = IntegrationProcess(self.model(), self.geometryModel()) + geometry = self._geometryTabs.geometryModel() + self.__integrationProcess = IntegrationProcess(self.model(), geometry) self.__integrationProcess.setMethod(self.__method) if self.__integrationResetZoomPolicy is not None: From 525fc8faaac40cc8fcbd0a1cb260d1c6b2a5d757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 15:07:24 +0100 Subject: [PATCH 17/21] use histogram default intgeration method (faster initialization) --- pyFAI/gui/tasks/IntegrationTask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyFAI/gui/tasks/IntegrationTask.py b/pyFAI/gui/tasks/IntegrationTask.py index f4e958ee0..c883e1b2a 100644 --- a/pyFAI/gui/tasks/IntegrationTask.py +++ b/pyFAI/gui/tasks/IntegrationTask.py @@ -911,7 +911,7 @@ def _initGui(self): self.__integrationUpToDate = True self.__integrationResetZoomPolicy = None - method = method_registry.Method(666, "bbox", "csr", "cython", None) + method = method_registry.Method(666, "bbox", "histogram", "cython", None) self.__setMethod(method) positiveValidator = validators.IntegerAndEmptyValidator(self) From 89feccf11fa82a73ce15538ecae48f851fdf2fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 17:45:51 +0100 Subject: [PATCH 18/21] fix display of some units in calib2 --- pyFAI/units.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyFAI/units.py b/pyFAI/units.py index 74e3f7e9b..3ada4daca 100644 --- a/pyFAI/units.py +++ b/pyFAI/units.py @@ -37,7 +37,7 @@ __contact__ = "picca@synchrotron-soleil.fr" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "31/01/2023" +__date__ = "24/02/2023" __status__ = "production" __docformat__ = 'restructuredtext' @@ -293,7 +293,7 @@ def eq_q(x, y, z, wavelength): register_radial_unit("log(1+q.A)_None", scale=1.0, - label=r"log(1+$q$.\AA)", + label=r"log(1+$q$.$\AA$)", equation=lambda x, y, z, wavelength: numpy.log1p(0.1 * eq_q(x, y, z, wavelength)), formula="log1p(4e-10*π/λ*sin(arctan2(sqrt(x * x + y * y), z)/2.0))", short_name=r"log(1+q.\AA)", @@ -309,7 +309,7 @@ def eq_q(x, y, z, wavelength): register_radial_unit("arcsinh(q.A)_None", scale=1.0, - label=r"arcsinh($q$.\AA)", + label=r"arcsinh($q$.$\AA$)", equation=lambda x, y, z, wavelength: numpy.arcsinh(0.1 * eq_q(x, y, z, wavelength)), formula="arcsinh(4e-10*π/λ*sin(arctan2(sqrt(x * x + y * y), z)/2.0))", short_name=r"arcsinh(q.\AA)", From 6cbece2011ab0cee78cda612ae8c4df1717e4a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 18:02:04 +0100 Subject: [PATCH 19/21] silent noisy test on windows+python3.9 --- pyFAI/test/test_csr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyFAI/test/test_csr.py b/pyFAI/test/test_csr.py index 852b88611..b0d3453d2 100644 --- a/pyFAI/test/test_csr.py +++ b/pyFAI/test/test_csr.py @@ -27,9 +27,9 @@ # THE SOFTWARE. """tests for Jon's geometry changes -FIXME : make some tests that the functions do what is expected """ - +import sys +import platform import unittest import numpy import logging @@ -182,11 +182,12 @@ def test_2d_splitbbox(self): self.assertTrue(numpy.allclose(res_csr[4].T, res_scipy.normalization), "count is same as normalization") self.assertTrue(numpy.allclose(res_csr[3].T, res_scipy.signal), "sum_data is almost the same") - @unittest.skipIf(UtilsTest.TEST_IS32_BIT, "test unreliable on 32bits processor") + @unittest.skipIf(UtilsTest.TEST_IS32_BIT or (any(platform.win32_ver()) and sys.version_info[:2] == (3, 9)), + "test unreliable on 32bits processor / Windows+Python3.9") def test_2d_nosplit(self): self.ai.reset() - result_histo = self.ai.integrate2d(self.data, self.N, unit="2th_deg", method="histogram") - result_nosplit = self.ai.integrate2d(self.data, self.N, unit="2th_deg", method="nosplit_csr") + result_histo = self.ai.integrate2d(self.data, self.N, unit="2th_deg", method=("no", "histogram", "cython")) + result_nosplit = self.ai.integrate2d(self.data, self.N, unit="2th_deg", method=("no", "csr", "cython")) self.assertTrue(numpy.allclose(result_histo.radial, result_nosplit.radial), " 2Th are the same") self.assertTrue(numpy.allclose(result_histo.azimuthal, result_nosplit.azimuthal, atol=1e-5), " Chi are the same") error = (result_histo.intensity - result_nosplit.intensity) From 3ac13682f8f6e0feabfac879d6eecb3bf504bbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 18:02:33 +0100 Subject: [PATCH 20/21] cleanup unused --- pyFAI/test/utilstest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyFAI/test/utilstest.py b/pyFAI/test/utilstest.py index d1fa151aa..dc6ee6477 100644 --- a/pyFAI/test/utilstest.py +++ b/pyFAI/test/utilstest.py @@ -28,7 +28,7 @@ __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "26/12/2022" +__date__ = "24/02/2023" PACKAGE = "pyFAI" @@ -38,7 +38,6 @@ import unittest import logging import shutil -import contextlib import tempfile import getpass import functools From ba5373a06e042920c8299804e243590384b7be7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Kieffer?= Date: Fri, 24 Feb 2023 18:05:08 +0100 Subject: [PATCH 21/21] Silent noisy test under windows --- pyFAI/test/test_azimuthal_integrator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyFAI/test/test_azimuthal_integrator.py b/pyFAI/test/test_azimuthal_integrator.py index b41a00e11..c1d7b5a28 100644 --- a/pyFAI/test/test_azimuthal_integrator.py +++ b/pyFAI/test/test_azimuthal_integrator.py @@ -32,7 +32,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "15/11/2022" +__date__ = "24/02/2023" import unittest import os @@ -452,7 +452,7 @@ def test_normalization_factor(self): ai = AzimuthalIntegrator(detector="Pilatus100k") ai.wavelength = 1e-10 methods = ["cython", "numpy", "lut", "csr", "splitpixel"] - if UtilsTest.opencl: + if UtilsTest.opencl and os.name != 'nt': methods.extend(["ocl_lut", "ocl_csr"]) ref1d = {} @@ -511,13 +511,13 @@ def test_empty(self): self.assertNotEqual(ref, target, "buggy test !") for m in ("LUT", "CSR", "CSC"): ai.integrate1d(img, 100, method=("no", m, "cython")) - for k,v in ai.engines.items(): + for k, v in ai.engines.items(): self.assertEqual(v.engine.empty, ref, k) ai.empty = target - for k,v in ai.engines.items(): + for k, v in ai.engines.items(): self.assertEqual(v.engine.empty, target, k) ai.empty = ref - for k,v in ai.engines.items(): + for k, v in ai.engines.items(): self.assertEqual(v.engine.empty, ref, k) @@ -628,7 +628,7 @@ def test_sigma_clip_ng(self): {"error_model":"azimuthal", "max_iter":3, "thres":0}, ): results = {} - for impl in ('python', # Python is already fixed, please fix the 2 others + for impl in ('python', # Python is already fixed, please fix the 2 others 'cython', # 'opencl' #TODO ): @@ -647,8 +647,8 @@ def test_sigma_clip_ng(self): ref = results['python'] for what, tol in (("radial", 1e-8), ("count", 1), - #("intensity", 1e-6), - #("sigma", 1e-6), + # ("intensity", 1e-6), + # ("sigma", 1e-6), ("sum_normalization", 1e-1), ("count", 1e-1)): for impl in results: