From add628c6e3c610814c5c01280afa56e28de9580e Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 14:04:09 +0100 Subject: [PATCH 01/18] merge integrated unit to mask method --- src/pyFAI/integrator/common.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 23bc53e35..63966cf33 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -287,6 +287,18 @@ def flat_correction(self, data, flat=None): else: return data, None + def _merge_integrated_unit_to_mask(self, unit_array:numpy.ndarray, unit_range:tuple, mask:numpy.ndarray=None) -> numpy.ndarray: + """ + Needed for integrate1d method, apply the integrated unit limit (e.g. azimuth unit) before histogramming, by merging it into the mask. + """ + unit_min, unit_max = unit_range + unit_mask = numpy.logical_or(unit_array > unit_max, unit_array < unit_min) + if mask is None: + mask = unit_mask + else: + mask = numpy.logical_or(mask, unit_mask) + return mask + def _normalize_method(self, method, dim, default): """ :rtype: IntegrationMethod From 105da50d38e583d03a329d35cff04222eb2ba6d7 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 14:04:54 +0100 Subject: [PATCH 02/18] use merge integrated for azimuthal integrator --- src/pyFAI/integrator/azimuthal.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pyFAI/integrator/azimuthal.py b/src/pyFAI/integrator/azimuthal.py index 70b26a4b2..94aca40bd 100644 --- a/src/pyFAI/integrator/azimuthal.py +++ b/src/pyFAI/integrator/azimuthal.py @@ -281,13 +281,12 @@ def integrate1d(self, data, npt, *, method.method[3] in ("python", "cython")): integr = method.class_funct_ng.function # should be histogram[_engine].histogram1d_engine if azimuth_range: - chi_min, chi_max = azimuth_range - chi = self.center_array(shape, unit=units.CHI_RAD, scale=False) - azim_mask = numpy.logical_or(chi > chi_max, chi < chi_min) - if mask is None: - mask = azim_mask - else: - mask = numpy.logical_or(mask, azim_mask) + azimuth_array = self.center_array(shape, unit=units.CHI_RAD, scale=False) + mask = self._merge_integrated_unit_to_mask( + unit_array=azimuth_array, + unit_range=azimuth_range, + mask=mask, + ) radial = self.center_array(shape, unit=unit, scale=False) intpl = integr(radial, npt, data, dark=dark, From 137663121b45b304939c3aa09701e3a951a3ff8b Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 15:02:10 +0100 Subject: [PATCH 03/18] implement engine for method=no-histogram-python/cython --- src/pyFAI/integrator/fiber.py | 198 ++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 57 deletions(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index f2f5fbb8a..a43082a30 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -40,6 +40,7 @@ from ..containers import Integrate1dFiberResult, Integrate2dFiberResult from ..method_registry import IntegrationMethod from ..io import save_integrate_result +from ..io.ponifile import PoniFile from ..units import parse_fiber_unit, ANGLE_UNITS, to_unit from ..utils.decorators import deprecated_warning logger = logging.getLogger(__name__) @@ -201,11 +202,14 @@ def integrate_fiber(self, data, sample_orientation=None, filename=None, correctSolidAngle=True, + variance=None, error_model=None, mask=None, dummy=None, delta_dummy=None, - polarization_factor=None, dark=None, flat=None, + polarization_factor=None, dark=None, flat=None, absorption=None, method=("no", "histogram", "cython"), normalization_factor=1.0, angle_unit="rad", + metadata=None, + use_2d_engine:bool=False, **kwargs) -> Integrate1dFiberResult: """Calculate the integrated profile curve along a specific FiberUnit, additional input for sample_orientation @@ -237,6 +241,10 @@ def integrate_fiber(self, data, :return: chi bins center positions and regrouped intensity :rtype: Integrate1dResult """ + method = self._normalize_method(method, dim=1, default=self.DEFAULT_METHOD_1D) + if method.dimension != 1: + raise RuntimeError("integration method is not 1D") + deprecated_params = get_deprecated_params_1d(**kwargs) npt_oop = deprecated_params.get('npt_oop', None) or npt_oop npt_ip = deprecated_params.get('npt_ip', None) or npt_ip @@ -251,6 +259,18 @@ def integrate_fiber(self, data, logger.warning(f"""Key parameters {invalid_keys} are wrong or deprecated. Valid parameters: npt_ip, unit_ip, ip_range, npt_oop, unit_oop, oop_range""") + dummy, delta_dummy = self._normalize_dummies(dummy, delta_dummy, data) + empty = self._empty + shape = data.shape + mask, mask_crc, has_mask = self._normalize_mask(mask) + solidangle, solidangle_crc = self._normalize_solidangle(shape, correctSolidAngle, with_checksum=False) + polarization, polarization_crc = self._normalize_polarization(shape, polarization_factor, with_checksum=True) + dark, has_dark = self._normalize_dark(dark) + flat, has_flat = self._normalize_flat(flat) + + error_model, variance = self._normalize_error_model_variance(data, method, dark, + error_model, variance) + unit_ip = unit_ip or 'qip_nm^-1' unit_oop = unit_oop or 'qoop_nm^-1' incident_angle = kwargs.get('incident_angle', None) @@ -279,71 +299,135 @@ def integrate_fiber(self, data, if (isinstance(method, (tuple, list)) and method[0] != "no") or (isinstance(method, IntegrationMethod) and method.split != "no"): logger.warning(f"Method {method} is using a pixel-splitting scheme. GI integration should be use WITHOUT PIXEL-SPLITTING! The results could be wrong!") - if vertical_integration and npt_oop is None: - raise RuntimeError("npt_oop (out-of-plane bins) is needed to do the integration") - elif not vertical_integration and npt_ip is None: - raise RuntimeError("npt_ip (in-plane bins) is needed to do the integration") - - npt_ip = npt_ip or 1000 - npt_oop = npt_oop or 1000 - - res2d_fiber = self.integrate2d_fiber(data, - npt_ip=npt_ip, unit_ip=unit_ip, ip_range=ip_range, - npt_oop=npt_oop, unit_oop=unit_oop, oop_range=oop_range, - sample_orientation=sample_orientation, - filename=None, - correctSolidAngle=correctSolidAngle, - mask=mask, dummy=dummy, delta_dummy=delta_dummy, - polarization_factor=polarization_factor, - dark=dark, flat=flat, method=method, - normalization_factor=normalization_factor, - **kwargs) - if vertical_integration: - output_unit = res2d_fiber.oop_unit + integrated_unit = unit_ip + integrated_bins = npt_ip + integrated_range = ip_range + projected_unit = unit_oop + projected_bins = npt_oop + projected_range = oop_range integration_axis = -1 - integrated_vector = res2d_fiber.outofplane else: - output_unit = res2d_fiber.ip_unit + integrated_unit = unit_oop + integrated_bins = npt_oop + integrated_range = oop_range + projected_unit = unit_ip + projected_bins = npt_ip + projected_range = ip_range integration_axis = -2 - integrated_vector = res2d_fiber.inplane - - sum_signal = res2d_fiber.sum_signal.sum(axis=integration_axis) - count = res2d_fiber.count.sum(axis=integration_axis) - sum_normalization = res2d_fiber._sum_normalization.sum(axis=integration_axis) - mask_ = numpy.where(count == 0) - empty = dummy if dummy is not None else self._empty - if USE_NUMEXPR: - intensity = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization)") - else: - intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) - intensity[mask_] = empty - if res2d_fiber.sigma is not None: - sum_variance = res2d_fiber.sum_variance.sum(axis=integration_axis) + if projected_bins is None: + raise RuntimeError(f" Needed the bins of the projected unit: {projected_unit}") + + result = None + if not use_2d_engine: + if method.algo == "histogram" and method.pixel_splitting == "no": + if integrated_range: + integrated_unit_array = self.center_array(shape, unit=integrated_unit, scale=False) + mask = self._merge_integrated_unit_to_mask( + unit_array=integrated_unit_array, + unit_range=integrated_range, + mask=mask, + ) + projected_unit_array = self.center_array(shape, unit=projected_unit, scale=False) + integr = method.class_funct_ng.function + intpl = integr(projected_unit_array, + projected_bins, + data, + dark=dark, + dummy=dummy, delta_dummy=delta_dummy, empty=empty, + variance=variance, + flat=flat, solidangle=solidangle, + polarization=polarization, + absorption=absorption, + normalization_factor=normalization_factor, + weighted_average=method.weighted_average, + mask=mask, + radial_range=projected_range, + error_model=error_model, + ) + + if error_model.do_variance: + result = Integrate1dFiberResult( + integrated=intpl.position * integrated_unit.scale, + intensity=intpl.intensity, + sigma=intpl.sigma, + ) + result._set_sum_variance(intpl.variance) + result._set_std(intpl.std) + result._set_sem(intpl.sem) + result._set_sum_normalization2(intpl.norm_sq) + else: + result = Integrate1dFiberResult( + integrated=intpl.position * integrated_unit.scale, + intensity=intpl.intensity, + sigma=None, + ) + result._set_compute_engine(integr.__module__ + "." + integr.__name__) + result._set_unit(projected_unit) + result._set_sum_signal(intpl.signal) + result._set_sum_normalization(intpl.normalization) + result._set_count(intpl.count) + + if result is None: + # Not implemented yet for other engines (still going through 2d engine) + res2d_fiber = self.integrate2d_fiber(data, + npt_ip=npt_ip, unit_ip=unit_ip, ip_range=ip_range, + npt_oop=npt_oop, unit_oop=unit_oop, oop_range=oop_range, + sample_orientation=sample_orientation, + filename=None, + correctSolidAngle=correctSolidAngle, + mask=mask, dummy=dummy, delta_dummy=delta_dummy, + polarization_factor=polarization_factor, + dark=dark, flat=flat, method=method, + normalization_factor=normalization_factor, + **kwargs) + if vertical_integration: + integrated_vector = res2d_fiber.outofplane + else: + integrated_vector = res2d_fiber.inplane + sum_signal = res2d_fiber.sum_signal.sum(axis=integration_axis) + count = res2d_fiber.count.sum(axis=integration_axis) + sum_normalization = res2d_fiber._sum_normalization.sum(axis=integration_axis) + mask_ = numpy.where(count == 0) + empty = dummy if dummy is not None else self._empty if USE_NUMEXPR: - sigma = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sqrt(sum_variance) / sum_normalization)") + intensity = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization)") else: - sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) - sigma[mask_] = empty - else: - sum_variance = None - sigma = None - - result = Integrate1dFiberResult(integrated_vector, intensity, sigma) - result._set_vertical_integration(vertical_integration) - result._set_method_called("integrate_radial") - result._set_unit(output_unit) - result._set_sum_normalization(sum_normalization) - result._set_count(count) - result._set_sum_signal(sum_signal) - result._set_sum_variance(sum_variance) - result._set_has_dark_correction(dark is not None) - result._set_has_flat_correction(flat is not None) + intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) + intensity[mask_] = empty + + if res2d_fiber.sigma is not None: + sum_variance = res2d_fiber.sum_variance.sum(axis=integration_axis) + if USE_NUMEXPR: + sigma = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sqrt(sum_variance) / sum_normalization)") + else: + sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) + sigma[mask_] = empty + else: + sum_variance = None + sigma = None + result = Integrate1dFiberResult(integrated_vector, intensity, sigma) + result._set_vertical_integration(vertical_integration) + result._set_unit(projected_unit) + result._set_sum_normalization(sum_normalization) + result._set_count(count) + result._set_sum_signal(sum_signal) + result._set_sum_variance(sum_variance) + result._set_compute_engine = res2d_fiber.compute_engine + + result._set_method(method) + result._set_method_called("integrate1d_ng") + result._set_has_dark_correction(has_dark) + result._set_has_flat_correction(has_flat) + result._set_has_mask_applied(has_mask) result._set_polarization_factor(polarization_factor) result._set_normalization_factor(normalization_factor) - result._set_method = res2d_fiber.method - result._set_compute_engine = res2d_fiber.compute_engine + result._set_metadata(metadata) + result._set_error_model(error_model) + result._set_poni(PoniFile(self)) + result._set_has_solidangle_correction(correctSolidAngle) + result._set_weighted_average(method.weighted_average) if filename is not None: save_integrate_result(filename, result) From f3aa4e8e653d6f87ec21da280ee077e28e274029 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 15:02:31 +0100 Subject: [PATCH 04/18] add test for equivalent integrations --- src/pyFAI/test/test_fiber_integrator.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/pyFAI/test/test_fiber_integrator.py b/src/pyFAI/test/test_fiber_integrator.py index ccb26d892..accec68c0 100644 --- a/src/pyFAI/test/test_fiber_integrator.py +++ b/src/pyFAI/test/test_fiber_integrator.py @@ -581,7 +581,25 @@ def test_equivalence_numpy_numexpr(self): array_numpy = self.fi.array_from_unit(unit=fiberunit) self.assertTrue(numpy.allclose(array_numexpr, array_numpy)) - + + def test_equivalence_using_engines1d(self): + params_gi = { + "data" : self.data, + "npt_ip" : 200, + "npt_oop" : 200, + "vertical_integration" : False, + "sample_orientation" : 6, + } + methods_test = [ + ("no", "histogram", "python"), + ("no", "histogram", "cython"), + ] + for method in methods_test: + res_from_2d = self.fi.integrate_fiber(use_2d_engine=True, method=method, **params_gi) + res_engine = self.fi.integrate_fiber(use_2d_engine=False, method=method, **params_gi) + self.assertTrue(numpy.allclose(res_from_2d.intensity, res_engine.intensity)) + + def suite(): testsuite = unittest.TestSuite() loader = unittest.defaultTestLoader.loadTestsFromTestCase From a0a74b0ef2f7a116201a65c5eebb55f1ff7dc597 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 16:37:36 +0100 Subject: [PATCH 05/18] method create_mask_generic --- src/pyFAI/integrator/common.py | 64 ++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 63966cf33..0d4fa4975 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -200,6 +200,39 @@ def create_mask(self, data, mask=None, the out= argument allows to recycle buffers and save considerable time in allocating temporary arrays. """ + if radial_range is not None: + if unit is None: + raise RuntimeError("unit is needed when building a mask based on radial_range") + elif isinstance(unit, (tuple, list)) and len(unit) == 2: + radial_unit = units.to_unit(unit[0]) + else: + radial_unit = units.to_unit(unit) + if azimuth_range is not None: + if isinstance(unit, (tuple, list)) and len(unit) == 2: + azimuth_unit = units.to_unit(unit[1]) + else: + logger.info("no azimuthal unit provided in `create_mask`, defaulting to `chi_rad`") + azimuth_unit = units.CHI_RAD + return self.create_mask_generic( + data=data, + mask=mask, + dummy=dummy, + delta_dummy=delta_dummy, + projected_unit=radial_unit, + projected_unit_range=radial_range, + integrated_unit=azimuth_unit, + integrated_unit_range=azimuth_range, + mode=mode, + ) + + create_mask_azimuthal = create_mask + + def create_mask_generic(self, data, mask=None, + dummy=None, delta_dummy=None, + projected_unit=None, projected_unit_range=None, + integrated_unit=None, integrated_unit_range=None, + mode="normal", + ): logical_or = numpy.logical_or shape = data.shape # ^^^^ this is why data is mandatory ! @@ -230,25 +263,18 @@ def create_mask(self, data, mask=None, else: logical_or(mask, abs(data - dummy) <= delta_dummy, out=mask) - if radial_range is not None: - if unit is None: - raise RuntimeError("unit is needed when building a mask based on radial_range") - elif isinstance(unit, (tuple, list)) and len(unit) == 2: - radial_unit = units.to_unit(unit[0]) - else: - radial_unit = units.to_unit(unit) - rad = self.array_from_unit(shape, "center", radial_unit, scale=False) - logical_or(mask, rad < radial_range[0], out=mask) - logical_or(mask, rad > radial_range[1], out=mask) - if azimuth_range is not None: - if isinstance(unit, (tuple, list)) and len(unit) == 2: - azimuth_unit = units.to_unit(unit[1]) - else: - logger.info("no azimuthal unit provided in `create_mask`, defaulting to `chi_rad`") - azimuth_unit = units.CHI_RAD - chi = self.array_from_unit(shape, "center", azimuth_unit, scale=False) - logical_or(mask, chi < azimuth_range[0], out=mask) - logical_or(mask, chi > azimuth_range[1], out=mask) + if projected_unit_range is not None: + if projected_unit is None: + raise RuntimeError("projected_unit is needed when building a mask based on projected_unit_range") + projected_unit_array = self.array_from_unit(shape, "center", projected_unit, scale=False) + logical_or(mask, projected_unit_array < projected_unit_range[0], out=mask) + logical_or(mask, projected_unit_array > projected_unit_range[1], out=mask) + if integrated_unit_range is not None: + if integrated_unit is None: + raise RuntimeError("integrated_unit is needed when building a mask based on integrated_unit_range") + integrated_unit_array = self.array_from_unit(shape, "center", integrated_unit, scale=False) + logical_or(mask, integrated_unit_array < integrated_unit_range[0], out=mask) + logical_or(mask, integrated_unit_array > integrated_unit_range[1], out=mask) # Prepare alternative representation for output: if mode == "numpy": From cc9ecf58f9e4e503d0c87a56f1e1eb278de0826a Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 16:40:42 +0100 Subject: [PATCH 06/18] docs for create_mask_generic --- src/pyFAI/integrator/common.py | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 0d4fa4975..310840fe7 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -233,6 +233,48 @@ def create_mask_generic(self, data, mask=None, integrated_unit=None, integrated_unit_range=None, mode="normal", ): + """ + Combines various masks into another one. + + :param data: input array of data + :type data: ndarray + :param mask: input mask (if none, self.mask is used) + :type mask: ndarray + :param dummy: value of dead pixels + :type dummy: float + :param delta_dummy: precision of dummy pixels + :type delta_dummy: float + :param projected_unit: unit to use for projected_unit_range (e.g. radial unit for radial_range) + :type projected_unit: pyFAI.units.Unit + :param projected_unit_range: range in projected unit to mask out (e.g. radial range for radial mask) + :type projected_unit_range: (float, float) + :param integrated_unit: unit to use for integrated_unit_range (e.g. azimuthal unit for azimuth_range) + :type integrated_unit: pyFAI.units.Unit + :param mode: can be "normal" or "numpy" (inverted) or "where" applied to the mask + :type mode: str + + :return: the new mask + :rtype: ndarray of bool + + This method combine two masks (dynamic mask from *data & + dummy* and *mask*) to generate a new one with the 'or' binary + operation. One can adjust the level, with the *dummy* and + the *delta_dummy* parameter, when you consider the *data* + values needs to be masked out. + + This method can work in two different *mode*: + + * "normal": False for valid pixels, True for bad pixels + * "numpy": True for valid pixels, false for others + * "where": does a numpy.where on the "numpy" output + + This method tries to accommodate various types of masks (like + valid=0 & masked=-1, ...) + + Note for the developer: we use a lot of numpy.logical_or in this method, + the out= argument allows to recycle buffers and save considerable time in + allocating temporary arrays. + """ logical_or = numpy.logical_or shape = data.shape # ^^^^ this is why data is mandatory ! From 983ea52b6938983d186348da1abdf6c2ba9a053d Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 16:46:12 +0100 Subject: [PATCH 07/18] use create_mask_generic --- src/pyFAI/integrator/azimuthal.py | 8 ++++---- src/pyFAI/integrator/common.py | 12 ------------ src/pyFAI/integrator/fiber.py | 8 ++++---- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/pyFAI/integrator/azimuthal.py b/src/pyFAI/integrator/azimuthal.py index 94aca40bd..f2cb5375e 100644 --- a/src/pyFAI/integrator/azimuthal.py +++ b/src/pyFAI/integrator/azimuthal.py @@ -281,11 +281,11 @@ def integrate1d(self, data, npt, *, method.method[3] in ("python", "cython")): integr = method.class_funct_ng.function # should be histogram[_engine].histogram1d_engine if azimuth_range: - azimuth_array = self.center_array(shape, unit=units.CHI_RAD, scale=False) - mask = self._merge_integrated_unit_to_mask( - unit_array=azimuth_array, - unit_range=azimuth_range, + mask = self.create_mask_generic( + data=data, mask=mask, + integrated_unit=units.CHI_RAD, + integrated_unit_range=azimuth_range, ) radial = self.center_array(shape, unit=unit, scale=False) intpl = integr(radial, npt, data, diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 310840fe7..7be76412c 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -355,18 +355,6 @@ def flat_correction(self, data, flat=None): else: return data, None - def _merge_integrated_unit_to_mask(self, unit_array:numpy.ndarray, unit_range:tuple, mask:numpy.ndarray=None) -> numpy.ndarray: - """ - Needed for integrate1d method, apply the integrated unit limit (e.g. azimuth unit) before histogramming, by merging it into the mask. - """ - unit_min, unit_max = unit_range - unit_mask = numpy.logical_or(unit_array > unit_max, unit_array < unit_min) - if mask is None: - mask = unit_mask - else: - mask = numpy.logical_or(mask, unit_mask) - return mask - def _normalize_method(self, method, dim, default): """ :rtype: IntegrationMethod diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index a43082a30..077c96671 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -323,11 +323,11 @@ def integrate_fiber(self, data, if not use_2d_engine: if method.algo == "histogram" and method.pixel_splitting == "no": if integrated_range: - integrated_unit_array = self.center_array(shape, unit=integrated_unit, scale=False) - mask = self._merge_integrated_unit_to_mask( - unit_array=integrated_unit_array, - unit_range=integrated_range, + mask = self.create_mask_generic( + data=data, mask=mask, + integrated_unit=integrated_unit, + integrated_unit_range=integrated_range, ) projected_unit_array = self.center_array(shape, unit=projected_unit, scale=False) integr = method.class_funct_ng.function From 3132e12506513ec1801153ecea210636c36c9179 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 3 Mar 2026 16:48:56 +0100 Subject: [PATCH 08/18] fix create_mask --- src/pyFAI/integrator/common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 7be76412c..8585bf7ff 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -207,12 +207,16 @@ def create_mask(self, data, mask=None, radial_unit = units.to_unit(unit[0]) else: radial_unit = units.to_unit(unit) + else: + radial_unit = None if azimuth_range is not None: if isinstance(unit, (tuple, list)) and len(unit) == 2: azimuth_unit = units.to_unit(unit[1]) else: logger.info("no azimuthal unit provided in `create_mask`, defaulting to `chi_rad`") azimuth_unit = units.CHI_RAD + else: + azimuth_unit = None return self.create_mask_generic( data=data, mask=mask, From 9cae231ec5d801a2876bf74f1e984668058f0e26 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 10:39:04 +0100 Subject: [PATCH 09/18] prefered methods fiber --- src/pyFAI/integrator/load_engines.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyFAI/integrator/load_engines.py b/src/pyFAI/integrator/load_engines.py index f21e9e89a..6bbd59965 100644 --- a/src/pyFAI/integrator/load_engines.py +++ b/src/pyFAI/integrator/load_engines.py @@ -351,3 +351,5 @@ IntegrationMethod.select_method(2, split="pseudo", algo="histogram") + \ IntegrationMethod.select_method(2, split="bbox", algo="histogram") + \ IntegrationMethod.select_method(2, split="no", algo="histogram") +PREFERED_METHOD_1D_FIBER = IntegrationMethod.select_method(1, split="no", algo="histogram")[0] +PREFERED_METHOD_2D_FIBER = IntegrationMethod.select_method(2, split="no", algo="histogram")[0] \ No newline at end of file From e6f27007595f41d7e8c871f447589c7d99862c72 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 16:14:24 +0100 Subject: [PATCH 10/18] mask generic --- src/pyFAI/integrator/common.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 8585bf7ff..0eb993b49 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -222,10 +222,10 @@ def create_mask(self, data, mask=None, mask=mask, dummy=dummy, delta_dummy=delta_dummy, - projected_unit=radial_unit, - projected_unit_range=radial_range, - integrated_unit=azimuth_unit, - integrated_unit_range=azimuth_range, + unit1=radial_unit, + unit1_range=radial_range, + unit2=azimuth_unit, + unit2_range=azimuth_range, mode=mode, ) @@ -233,8 +233,8 @@ def create_mask(self, data, mask=None, def create_mask_generic(self, data, mask=None, dummy=None, delta_dummy=None, - projected_unit=None, projected_unit_range=None, - integrated_unit=None, integrated_unit_range=None, + unit1=None, unit1_range=None, + unit2=None, unit2_range=None, mode="normal", ): """ @@ -309,18 +309,18 @@ def create_mask_generic(self, data, mask=None, else: logical_or(mask, abs(data - dummy) <= delta_dummy, out=mask) - if projected_unit_range is not None: - if projected_unit is None: + if unit1_range is not None: + if unit1 is None: raise RuntimeError("projected_unit is needed when building a mask based on projected_unit_range") - projected_unit_array = self.array_from_unit(shape, "center", projected_unit, scale=False) - logical_or(mask, projected_unit_array < projected_unit_range[0], out=mask) - logical_or(mask, projected_unit_array > projected_unit_range[1], out=mask) - if integrated_unit_range is not None: - if integrated_unit is None: + unit1_array = self.array_from_unit(shape, "center", unit1) + logical_or(mask, unit1_array < unit1_range[0], out=mask) + logical_or(mask, unit1_array > unit1_range[1], out=mask) + if unit2_range is not None: + if unit2 is None: raise RuntimeError("integrated_unit is needed when building a mask based on integrated_unit_range") - integrated_unit_array = self.array_from_unit(shape, "center", integrated_unit, scale=False) - logical_or(mask, integrated_unit_array < integrated_unit_range[0], out=mask) - logical_or(mask, integrated_unit_array > integrated_unit_range[1], out=mask) + unit2_array = self.array_from_unit(shape, "center", unit2) + logical_or(mask, unit2_array < unit2_range[0], out=mask) + logical_or(mask, unit2_array > unit2_range[1], out=mask) # Prepare alternative representation for output: if mode == "numpy": From 7adecd841f4fc0d5368747b810cff576656e8ec7 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 16:14:36 +0100 Subject: [PATCH 11/18] ai mask generic --- src/pyFAI/integrator/azimuthal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyFAI/integrator/azimuthal.py b/src/pyFAI/integrator/azimuthal.py index f2cb5375e..03d455a90 100644 --- a/src/pyFAI/integrator/azimuthal.py +++ b/src/pyFAI/integrator/azimuthal.py @@ -284,8 +284,8 @@ def integrate1d(self, data, npt, *, mask = self.create_mask_generic( data=data, mask=mask, - integrated_unit=units.CHI_RAD, - integrated_unit_range=azimuth_range, + unit2=units.CHI_RAD, + unit2_range=azimuth_range, ) radial = self.center_array(shape, unit=unit, scale=False) intpl = integr(radial, npt, data, From 4e5787650e09a32779f495861011d13a49e6711c Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 16:14:54 +0100 Subject: [PATCH 12/18] refactor fiber integrator --- src/pyFAI/integrator/fiber.py | 599 +++++++++++++++++++--------------- 1 file changed, 330 insertions(+), 269 deletions(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index 077c96671..2307f8684 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -37,7 +37,8 @@ import logging import numpy from .azimuthal import AzimuthalIntegrator -from ..containers import Integrate1dFiberResult, Integrate2dFiberResult +from .load_engines import PREFERED_METHOD_1D_FIBER, PREFERED_METHOD_2D_FIBER +from ..containers import Integrate1dFiberResult, Integrate2dFiberResult,Integrate2dtpl,Integrate1dtpl from ..method_registry import IntegrationMethod from ..io import save_integrate_result from ..io.ponifile import PoniFile @@ -125,6 +126,8 @@ class FiberIntegrator(AzimuthalIntegrator): ) """ + DEFAULT_METHOD_1D = PREFERED_METHOD_1D_FIBER + DEFAULT_METHOD_2D = PREFERED_METHOD_2D_FIBER def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -194,12 +197,13 @@ def reset_integrator(self, incident_angle, tilt_angle, sample_orientation): self._cache_parameters['tilt_angle'] = tilt_angle self._cache_parameters['sample_orientation'] = sample_orientation - - def integrate_fiber(self, data, + def integrate1d_fiber(self, data, npt_ip=None, unit_ip=None, ip_range=None, npt_oop=None, unit_oop=None, oop_range=None, vertical_integration = True, - sample_orientation=None, + incident_angle:float=None, + tilt_angle:float=None, + sample_orientation:int=None, filename=None, correctSolidAngle=True, variance=None, error_model=None, @@ -210,8 +214,11 @@ def integrate_fiber(self, data, angle_unit="rad", metadata=None, use_2d_engine:bool=False, + use_missing_wedge:bool = False, + missing_wedge_percentile:float = None, + missing_wedge_threshold_bins:int = None, **kwargs) -> Integrate1dFiberResult: - """Calculate the integrated profile curve along a specific FiberUnit, additional input for sample_orientation + """Calculate the integrated 1d profile curve along a specific FiberUnit, additional input for sample_orientation :param ndarray data: 2D array from the Detector/CCD camera :param int npt_oop: number of points to be used along the out-of-plane axis @@ -259,196 +266,45 @@ def integrate_fiber(self, data, logger.warning(f"""Key parameters {invalid_keys} are wrong or deprecated. Valid parameters: npt_ip, unit_ip, ip_range, npt_oop, unit_oop, oop_range""") - dummy, delta_dummy = self._normalize_dummies(dummy, delta_dummy, data) - empty = self._empty - shape = data.shape - mask, mask_crc, has_mask = self._normalize_mask(mask) - solidangle, solidangle_crc = self._normalize_solidangle(shape, correctSolidAngle, with_checksum=False) - polarization, polarization_crc = self._normalize_polarization(shape, polarization_factor, with_checksum=True) - dark, has_dark = self._normalize_dark(dark) - flat, has_flat = self._normalize_flat(flat) - - error_model, variance = self._normalize_error_model_variance(data, method, dark, - error_model, variance) - - unit_ip = unit_ip or 'qip_nm^-1' - unit_oop = unit_oop or 'qoop_nm^-1' - incident_angle = kwargs.get('incident_angle', None) - tilt_angle = kwargs.get('tilt_angle', None) - - angle_unit_parsed = to_unit(angle_unit, ANGLE_UNITS) - - if incident_angle is not None: - incident_angle = (incident_angle % angle_unit_parsed.period) / angle_unit_parsed.scale - if tilt_angle is not None: - tilt_angle = (tilt_angle % angle_unit_parsed.period) / angle_unit_parsed.scale - - unit_ip = parse_fiber_unit(unit=unit_ip, - incident_angle=incident_angle, - tilt_angle=tilt_angle, - sample_orientation=sample_orientation) - unit_oop = parse_fiber_unit(unit=unit_oop, - incident_angle=unit_ip.incident_angle, - tilt_angle=unit_ip.tilt_angle, - sample_orientation=unit_ip.sample_orientation) - - self.reset_integrator(incident_angle=unit_ip.incident_angle, - tilt_angle=unit_ip.tilt_angle, - sample_orientation=unit_ip.sample_orientation) - - if (isinstance(method, (tuple, list)) and method[0] != "no") or (isinstance(method, IntegrationMethod) and method.split != "no"): - logger.warning(f"Method {method} is using a pixel-splitting scheme. GI integration should be use WITHOUT PIXEL-SPLITTING! The results could be wrong!") - - if vertical_integration: - integrated_unit = unit_ip - integrated_bins = npt_ip - integrated_range = ip_range - projected_unit = unit_oop - projected_bins = npt_oop - projected_range = oop_range - integration_axis = -1 - else: - integrated_unit = unit_oop - integrated_bins = npt_oop - integrated_range = oop_range - projected_unit = unit_ip - projected_bins = npt_ip - projected_range = ip_range - integration_axis = -2 - - if projected_bins is None: - raise RuntimeError(f" Needed the bins of the projected unit: {projected_unit}") - - result = None - if not use_2d_engine: - if method.algo == "histogram" and method.pixel_splitting == "no": - if integrated_range: - mask = self.create_mask_generic( - data=data, - mask=mask, - integrated_unit=integrated_unit, - integrated_unit_range=integrated_range, - ) - projected_unit_array = self.center_array(shape, unit=projected_unit, scale=False) - integr = method.class_funct_ng.function - intpl = integr(projected_unit_array, - projected_bins, - data, - dark=dark, - dummy=dummy, delta_dummy=delta_dummy, empty=empty, - variance=variance, - flat=flat, solidangle=solidangle, - polarization=polarization, - absorption=absorption, - normalization_factor=normalization_factor, - weighted_average=method.weighted_average, - mask=mask, - radial_range=projected_range, - error_model=error_model, - ) - - if error_model.do_variance: - result = Integrate1dFiberResult( - integrated=intpl.position * integrated_unit.scale, - intensity=intpl.intensity, - sigma=intpl.sigma, - ) - result._set_sum_variance(intpl.variance) - result._set_std(intpl.std) - result._set_sem(intpl.sem) - result._set_sum_normalization2(intpl.norm_sq) - else: - result = Integrate1dFiberResult( - integrated=intpl.position * integrated_unit.scale, - intensity=intpl.intensity, - sigma=None, - ) - result._set_compute_engine(integr.__module__ + "." + integr.__name__) - result._set_unit(projected_unit) - result._set_sum_signal(intpl.signal) - result._set_sum_normalization(intpl.normalization) - result._set_count(intpl.count) - - if result is None: - # Not implemented yet for other engines (still going through 2d engine) - res2d_fiber = self.integrate2d_fiber(data, - npt_ip=npt_ip, unit_ip=unit_ip, ip_range=ip_range, - npt_oop=npt_oop, unit_oop=unit_oop, oop_range=oop_range, - sample_orientation=sample_orientation, - filename=None, - correctSolidAngle=correctSolidAngle, - mask=mask, dummy=dummy, delta_dummy=delta_dummy, - polarization_factor=polarization_factor, - dark=dark, flat=flat, method=method, - normalization_factor=normalization_factor, - **kwargs) - if vertical_integration: - integrated_vector = res2d_fiber.outofplane - else: - integrated_vector = res2d_fiber.inplane - sum_signal = res2d_fiber.sum_signal.sum(axis=integration_axis) - count = res2d_fiber.count.sum(axis=integration_axis) - sum_normalization = res2d_fiber._sum_normalization.sum(axis=integration_axis) - mask_ = numpy.where(count == 0) - empty = dummy if dummy is not None else self._empty - if USE_NUMEXPR: - intensity = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization)") - else: - intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) - intensity[mask_] = empty - - if res2d_fiber.sigma is not None: - sum_variance = res2d_fiber.sum_variance.sum(axis=integration_axis) - if USE_NUMEXPR: - sigma = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sqrt(sum_variance) / sum_normalization)") - else: - sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) - sigma[mask_] = empty - else: - sum_variance = None - sigma = None - result = Integrate1dFiberResult(integrated_vector, intensity, sigma) - result._set_vertical_integration(vertical_integration) - result._set_unit(projected_unit) - result._set_sum_normalization(sum_normalization) - result._set_count(count) - result._set_sum_signal(sum_signal) - result._set_sum_variance(sum_variance) - result._set_compute_engine = res2d_fiber.compute_engine - - result._set_method(method) - result._set_method_called("integrate1d_ng") - result._set_has_dark_correction(has_dark) - result._set_has_flat_correction(has_flat) - result._set_has_mask_applied(has_mask) - result._set_polarization_factor(polarization_factor) - result._set_normalization_factor(normalization_factor) - result._set_metadata(metadata) - result._set_error_model(error_model) - result._set_poni(PoniFile(self)) - result._set_has_solidangle_correction(correctSolidAngle) - result._set_weighted_average(method.weighted_average) - + result_fiber = self._integrate_fiber( + data=data, + method=method, + vertical_integration=vertical_integration, + npt_ip=npt_ip, unit_ip=unit_ip, ip_range=ip_range, + npt_oop=npt_oop, unit_oop=unit_oop, oop_range=oop_range, + incident_angle=incident_angle, tilt_angle=tilt_angle, angle_unit=angle_unit, sample_orientation=sample_orientation, + correctSolidAngle=correctSolidAngle, polarization_factor=polarization_factor, normalization_factor=normalization_factor, + variance=variance, error_model=error_model, + mask=mask, dummy=dummy, delta_dummy=delta_dummy, + dark=dark, flat=flat, absorption=absorption, + metadata=metadata, + use_2d_engine=use_2d_engine, + use_missing_wedge=use_missing_wedge, + missing_wedge_percentile=missing_wedge_percentile, missing_wedge_threshold_bins=missing_wedge_threshold_bins, + ) if filename is not None: - save_integrate_result(filename, result) - - return result + save_integrate_result(filename, result_fiber) - integrate_grazing_incidence = integrate_fiber - integrate1d_grazing_incidence = integrate_grazing_incidence - integrate1d_fiber = integrate_fiber + return result_fiber def integrate2d_fiber(self, data, npt_ip=1000, unit_ip=None, ip_range=None, npt_oop=1000, unit_oop=None, oop_range=None, - sample_orientation=None, + incident_angle:float=None, + tilt_angle:float=None, + sample_orientation:int=None, filename=None, correctSolidAngle=True, + variance=None, error_model=None, mask=None, dummy=None, delta_dummy=None, - polarization_factor=None, dark=None, flat=None, + polarization_factor=None, dark=None, flat=None, absorption=None, method=("no", "histogram", "cython"), normalization_factor=1.0, angle_unit="rad", + use_2d_engine:bool=False, + use_missing_wedge:bool = False, + missing_wedge_percentile:float = None, + missing_wedge_threshold_bins:int = None, **kwargs) -> Integrate2dFiberResult: """Reshapes the data pattern as a function of two FiberUnits, additional inputs for sample_orientation @@ -480,6 +336,10 @@ def integrate2d_fiber(self, data, :return: regrouped intensity and unit arrays :rtype: Integrate2dResult """ + method = self._normalize_method(method, dim=2, default=self.DEFAULT_METHOD_2D) + if method.dimension != 2: + raise RuntimeError("Integration method is not 2D") + deprecated_params = get_deprecated_params_2d(**kwargs) npt_oop = deprecated_params.get('npt_oop', None) or npt_oop npt_ip = deprecated_params.get('npt_ip', None) or npt_ip @@ -491,20 +351,70 @@ def integrate2d_fiber(self, data, invalid_keys = [k for k in kwargs if any(ss in k for ss in ["oop", "ip", "unit", "range"])] if invalid_keys: logger.warning(f"""Key parameters {invalid_keys} are wrong or deprecated. - Valid parameters: npt_ip, unit_ip, ip_range, npt_oop, unit_oop, oop_range""") + Valid parameters: npt_ip, unit_ip, ip_range, npt_oop, unit_oop, oop_range""") + + result_fiber = self._integrate_fiber( + data=data, + method=method, + npt_ip=npt_ip, unit_ip=unit_ip, ip_range=ip_range, + npt_oop=npt_oop, unit_oop=unit_oop, oop_range=oop_range, + incident_angle=incident_angle, tilt_angle=tilt_angle, angle_unit=angle_unit, sample_orientation=sample_orientation, + correctSolidAngle=correctSolidAngle, polarization_factor=polarization_factor, normalization_factor=normalization_factor, + variance=variance, error_model=error_model, + mask=mask, dummy=dummy, delta_dummy=delta_dummy, + dark=dark, flat=flat, absorption=absorption, + use_2d_engine=use_2d_engine, + use_missing_wedge=use_missing_wedge, + missing_wedge_percentile=missing_wedge_percentile, missing_wedge_threshold_bins=missing_wedge_threshold_bins, + ) + if filename is not None: + save_integrate_result(filename, result_fiber) - unit_ip = unit_ip or 'qip_nm^-1' - unit_oop = unit_oop or 'qoop_nm^-1' - incident_angle = kwargs.get('incident_angle', None) - tilt_angle = kwargs.get('tilt_angle', None) + return result_fiber - angle_unit_parsed = to_unit(angle_unit, ANGLE_UNITS) + integrate_grazing_incidence = integrate1d_fiber + integrate_fiber = integrate1d_fiber + integrate1d_grazing_incidence = integrate1d_fiber + integrate2d_grazing_incidence = integrate2d_fiber + def _integrate_fiber(self, + data, + method, # Already normalized + npt_ip=1000, unit_ip="qip_nm^-1", ip_range=None, + npt_oop=1000, unit_oop="qoop_nm^-1", oop_range=None, + incident_angle:float=None, + tilt_angle:float=None, + sample_orientation:int=None, + correctSolidAngle=True, + variance=None, error_model=None, + mask=None, dummy=None, delta_dummy=None, + polarization_factor=None, dark=None, flat=None, absorption=None, + normalization_factor=1.0, + angle_unit="rad", + vertical_integration = True, # Only applicable to 1d engines + use_missing_wedge:bool = False, + missing_wedge_percentile:float = None, + missing_wedge_threshold_bins:int = None, + metadata = None, + use_2d_engine:bool=True, # legacy, every method goes through AzimuthalIntegrator.integrate2d_ng + ) -> Integrate1dFiberResult | Integrate2dFiberResult: + """ + Unify method between 1d and 2d + """ + if not isinstance(method, IntegrationMethod): + raise RuntimeError(f"method {method} needs to be normalized into a pyFAI.method_registry.IntegrationMethod instance") + + if method.split != "no": + logger.warning(f"Method {method} is using the pixel-splitting scheme ({method.split}). Be careful,the results could be wrong, no pixel split is recommended.") + + # Normalize grazing incidence angle parameters + angle_unit_parsed = to_unit(angle_unit, ANGLE_UNITS) if incident_angle is not None: incident_angle = (incident_angle % angle_unit_parsed.period) / angle_unit_parsed.scale if tilt_angle is not None: tilt_angle = (tilt_angle % angle_unit_parsed.period) / angle_unit_parsed.scale + # Consistency of Grazing Incidence params between the two units unit_ip = parse_fiber_unit(unit=unit_ip, sample_orientation=sample_orientation, incident_angle=incident_angle, @@ -515,28 +425,168 @@ def integrate2d_fiber(self, data, **config) self.reset_integrator(**config) - res2d = self.integrate2d_ng(data, npt_rad=npt_ip, npt_azim=npt_oop, - correctSolidAngle=correctSolidAngle, - mask=mask, dummy=dummy, delta_dummy=delta_dummy, - polarization_factor=polarization_factor, - dark=dark, flat=flat, method=method, - normalization_factor=normalization_factor, - radial_range=ip_range, - azimuth_range=oop_range, - unit=(unit_ip, unit_oop), - filename=None) - - intensity = res2d.intensity - sum_signal = res2d.sum_signal - count = res2d.count - sum_normalization = res2d.sum_normalization - sum_normalization2 = res2d.sum_normalization2 - sum_variance = res2d.sum_variance - std = res2d.std - sem = res2d.sem - - use_pixel_split = (isinstance(method, (tuple, list)) and method[0] != "no") or (isinstance(method, IntegrationMethod) and method.split != "no") - use_missing_wedge = kwargs.get("use_missing_wedge", False) + dummy, delta_dummy = self._normalize_dummies(dummy, delta_dummy, data) + empty = self._empty + shape = data.shape + mask, mask_crc, has_mask = self._normalize_mask(mask) + solidangle, solidangle_crc = self._normalize_solidangle(shape, correctSolidAngle, with_checksum=False) + polarization, polarization_crc = self._normalize_polarization(shape, polarization_factor, with_checksum=True) + dark, has_dark = self._normalize_dark(dark) + flat, has_flat = self._normalize_flat(flat) + error_model, variance = self._normalize_error_model_variance(data, method, dark, + error_model, variance) + + if method.dim == 1 and vertical_integration: + integrated_unit = unit_ip + integrated_bins = npt_ip + integrated_range = ip_range + projected_unit = unit_oop + projected_bins = npt_oop + projected_range = oop_range + integration_axis = -1 + else: + integrated_unit = unit_oop + integrated_bins = npt_oop + integrated_range = oop_range + projected_unit = unit_ip + projected_bins = npt_ip + projected_range = ip_range + integration_axis = -2 + + if method.dim == 1 and projected_bins is None: + raise RuntimeError(f" Needed the bins of the projected unit: {projected_unit}") + + result_tuple = None + result_fiber = None + if use_2d_engine: + res2d_fiber = self.integrate2d_ng(data, npt_rad=npt_ip, npt_azim=npt_oop, + correctSolidAngle=correctSolidAngle, + mask=mask, dummy=dummy, delta_dummy=delta_dummy, + polarization_factor=polarization_factor, + dark=dark, flat=flat, method=method, + normalization_factor=normalization_factor, + radial_range=ip_range, + azimuth_range=oop_range, + unit=(unit_ip, unit_oop), + filename=None) + result_tuple = Integrate2dtpl( + res2d_fiber.radial, + res2d_fiber.azimuthal, + res2d_fiber.intensity, + res2d_fiber.sem, + res2d_fiber.sum_signal, + res2d_fiber.sum_variance, + res2d_fiber.sum_normalization, + res2d_fiber.count, + res2d_fiber.std, + res2d_fiber.sem, + res2d_fiber.sum_normalization2, + ) + if method.dim == 1: + # Transform the 2d result into a 1d result + sum_signal = res2d_fiber.sum_signal.sum(axis=integration_axis) + count = res2d_fiber.count.sum(axis=integration_axis) + sum_normalization = res2d_fiber._sum_normalization.sum(axis=integration_axis) + mask_ = numpy.where(count == 0) + empty = dummy if dummy is not None else self._empty + if USE_NUMEXPR: + intensity = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization)") + else: + intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) + intensity[mask_] = empty + + if res2d_fiber.sigma is not None: + sum_variance = res2d_fiber.sum_variance.sum(axis=integration_axis) + if USE_NUMEXPR: + sigma = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sqrt(sum_variance) / sum_normalization)") + else: + sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) + sigma[mask_] = empty + else: + sum_variance = None + sigma = None + + if vertical_integration: + projected_vector = res2d_fiber.radial + else: + projected_vector = res2d_fiber.azimuthal + + result_tuple = Integrate1dtpl( + projected_vector, + intensity, + sigma, + sum_signal, + sum_variance, + sum_normalization, + count, + None, #std, + None, #sem, + None, #histo_normalization2, + ) + else: + # Engine implementation, like AzimuthalIntegrator + logger.warning(f"You are using engine implementation with method {method}. The method may not be available") + if method.pixel_splitting == "no" and method.algo == "histogram" and method.impl in ("python", "cython"): + mask = self.create_mask_generic( + data=data, + mask=mask, + unit1=unit_ip, + unit2=unit_oop, + # These parameters can be skipped here: + # unit1_range=ip_range, + # unit2_range=oop_range, + # dummy=dummy, + # delta_dummy=delta_dummy, + ) + params_integrator = { + "raw" : data, + "dark" : dark, + "flat" : flat, + "solidangle" : solidangle, + "polarization" : polarization, + "absorption" : absorption, + "dummy" : dummy, + "delta_dummy" : delta_dummy, + "normalization_factor" : normalization_factor, + "empty" : empty, + "variance" : variance, "error_model" : error_model, + "weighted_average" : method.weighted_average, + } + + if method.dim == 1: + # The integrated limits are masked here + mask = self.create_mask_generic( + data=data, + mask=mask, + unit1=integrated_unit, + unit1_range=integrated_range, + ) + params_integrator.update({ + "mask" : mask, + "radial" : self.center_array(shape, unit=projected_unit, scale=False), + "radial_range" : projected_range, + "npt" : projected_bins, + }) + elif method.dim == 2: + # For 2d, there's no need to mask before the integration + mask = self.create_mask_generic( + data=data, + mask=mask, + ) + params_integrator.update({ + "mask" : mask, + "radial" : self.center_array(shape, unit=unit_ip, scale=False), + "radial_range" : ip_range, + "azimuthal" : self.array_from_unit(shape, "center", unit_oop, scale=False), + "azimuth_range" : oop_range, + "bins" : (npt_ip, npt_oop), + "dark_variance" : None, + "allow_radial_neg" : True, + }) + histogrammer = method.class_funct_ng.function + result_tuple = histogrammer(**params_integrator) + + use_pixel_split = method.pixel_splitting != "no" if use_pixel_split and not use_missing_wedge: logger.warning(f""" Method {method} is using a pixel-splitting scheme without the missing wedge mask.\n\ @@ -546,57 +596,64 @@ def integrate2d_fiber(self, data, logger.warning("Pixel splitting + missing wedge masking is experimental and may not work as expected. Use with caution.") elif not use_pixel_split and use_missing_wedge: logger.warning("Missing wedge masking should not be used if pixel splitting is disable. The results may be incorrect.") - - empty = self._empty if use_missing_wedge: # Mask by percentile or by threshold bins - missing_wedge_percentile = kwargs.get("missing_wedge_percentile") if missing_wedge_percentile: - missing_wedge_mask = get_missing_wedge_mask_by_percentile(result=res2d, percentile=missing_wedge_percentile) + missing_wedge_mask = get_missing_wedge_mask_by_percentile(result=result_tuple, percentile=missing_wedge_percentile) else: - missing_wedge_mask = get_missing_wedge_mask(res2d, threshold_bins=kwargs.get("missing_wedge_threshold_bins", None)) - intensity[missing_wedge_mask] = empty - sum_signal[missing_wedge_mask] = empty - sum_normalization[missing_wedge_mask] = empty - count[missing_wedge_mask] = 0 - sum_normalization[missing_wedge_mask] = empty - if sum_normalization2 is not None: - sum_normalization2[missing_wedge_mask] = empty - sum_variance[missing_wedge_mask] = empty - std[missing_wedge_mask] = empty - sem[missing_wedge_mask] = empty - - result2d_fiber = Integrate2dFiberResult( - intensity, - res2d.radial, - res2d.azimuthal, - sem, - ) - result2d_fiber._set_method_called("integrate2d") - result2d_fiber._set_compute_engine(str(res2d.method)) - result2d_fiber._set_method(res2d.method) - result2d_fiber._set_ip_unit(res2d.radial_unit) - result2d_fiber._set_oop_unit(res2d.azimuthal_unit) - result2d_fiber._set_count(res2d.count) - result2d_fiber._set_has_dark_correction(res2d.has_dark_correction) - result2d_fiber._set_has_flat_correction(res2d.has_flat_correction) - result2d_fiber._set_has_mask_applied(res2d.has_mask_applied) - result2d_fiber._set_polarization_factor(res2d.polarization_factor) - result2d_fiber._set_normalization_factor(res2d.normalization_factor) - result2d_fiber._set_metadata(res2d.metadata) - result2d_fiber._set_sum_signal(sum_signal) - result2d_fiber._set_sum_normalization(sum_normalization) - result2d_fiber._set_sum_normalization2(sum_normalization2) - result2d_fiber._set_sum_variance(sum_variance) - result2d_fiber._set_std(std) - result2d_fiber._set_sem(sem) - - if filename is not None: - save_integrate_result(filename, result2d_fiber) - - return result2d_fiber - - integrate2d_grazing_incidence = integrate2d_fiber + missing_wedge_mask = get_missing_wedge_mask(result_tuple, threshold_bins=missing_wedge_threshold_bins) + result_tuple.intensity[missing_wedge_mask] = empty + result_tuple.signal[missing_wedge_mask] = empty + result_tuple.normalization[missing_wedge_mask] = empty + result_tuple.count[missing_wedge_mask] = 0 + if result_tuple.norm_sq is not None: + result_tuple.norm_sq[missing_wedge_mask] = empty + result_tuple.variance[missing_wedge_mask] = empty + result_tuple.std[missing_wedge_mask] = empty + result_tuple.sem[missing_wedge_mask] = empty + + if result_tuple is not None: + if method.dim == 1: + result_fiber = Integrate1dFiberResult( + intensity=result_tuple.intensity, + integrated=result_tuple.position * integrated_unit.scale, + sigma=result_tuple.sigma, + ) + result_fiber._set_vertical_integration(vertical_integration) + result_fiber._set_unit(projected_unit) + elif method.dim == 2: + result_fiber = Integrate2dFiberResult( + intensity=result_tuple.intensity, + inplane=result_tuple.radial, + outofplane=result_tuple.azimuthal, + sigma=result_tuple.sigma, + ) + result_fiber._set_ip_unit(unit_ip) + result_fiber._set_oop_unit(unit_oop) + result_fiber._set_sum_signal(result_tuple.signal) + result_fiber._set_sum_normalization(result_tuple.normalization) + result_fiber._set_count(result_tuple.count) + result_fiber._set_sum_variance(result_tuple.variance) + result_fiber._set_std(result_tuple.std) + result_fiber._set_sem(result_tuple.sem) + result_fiber._set_sum_normalization2(result_tuple.norm_sq) + result_fiber._set_compute_engine = f"{method.class_funct_ng.function.__module__}:{method.class_funct_ng.function.__name__}" + result_fiber._set_method(method) + result_fiber._set_method_called(f"integrate{method.dim}d") + result_fiber._set_has_dark_correction(has_dark) + result_fiber._set_has_flat_correction(has_flat) + result_fiber._set_has_mask_applied(has_mask) + result_fiber._set_polarization_factor(polarization_factor) + result_fiber._set_normalization_factor(normalization_factor) + result_fiber._set_metadata(metadata) + result_fiber._set_error_model(error_model) + result_fiber._set_poni(PoniFile(self)) + result_fiber._set_has_solidangle_correction(correctSolidAngle) + result_fiber._set_weighted_average(method.weighted_average) + + if result_fiber is None and not use_2d_engine: + logger.error(f"No result. Maybe {method} is not available yet for 1d engines.") + return result_fiber def integrate2d_polar(self, polar_degrees=True, radial_unit="nm^-1", rotate=False, **kwargs): """Reshapes the data pattern as a function of polar angle=arctan(qOOP / qIP) versus q modulus. @@ -683,13 +740,25 @@ def integrate1d_exitangles(self, angle_degrees=True, vertical_integration=True, integrate1d_exitangles.__doc__ += "\n" + integrate_fiber.__doc__ -def get_missing_wedge_mask(result: Integrate2dFiberResult, threshold_bins=None) -> numpy.ndarray: +def get_missing_wedge_mask(result, threshold_bins=None) -> numpy.ndarray: """Calculate a mask for the missing wedge after calculating a count threshold. :param result: Integrate2dFiberResult :param threshold_bins: number of bins to histogram the normalization values """ - return result.sum_normalization < get_missing_wedge_threshold(intensity=result.sum_normalization, threshold_bins=threshold_bins) + if "sum_normalization" in dir(result): + intensity = result.sum_normalization + elif "normalization" in dir(result): + intensity = result.normalization + return intensity < get_missing_wedge_threshold(intensity=intensity, threshold_bins=threshold_bins) + +def get_missing_wedge_mask_by_percentile(result, percentile=20) -> numpy.ndarray: + """Calculate a mask for the missing wedge based on the percentage of bins of result.count array falling into the missing wedge. + + :param result: Integrate2DFiberResult, the return of a FiberIntegrator.integrate2d_grazing_incidence + :param percentile: float (0 -> 100), upper limit of bins to filter out of the result.count array + """ + return result.count < numpy.percentile(result.count, percentile) def get_missing_wedge_threshold(intensity:numpy.ndarray, threshold_bins=None) -> float: """Calculate the count threshold to mask the missing wedge. @@ -702,12 +771,4 @@ def get_missing_wedge_threshold(intensity:numpy.ndarray, threshold_bins=None) -> """ threshold_bins = threshold_bins or max(intensity.shape) counts, bin = numpy.histogram(intensity.ravel(), bins=threshold_bins) - return bin[counts.argmax()] / 2 - -def get_missing_wedge_mask_by_percentile(result: Integrate2dFiberResult, percentile=20) -> numpy.ndarray: - """Calculate a mask for the missing wedge based on the percentage of bins of result.count array falling into the missing wedge. - - :param result: Integrate2DFiberResult, the return of a FiberIntegrator.integrate2d_grazing_incidence - :param percentile: float (0 -> 100), upper limit of bins to filter out of the result.count array - """ - return result.count < numpy.percentile(result.count, percentile) + return bin[counts.argmax()] / 2 \ No newline at end of file From 5de74142b1b473b7f205eb45c467f9723071caca Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 17:41:52 +0100 Subject: [PATCH 13/18] change defaults in fiber --- src/pyFAI/integrator/fiber.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index 2307f8684..d7e4418d9 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -198,8 +198,8 @@ def reset_integrator(self, incident_angle, tilt_angle, sample_orientation): self._cache_parameters['sample_orientation'] = sample_orientation def integrate1d_fiber(self, data, - npt_ip=None, unit_ip=None, ip_range=None, - npt_oop=None, unit_oop=None, oop_range=None, + npt_ip=None, unit_ip="qip_nm^-1", ip_range=None, + npt_oop=None, unit_oop="qoop_nm^-1", oop_range=None, vertical_integration = True, incident_angle:float=None, tilt_angle:float=None, @@ -213,7 +213,7 @@ def integrate1d_fiber(self, data, normalization_factor=1.0, angle_unit="rad", metadata=None, - use_2d_engine:bool=False, + use_2d_engine:bool=True, use_missing_wedge:bool = False, missing_wedge_percentile:float = None, missing_wedge_threshold_bins:int = None, @@ -288,8 +288,8 @@ def integrate1d_fiber(self, data, return result_fiber def integrate2d_fiber(self, data, - npt_ip=1000, unit_ip=None, ip_range=None, - npt_oop=1000, unit_oop=None, oop_range=None, + npt_ip=1000, unit_ip="qip_nm^-1", ip_range=None, + npt_oop=1000, unit_oop="qoop_nm^-1", oop_range=None, incident_angle:float=None, tilt_angle:float=None, sample_orientation:int=None, @@ -301,7 +301,7 @@ def integrate2d_fiber(self, data, method=("no", "histogram", "cython"), normalization_factor=1.0, angle_unit="rad", - use_2d_engine:bool=False, + use_2d_engine:bool=True, use_missing_wedge:bool = False, missing_wedge_percentile:float = None, missing_wedge_threshold_bins:int = None, @@ -380,8 +380,8 @@ def integrate2d_fiber(self, data, def _integrate_fiber(self, data, method, # Already normalized - npt_ip=1000, unit_ip="qip_nm^-1", ip_range=None, - npt_oop=1000, unit_oop="qoop_nm^-1", oop_range=None, + npt_ip=None, unit_ip=None, ip_range=None, + npt_oop=None, unit_oop=None, oop_range=None, incident_angle:float=None, tilt_angle:float=None, sample_orientation:int=None, @@ -459,6 +459,16 @@ def _integrate_fiber(self, result_tuple = None result_fiber = None if use_2d_engine: + # Here, radial is always in-plane, azimuthal is always out-of-plane + # For integration1d: + # - If vertical_integration=True, we need explicit npt_oop, npt_ip could be default + # - If vertical_integration=False, we need explicit npt_ip, npt_oop could be default + if method.dim == 1: + if vertical_integration: + npt_ip = npt_ip or 1000 + else: + npt_oop = npt_oop or 1000 + res2d_fiber = self.integrate2d_ng(data, npt_rad=npt_ip, npt_azim=npt_oop, correctSolidAngle=correctSolidAngle, mask=mask, dummy=dummy, delta_dummy=delta_dummy, From 9404624e94393647d2008a2fa4d4412862e5c2bd Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 17:54:31 +0100 Subject: [PATCH 14/18] fix tests --- src/pyFAI/integrator/fiber.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index d7e4418d9..dca79f05c 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -517,9 +517,9 @@ def _integrate_fiber(self, sigma = None if vertical_integration: - projected_vector = res2d_fiber.radial - else: projected_vector = res2d_fiber.azimuthal + else: + projected_vector = res2d_fiber.radial result_tuple = Integrate1dtpl( projected_vector, From 392ba0d2bf8799b0c1116cddf3de92123b3b52fa Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 18:03:09 +0100 Subject: [PATCH 15/18] simplify no numexpr --- src/pyFAI/integrator/fiber.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index dca79f05c..cf37ad211 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -496,21 +496,16 @@ def _integrate_fiber(self, # Transform the 2d result into a 1d result sum_signal = res2d_fiber.sum_signal.sum(axis=integration_axis) count = res2d_fiber.count.sum(axis=integration_axis) - sum_normalization = res2d_fiber._sum_normalization.sum(axis=integration_axis) + sum_normalization = res2d_fiber.sum_normalization.sum(axis=integration_axis) + intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) + mask_ = numpy.where(count == 0) empty = dummy if dummy is not None else self._empty - if USE_NUMEXPR: - intensity = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization)") - else: - intensity = numpy.where(sum_normalization <= 0, 0.0, sum_signal / sum_normalization) intensity[mask_] = empty if res2d_fiber.sigma is not None: sum_variance = res2d_fiber.sum_variance.sum(axis=integration_axis) - if USE_NUMEXPR: - sigma = numexpr.evaluate("where(sum_normalization <= 0, 0.0, sqrt(sum_variance) / sum_normalization)") - else: - sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) + sigma = numpy.where(sum_normalization <= 0, 0.0, numpy.sqrt(sum_variance) / sum_normalization) sigma[mask_] = empty else: sum_variance = None @@ -520,7 +515,6 @@ def _integrate_fiber(self, projected_vector = res2d_fiber.azimuthal else: projected_vector = res2d_fiber.radial - result_tuple = Integrate1dtpl( projected_vector, intensity, @@ -529,8 +523,8 @@ def _integrate_fiber(self, sum_variance, sum_normalization, count, - None, #std, - None, #sem, + sigma, #std + sigma, #sem None, #histo_normalization2, ) else: From 4db05c1c92460e03e449fdf0dc6f9b972bca272f Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 18:04:34 +0100 Subject: [PATCH 16/18] fix _set_compute_engine --- src/pyFAI/integrator/fiber.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index cf37ad211..9b6665ce6 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -641,7 +641,7 @@ def _integrate_fiber(self, result_fiber._set_std(result_tuple.std) result_fiber._set_sem(result_tuple.sem) result_fiber._set_sum_normalization2(result_tuple.norm_sq) - result_fiber._set_compute_engine = f"{method.class_funct_ng.function.__module__}:{method.class_funct_ng.function.__name__}" + result_fiber._set_compute_engine(f"{method.class_funct_ng.function.__module__}:{method.class_funct_ng.function.__name__}") result_fiber._set_method(method) result_fiber._set_method_called(f"integrate{method.dim}d") result_fiber._set_has_dark_correction(has_dark) From 8e1bc543e994225586d3392292186e0abebb3fd4 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Mon, 23 Mar 2026 18:22:27 +0100 Subject: [PATCH 17/18] do not use create_mask --- src/pyFAI/integrator/azimuthal.py | 13 +++++++------ src/pyFAI/integrator/fiber.py | 31 +++++++++---------------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/pyFAI/integrator/azimuthal.py b/src/pyFAI/integrator/azimuthal.py index 03d455a90..70b26a4b2 100644 --- a/src/pyFAI/integrator/azimuthal.py +++ b/src/pyFAI/integrator/azimuthal.py @@ -281,12 +281,13 @@ def integrate1d(self, data, npt, *, method.method[3] in ("python", "cython")): integr = method.class_funct_ng.function # should be histogram[_engine].histogram1d_engine if azimuth_range: - mask = self.create_mask_generic( - data=data, - mask=mask, - unit2=units.CHI_RAD, - unit2_range=azimuth_range, - ) + chi_min, chi_max = azimuth_range + chi = self.center_array(shape, unit=units.CHI_RAD, scale=False) + azim_mask = numpy.logical_or(chi > chi_max, chi < chi_min) + if mask is None: + mask = azim_mask + else: + mask = numpy.logical_or(mask, azim_mask) radial = self.center_array(shape, unit=unit, scale=False) intpl = integr(radial, npt, data, dark=dark, diff --git a/src/pyFAI/integrator/fiber.py b/src/pyFAI/integrator/fiber.py index 9b6665ce6..f9c6bd849 100644 --- a/src/pyFAI/integrator/fiber.py +++ b/src/pyFAI/integrator/fiber.py @@ -530,18 +530,7 @@ def _integrate_fiber(self, else: # Engine implementation, like AzimuthalIntegrator logger.warning(f"You are using engine implementation with method {method}. The method may not be available") - if method.pixel_splitting == "no" and method.algo == "histogram" and method.impl in ("python", "cython"): - mask = self.create_mask_generic( - data=data, - mask=mask, - unit1=unit_ip, - unit2=unit_oop, - # These parameters can be skipped here: - # unit1_range=ip_range, - # unit2_range=oop_range, - # dummy=dummy, - # delta_dummy=delta_dummy, - ) + if method.pixel_splitting == "no" and method.algo == "histogram" and method.impl in ("python", "cython"): params_integrator = { "raw" : data, "dark" : dark, @@ -559,12 +548,14 @@ def _integrate_fiber(self, if method.dim == 1: # The integrated limits are masked here - mask = self.create_mask_generic( - data=data, - mask=mask, - unit1=integrated_unit, - unit1_range=integrated_range, - ) + if integrated_range: + r0, r1 = integrated_range + chi = self.center_array(shape, unit=integrated_unit, scale=False) + integration_mask = numpy.logical_or(chi > r1, chi < r0) + if mask is None: + mask = integration_mask + else: + mask = numpy.logical_or(mask, integration_mask) params_integrator.update({ "mask" : mask, "radial" : self.center_array(shape, unit=projected_unit, scale=False), @@ -573,10 +564,6 @@ def _integrate_fiber(self, }) elif method.dim == 2: # For 2d, there's no need to mask before the integration - mask = self.create_mask_generic( - data=data, - mask=mask, - ) params_integrator.update({ "mask" : mask, "radial" : self.center_array(shape, unit=unit_ip, scale=False), From 784028a3cecd1e8d752a149715b3e5d40b99dce6 Mon Sep 17 00:00:00 2001 From: edgar1993a Date: Tue, 24 Mar 2026 10:07:18 +0100 Subject: [PATCH 18/18] common back to original --- src/pyFAI/integrator/common.py | 110 ++++++--------------------------- 1 file changed, 19 insertions(+), 91 deletions(-) diff --git a/src/pyFAI/integrator/common.py b/src/pyFAI/integrator/common.py index 0eb993b49..23bc53e35 100644 --- a/src/pyFAI/integrator/common.py +++ b/src/pyFAI/integrator/common.py @@ -196,85 +196,6 @@ def create_mask(self, data, mask=None, This method tries to accommodate various types of masks (like valid=0 & masked=-1, ...) - Note for the developer: we use a lot of numpy.logical_or in this method, - the out= argument allows to recycle buffers and save considerable time in - allocating temporary arrays. - """ - if radial_range is not None: - if unit is None: - raise RuntimeError("unit is needed when building a mask based on radial_range") - elif isinstance(unit, (tuple, list)) and len(unit) == 2: - radial_unit = units.to_unit(unit[0]) - else: - radial_unit = units.to_unit(unit) - else: - radial_unit = None - if azimuth_range is not None: - if isinstance(unit, (tuple, list)) and len(unit) == 2: - azimuth_unit = units.to_unit(unit[1]) - else: - logger.info("no azimuthal unit provided in `create_mask`, defaulting to `chi_rad`") - azimuth_unit = units.CHI_RAD - else: - azimuth_unit = None - return self.create_mask_generic( - data=data, - mask=mask, - dummy=dummy, - delta_dummy=delta_dummy, - unit1=radial_unit, - unit1_range=radial_range, - unit2=azimuth_unit, - unit2_range=azimuth_range, - mode=mode, - ) - - create_mask_azimuthal = create_mask - - def create_mask_generic(self, data, mask=None, - dummy=None, delta_dummy=None, - unit1=None, unit1_range=None, - unit2=None, unit2_range=None, - mode="normal", - ): - """ - Combines various masks into another one. - - :param data: input array of data - :type data: ndarray - :param mask: input mask (if none, self.mask is used) - :type mask: ndarray - :param dummy: value of dead pixels - :type dummy: float - :param delta_dummy: precision of dummy pixels - :type delta_dummy: float - :param projected_unit: unit to use for projected_unit_range (e.g. radial unit for radial_range) - :type projected_unit: pyFAI.units.Unit - :param projected_unit_range: range in projected unit to mask out (e.g. radial range for radial mask) - :type projected_unit_range: (float, float) - :param integrated_unit: unit to use for integrated_unit_range (e.g. azimuthal unit for azimuth_range) - :type integrated_unit: pyFAI.units.Unit - :param mode: can be "normal" or "numpy" (inverted) or "where" applied to the mask - :type mode: str - - :return: the new mask - :rtype: ndarray of bool - - This method combine two masks (dynamic mask from *data & - dummy* and *mask*) to generate a new one with the 'or' binary - operation. One can adjust the level, with the *dummy* and - the *delta_dummy* parameter, when you consider the *data* - values needs to be masked out. - - This method can work in two different *mode*: - - * "normal": False for valid pixels, True for bad pixels - * "numpy": True for valid pixels, false for others - * "where": does a numpy.where on the "numpy" output - - This method tries to accommodate various types of masks (like - valid=0 & masked=-1, ...) - Note for the developer: we use a lot of numpy.logical_or in this method, the out= argument allows to recycle buffers and save considerable time in allocating temporary arrays. @@ -309,18 +230,25 @@ def create_mask_generic(self, data, mask=None, else: logical_or(mask, abs(data - dummy) <= delta_dummy, out=mask) - if unit1_range is not None: - if unit1 is None: - raise RuntimeError("projected_unit is needed when building a mask based on projected_unit_range") - unit1_array = self.array_from_unit(shape, "center", unit1) - logical_or(mask, unit1_array < unit1_range[0], out=mask) - logical_or(mask, unit1_array > unit1_range[1], out=mask) - if unit2_range is not None: - if unit2 is None: - raise RuntimeError("integrated_unit is needed when building a mask based on integrated_unit_range") - unit2_array = self.array_from_unit(shape, "center", unit2) - logical_or(mask, unit2_array < unit2_range[0], out=mask) - logical_or(mask, unit2_array > unit2_range[1], out=mask) + if radial_range is not None: + if unit is None: + raise RuntimeError("unit is needed when building a mask based on radial_range") + elif isinstance(unit, (tuple, list)) and len(unit) == 2: + radial_unit = units.to_unit(unit[0]) + else: + radial_unit = units.to_unit(unit) + rad = self.array_from_unit(shape, "center", radial_unit, scale=False) + logical_or(mask, rad < radial_range[0], out=mask) + logical_or(mask, rad > radial_range[1], out=mask) + if azimuth_range is not None: + if isinstance(unit, (tuple, list)) and len(unit) == 2: + azimuth_unit = units.to_unit(unit[1]) + else: + logger.info("no azimuthal unit provided in `create_mask`, defaulting to `chi_rad`") + azimuth_unit = units.CHI_RAD + chi = self.array_from_unit(shape, "center", azimuth_unit, scale=False) + logical_or(mask, chi < azimuth_range[0], out=mask) + logical_or(mask, chi > azimuth_range[1], out=mask) # Prepare alternative representation for output: if mode == "numpy":