From 65063588101325b40e9865cefc067d0380e1ccb1 Mon Sep 17 00:00:00 2001 From: Jerome Kieffer Date: Wed, 10 Dec 2025 11:24:34 +0100 Subject: [PATCH 1/4] upgrade formating --- src/pyFAI/geometry/core.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/pyFAI/geometry/core.py b/src/pyFAI/geometry/core.py index e634fdcfa9..c46e6ab7c5 100644 --- a/src/pyFAI/geometry/core.py +++ b/src/pyFAI/geometry/core.py @@ -40,7 +40,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "04/12/2025" +__date__ = "10/12/2025" __status__ = "production" __docformat__ = "restructuredtext" @@ -2176,22 +2176,19 @@ def make_headers(self, type_="list"): f2d = self.getFit2D() res = [ "== pyFAI calibration ==", - "Distance Sample to Detector: %s m" % self.dist, - "PONI: %.3e, %.3e m" % (self.poni1, self.poni2), - "Rotations: %.6f %.6f %.6f rad" % (self.rot1, self.rot2, self.rot3), + f"Distance Sample to Detector: {self.dist} m", + f"PONI: {self.poni1:.3e}, {self.poni2:.3e} m", + f"Rotations: {self.rot1:.6f} {self.rot2:.6f} {self.rot3:.6f} rad", "", "== Fit2d calibration ==", - "Distance Sample-beamCenter: %.3f mm" % f2d["directDist"], - "Center: x=%.3f, y=%.3f pix" % (f2d["centerX"], f2d["centerY"]), - "Tilt: %.3f deg TiltPlanRot: %.3f deg" - % (f2d["tilt"], f2d["tiltPlanRotation"]), + f"Distance Sample-beamCenter: {f2d.directDist:.3f} mm", + f"Center: x={f2d.centerX:.3f}, y={f2d.centerY:.3f} pix", + f"Tilt: {f2d.tilt:.3f} deg TiltPlanRot: {f2d.tiltPlanRotation:.3f} deg", "", str(self.detector), " Detector has a mask: %s " % (self.detector.mask is not None), - " Detector has a dark current: %s " - % (self.detector.darkcurrent is not None), - " detector has a flat field: %s " - % (self.detector.flatfield is not None), + f" Detector has a dark current: {self.detector.darkcurrent is not None}", + f" Detector has a flat field: {self.detector.flatfield is not None}", "", ] From e309c467a726f241675198a2f5a9730004c1c949 Mon Sep 17 00:00:00 2001 From: Jerome Kieffer Date: Wed, 10 Dec 2025 11:25:01 +0100 Subject: [PATCH 2/4] TODO: update --- src/pyFAI/gui/IntegrationDialog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyFAI/gui/IntegrationDialog.py b/src/pyFAI/gui/IntegrationDialog.py index 563429fcc7..aeb600883b 100644 --- a/src/pyFAI/gui/IntegrationDialog.py +++ b/src/pyFAI/gui/IntegrationDialog.py @@ -36,7 +36,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "08/12/2025" +__date__ = "10/12/2025" __status__ = "development" import logging @@ -227,6 +227,8 @@ def createObserver(self, qtSafe=True): class IntegrationDialog(qt.QWidget): """Dialog to configure an azimuthal integration. + + TODO: this is not a Dialog !!! """ batchProcessRequested = qt.Signal() From 380200a4c7f2a0c20bb5658a19635a87401333f0 Mon Sep 17 00:00:00 2001 From: Jerome Kieffer Date: Wed, 10 Dec 2025 11:25:36 +0100 Subject: [PATCH 3/4] impement medfilt in GUI --- src/pyFAI/gui/widgets/WorkerConfigurator.py | 71 +++++---- .../resources/gui/worker-configurator.ui | 135 ++++++++++-------- 2 files changed, 120 insertions(+), 86 deletions(-) diff --git a/src/pyFAI/gui/widgets/WorkerConfigurator.py b/src/pyFAI/gui/widgets/WorkerConfigurator.py index 8feb169959..5a8ca4f906 100644 --- a/src/pyFAI/gui/widgets/WorkerConfigurator.py +++ b/src/pyFAI/gui/widgets/WorkerConfigurator.py @@ -33,7 +33,7 @@ __contact__ = "Jerome.Kieffer@ESRF.eu" __license__ = "MIT" __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" -__date__ = "05/12/2025" +__date__ = "10/12/2025" __status__ = "production" import logging @@ -75,6 +75,9 @@ class WorkerConfigurator(qt.QWidget): """Frame displaying integration configuration which can be used as input param of the ~`pyFAI.worker.Worker`. """ + INTEGRATOR_METHODS = {"integrate/average": "integrate1d_ng", + "sigma-clip": "sigma_clip_ng", + "median-filter": "medfilt1d_ng"} def __init__(self, parent=None): qt.QWidget.__init__(self, parent) @@ -140,7 +143,7 @@ def __init__(self, parent=None): self.radial_unit.setShortNameDisplay(True) self.radial_unit.model().changed.connect(self.__radialUnitUpdated) self.__radialUnitUpdated() - + # Fiber integration parameters fiber_units = [v for v in RADIAL_UNITS.values() if isinstance(v, UnitFiber)] self.ip_unit.setUnits(fiber_units) @@ -148,16 +151,16 @@ def __init__(self, parent=None): self.oop_unit.setUnits(fiber_units) self.oop_unit.model().setValue(RADIAL_UNITS["qoop_nm^-1"]) - + self.nbpt_ip.setValidator(npt_validator) self.nbpt_oop.setValidator(npt_validator) - + so_validator = qt.QIntValidator() so_validator.setBottom(1) so_validator.setTop(8) self.sample_orientation.setValidator(so_validator) # - + doubleOrEmptyValidator = validators.AdvancedDoubleValidator(self) doubleOrEmptyValidator.setAllowEmpty(True) self.normalization_factor.setValidator(doubleOrEmptyValidator) @@ -168,17 +171,17 @@ def __init__(self, parent=None): self.error_model.addItem(text, value) self.error_model.setCurrentIndex(0) - for value in ["integrate", - "sigma_clip_ng", - "medfilt_ng" - ]: - text = value + for text, value in self.INTEGRATOR_METHODS.items(): self.integrator_name.addItem(text, value) self.integrator_name.setCurrentIndex(0) self.sigmaclip_threshold.setValidator(qt.QDoubleValidator(0.0, 100.0, 1)) self.sigmaclip_threshold.setText("5.0") self.sigmaclip_maxiter.setValidator(qt.QIntValidator(0, 100)) self.sigmaclip_maxiter.setText("5") + self.medfilt_lower.setValidator(qt.QDoubleValidator(0.0, 100.0, 1)) + self.medfilt_upper.setValidator(qt.QDoubleValidator(0.0, 100.0, 1)) + self.medfilt_upper.setText("50.0") + self.medfilt_lower.setText("50.0") self.__configureDisabledStates() @@ -222,11 +225,13 @@ def __updateDisabledStates(self): self.oop_range_max.setEnabled(enabled) enabled = self.do_1d_integration.isChecked() self.vertical_integration.setEnabled(enabled) - + self.normalization_factor.setEnabled(self.do_normalization.isChecked()) self.monitor_name.setEnabled(self.do_normalization.isChecked()) - self.sigmaclip_threshold.setEnabled(self.integrator_name.currentText() == "sigma_clip_ng") - self.sigmaclip_maxiter.setEnabled(self.integrator_name.currentText() == "sigma_clip_ng") + self.sigmaclip_threshold.setEnabled(self.integrator_name.currentText() == "sigma-clip") + self.sigmaclip_maxiter.setEnabled(self.integrator_name.currentText() == "sigma-clip") + self.medfilt_lower.setEnabled(self.integrator_name.currentText() == "median-filter") + self.medfilt_upper.setEnabled(self.integrator_name.currentText() == "median-filter") def __unitChanged(self): unit = self.wavelengthUnit.getUnit() @@ -265,21 +270,21 @@ def __getAzimuthalNbpt(self): if value == "": return None return int(value) - + def __getInPlaneNbpt(self): # Only for WorkerFiberConfig value = str(self.nbpt_ip.text()).strip() if value == "": return None return int(value) - + def __getOutOfPlaneNbpt(self): # Only for WorkerFiberConfig value = str(self.nbpt_oop.text()).strip() if value == "": return None return int(value) - + def getPoni(self): poni = {"wavelength": self.__geometryModel.wavelength().value(), "dist": self.__geometryModel.distance().value(), @@ -297,13 +302,13 @@ def getPoni(self): def getPoniDict(self): return self.getPoni().as_dict() - + def _getIntegrationActiveTab(self) -> int: # tab_index = 0 -> Azimuthal integration # tab_index = 1 -> Fiber integration tab_index = int(self.tabWidget.currentIndex()) return tab_index - + def getWorkerConfigGeneric(self): """Read the configuration of the plugin and returns it as a WorkerConfig / WorkerFiberConfig instance @@ -387,11 +392,17 @@ def splitFiles(filenames): value = str(value) wc.monitor_name = value - if self.integrator_name.currentText() == "sigma_clip_ng": + if self.integrator_name.currentText() == "sigma-clip": wc.nbpt_azim = 1 wc.integrator_method = "sigma_clip_ng" wc.extra_options = {"thres": float(self.sigmaclip_threshold.text()), - "max_iter": float(self.sigmaclip_maxiter.text()), + "max_iter": int(self.sigmaclip_maxiter.text()), + } + elif self.integrator_name.currentText() == "median-filter": + wc.nbpt_azim = 1 + wc.integrator_method = "medfilt1d_ng" + wc.extra_options = {"percentile":(float(self.medfilt_lower.text()), + float(self.medfilt_upper.text())), } return wc @@ -448,7 +459,7 @@ def splitFiles(filenames): if self.do_oop_range.isChecked(): wc.oop_range = [self._float("oop_range_min", -numpy.inf), self._float("oop_range_max", numpy.inf)] - + wc.vertical_integration = self.vertical_integration.isChecked() wc.integration_1d = self.do_1d_integration.isChecked() @@ -492,7 +503,7 @@ def getConfig(self): :return: dict with all information """ return self.getWorkerConfig().as_dict() - + def setConfig(self, dico): """Setup the widget from its description @@ -508,7 +519,7 @@ def setConfig(self, dico): self.tabWidget.setCurrentIndex(1) else: raise ValueError(f"{integrator_class} is not a valid Integrator class") - + def setWorkerConfig(self, wc): """Setup the widget from its description @@ -571,7 +582,7 @@ def normalizeFiles(filenames): self.polarization_factor.setValue(wc.polarization_factor) elif isinstance(wc.polarization_factor, (tuple, list)): self.polarization_factor.setValue(wc.polarization_factor[0]) - + if type(wc) is integration_config.WorkerConfig: self.nbpt_rad.setText(str_(wc.nbpt_rad)) self.nbpt_azim.setText(str_(wc.nbpt_azim)) @@ -597,25 +608,25 @@ def normalizeFiles(filenames): self.vertical_integration.setChecked(wc.vertical_integration) self.do_1d_integration.setChecked(wc.integration_1d) unit_ip = parse_fiber_unit(**wc.unit_ip) - + # In UnitSelector, searching the unit is made with 'is' not '==', not valid for FiberUnit (which are copies) for index in range(self.ip_unit.count()): item = self.ip_unit.itemData(index) - if item == unit_ip: + if item == unit_ip: self.ip_unit.setCurrentIndex(index) break - + unit_oop = parse_fiber_unit(**wc.unit_oop) for index in range(self.oop_unit.count()): item = self.oop_unit.itemData(index) - if item == unit_oop: + if item == unit_oop: self.oop_unit.setCurrentIndex(index) break self.incident_angle_deg.setText(f"{numpy.rad2deg(unit_ip.incident_angle):.2f}") self.tilt_angle_deg.setText(f"{numpy.rad2deg(unit_ip.tilt_angle):.2f}") self.sample_orientation.setText(str(unit_ip.sample_orientation)) - + self.do_solid_angle.setChecked(bool(wc.correct_solid_angle)) self.do_dummy.setChecked(wc.do_dummy) self.do_dark.setChecked(wc.do_dark) @@ -664,7 +675,7 @@ def normalizeFiles(filenames): self.sigmaclip_maxiter.setText(str(extra_options.get("max_iter", 5))) self.__updateDisabledStates() - + def getOpenFileName(self, title): """Display a dialog to select a filename and return it. diff --git a/src/pyFAI/resources/gui/worker-configurator.ui b/src/pyFAI/resources/gui/worker-configurator.ui index 2df7e55a6f..2247d8af64 100644 --- a/src/pyFAI/resources/gui/worker-configurator.ui +++ b/src/pyFAI/resources/gui/worker-configurator.ui @@ -354,11 +354,11 @@ - 1 + 0 - Radial integration + Azimuthal integration @@ -766,13 +766,16 @@ Processing - - - - Error propagation: + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -786,75 +789,96 @@ - - + + + + ... + + + + + - Normalization factor applied to each integration + <html><head/><body><p>sigma-clip threshold: i.e. drop any value not in the &lt;I&gt; ± n⋅σ(I) interval.</p></body></html> + + + + - + OpenCL device: + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - × + ... - - + + - Monitor name to reach normalization from the header of the input data - - - + <html><head/><body><p>Number of sigma-clipping iterations to perform, the higher, the better (and the solver). </p></body></html> - - + + - Algorithm: + SigmaClip Max.Iter: - - + + - ... + × - - + + - OpenCL device: + Error propagation: - - + + + + Monitor name to reach normalization from the header of the input data + - ... + - - + + + + Normalization factor applied to each integration + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + SigmaClip threshold: @@ -865,37 +889,36 @@ - - - - - + + - SigmaClip threshold: - - - - - - - SigmaClip threshold + Algorithm: - - + + - SigmaClip Max.Iter: + MedFilt lower: - - + + + + + - SigmaClip max iter + <html><head/><body><p>Upper percentile for quantile-average. </p><p>Leave at 50 to get obtain filtering.</p></body></html> + + + MedFilt upper: + + + From 8fc50c3f7c04f839c7808171d3853f9106992dac Mon Sep 17 00:00:00 2001 From: Jerome Kieffer Date: Thu, 11 Dec 2025 09:02:58 +0100 Subject: [PATCH 4/4] refactor user interface --- .../resources/gui/worker-configurator.ui | 518 +++++++++--------- 1 file changed, 252 insertions(+), 266 deletions(-) diff --git a/src/pyFAI/resources/gui/worker-configurator.ui b/src/pyFAI/resources/gui/worker-configurator.ui index 2247d8af64..9396023c6c 100644 --- a/src/pyFAI/resources/gui/worker-configurator.ui +++ b/src/pyFAI/resources/gui/worker-configurator.ui @@ -365,98 +365,25 @@ - - - - - Number of points: - - - - - - - - - - Radial unit: - - - - - - - QComboBox::AdjustToMinimumContentsLengthWithIcon - - - - - - - - - 2D integration - - - true - - - false + Radial dimension - - - - - Number of points: - - - - - - - Azimuthal unit: - - - - - - - - - - χ (rad) - - - - - - - - - - - - - - Integration range - - - - + + + - ... + Points: - + upper radial bound - + Limit/Force the radial range @@ -466,68 +393,41 @@ - - - - Lower radial bound - - - - - + + - ? + <html><head/><body><p align="center">...</p></body></html> - - - - if unset, the discontinuity is at 180° - - - χ discontinuity at 0 - - + + - - - - Limit/force the azimuthal range - - - Azimuthal range: + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon - - - Lower azimuthal bound - - - - - - - - + - Upper azimuthal bound + Lower radial bound - - + + - deg + Radial unit: - - + + - ... + ? @@ -536,6 +436,96 @@ + + + + 2D integration + + + true + + + true + + + + + + Points: + + + + + + + + + + Limit/force the azimuthal range + + + Azimuthal range: + + + + + + + χ (°) + + + + + + + Upper azimuthal bound + + + + + + + Lower azimuthal bound + + + + + + + + + + Azimuthal unit: + + + + + + + ° + + + + + + + <html><head/><body><p align="center">...</p></body></html> + + + + + + + if unset, the discontinuity is at 180° + + + χ discontinuity at 0 + + + + + + @@ -547,58 +537,75 @@ + + In Plane + + + true + - - + + + + <html><head/><body><p>Number of bins in this dimention</p></body></html> + - In-Plane unit: + Points: - + QComboBox::AdjustToMinimumContentsLengthWithIcon - + + + + Limit/Force the in-plane range + + + IP range: + + + + - - + + - Number of points: + <html><head/><body><p align="center">...</p></body></html> - - - - - - - - - - Number of points: + + + + Lower in-plane bound - - - - Out-of-plane unit: + + + + upper in-plane bound - - + + + + In-Plane unit: + + - - - - QComboBox::AdjustToMinimumContentsLengthWithIcon + + + + ? @@ -607,144 +614,133 @@ + + + + Out-of-plane + + + true + + + false + + + + + + Limit/force the out-of-plane range + + + OOP range: + + + + + + + <html><head/><body><p align="center">...</p></body></html> + + + + + + + Lower out-of-plane bound + + + + + + + + + + Upper out-of-plane bound + + + + + + + ? + + + + + + + Out-of-plane unit: + + + + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + + + + + Points: + + + + + + + + + - + Sample orientation - - + + - ... + Incident angle (°) - + 0.0 - - - - Lower out-of-plane bound - - - - - - - - - - Upper out-of-plane bound - - - - - - - false - - - Vertical integration - - - true - - - true - - - - - - - Limit/Force the in-plane range - - - IP range: - - - - - - - upper in-plane bound - - - - - - - Limit/force the out-of-plane range - + + - OOP range: + Tilt angle (°) - + 0.0 - - - - Lower in-plane bound - - - - - - - 1 - - - - + Orientation - - - - Tilt angle (°) - - - - - - - 1D integration - - - false - - - - - - - ... - - - - - + + - Incident angle (°) + 1 @@ -997,17 +993,7 @@ do_polarization polarization_factor do_solid_angle - do_2D radial_unit - nbpt_rad - nbpt_azim - do_radial_range - radial_range_min - radial_range_max - do_azimuthal_range - azimuth_range_min - azimuth_range_max - chi_discontinuity_at_0 error_model do_normalization normalization_factor