Skip to content

Commit 0faec93

Browse files
authored
Merge pull request #2691 from kif/2634_2pi_gi
Ensure 2pi periodicity is not used in 2D integration with fiber integration
2 parents be80c11 + c16ae23 commit 0faec93

16 files changed

+125
-90
lines changed

src/pyFAI/engines/histogram_engine.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
__contact__ = "Jerome.Kieffer@ESRF.eu"
2727
__license__ = "MIT"
2828
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
29-
__date__ = "06/10/2025"
29+
__date__ = "18/11/2025"
3030
__status__ = "development"
3131

3232
import logging
33+
from math import pi
3334
import numpy
3435
from ..utils.mathutil import EPS32
3536
from .preproc import preproc as preproc_np
@@ -42,7 +43,7 @@
4243
preproc = preproc_np
4344
else:
4445
preproc = preproc_cy
45-
46+
twopi = 2.0*pi
4647

4748

4849
def histogram1d_engine(radial, npt,
@@ -175,7 +176,7 @@ def histogram2d_engine(radial, azimuthal, bins,
175176
azimuth_range=None,
176177
allow_radial_neg=False,
177178
chiDiscAtPi=True,
178-
clip_pos1=True
179+
pos1_period=twopi
179180
):
180181
"""Implementation of 2D rebinning engine using pure numpy histograms
181182

src/pyFAI/ext/histogram.pyx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Can be replaced by ``silx.math.histogramnd``.
3838
"""
3939

4040
__author__ = "Jérôme Kieffer"
41-
__date__ = "25/04/2024"
41+
__date__ = "18/11/2025"
4242
__license__ = "MIT"
4343
__copyright__ = "2011-2022, ESRF"
4444
__contact__ = "jerome.kieffer@esrf.fr"
@@ -583,7 +583,7 @@ def histogram2d_engine(radial, azimuthal,
583583
azimuth_range=None,
584584
bint allow_radial_neg=False,
585585
bint chiDiscAtPi=1,
586-
bint clip_pos1=True
586+
position_t pos1_period=twopi
587587
):
588588
"""Implementation of 2D rebinning engine using pure numpy histograms
589589
@@ -609,7 +609,8 @@ def histogram2d_engine(radial, azimuthal,
609609
:param azimuth_range: enforce boundaries in azimuthal dimension, 2tuple with lower and upper bound
610610
:param allow_radial_neg: clip negative radial position (can a dimension be negative ?)
611611
:param chiDiscAtPi: set the azimuthal discontinuity at π (True) or at 0/2π (False)
612-
:param clip_pos1: clip the azimuthal range to [-π π] (or [0 2π]), set to False to deactivate behavior.
612+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
613+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
613614
614615
NaN are always considered as invalid values
615616
@@ -659,6 +660,7 @@ def histogram2d_engine(radial, azimuthal,
659660
bint do_dark = False, do_flat = False, do_polarization = False, do_solidangle = False, do_absorption=False
660661
bint do_variance=error_model, do_dark_variance=False
661662
preproc_t value
663+
bint clip_pos1 = True if pos1_period>0 else False
662664

663665
if variance is not None:
664666
assert variance.size == size, "variance size matches"

src/pyFAI/ext/regrid_common.pxi

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Some are defined in the associated header file .pxd
3232

3333
__author__ = "Jérôme Kieffer"
3434
__contact__ = "Jerome.kieffer@esrf.fr"
35-
__date__ = "12/11/2024"
35+
__date__ = "18/11/2025"
3636
__status__ = "stable"
3737
__license__ = "MIT"
3838

@@ -373,16 +373,20 @@ cdef inline floating area4(floating a0,
373373
def _sp_area4(floating a0, floating a1, floating b0, floating b1, floating c0, floating c1, floating d0, floating d1):
374374
return area4(a0, a1, b0, b1, c0, c1, d0, d1)
375375

376-
cdef inline position_t _recenter_helper(position_t azim, bint chiDiscAtPi) noexcept nogil:
376+
cdef inline position_t _recenter_helper(position_t azim,
377+
position_t period,
378+
bint chiDiscAtPi=1) noexcept nogil:
377379
"""Helper function
378380
"""
379-
if (chiDiscAtPi and azim<0) or (not chiDiscAtPi and azim<pi):
380-
return azim + twopi
381+
if (chiDiscAtPi and azim<0) or (not chiDiscAtPi and azim<0.5*period):
382+
return azim + period
381383
else:
382384
return azim
383385

384386

385-
cdef inline position_t _recenter(position_t[:, ::1] pixel, bint chiDiscAtPi) noexcept nogil:
387+
cdef inline position_t _recenter(position_t[:, ::1] pixel,
388+
position_t pos1_period=twopi,
389+
bint chiDiscAtPi=1) noexcept nogil:
386390
cdef position_t a0, a1, b0, b1, c0, c1, d0, d1, center1, area, hi
387391
a0 = pixel[0, 0]
388392
a1 = pixel[0, 1]
@@ -393,27 +397,29 @@ cdef inline position_t _recenter(position_t[:, ::1] pixel, bint chiDiscAtPi) noe
393397
d0 = pixel[3, 0]
394398
d1 = pixel[3, 1]
395399
area = area4p(a0, a1, b0, b1, c0, c1, d0, d1) # check if the quad is crossed
396-
if area>0:
400+
if pos1_period>0.0 and area>0:
397401
# area are expected to be negative except for pixel on the boundary
398-
a1 = _recenter_helper(a1, chiDiscAtPi)
399-
b1 = _recenter_helper(b1, chiDiscAtPi)
400-
c1 = _recenter_helper(c1, chiDiscAtPi)
401-
d1 = _recenter_helper(d1, chiDiscAtPi)
402+
a1 = _recenter_helper(a1, pos1_period, chiDiscAtPi)
403+
b1 = _recenter_helper(b1, pos1_period, chiDiscAtPi)
404+
c1 = _recenter_helper(c1, pos1_period, chiDiscAtPi)
405+
d1 = _recenter_helper(d1, pos1_period, chiDiscAtPi)
402406
center1 = 0.25 * (a1 + b1 + c1 + d1)
403-
hi = pi if chiDiscAtPi else twopi
407+
hi = 0.5*pos1_period if chiDiscAtPi else pos1_period
404408
if center1>hi:
405-
a1 -= twopi
406-
b1 -= twopi
407-
c1 -= twopi
408-
d1 -= twopi
409+
a1 -= pos1_period
410+
b1 -= pos1_period
411+
c1 -= pos1_period
412+
d1 -= pos1_period
409413
pixel[0, 1] = a1
410414
pixel[1, 1] = b1
411415
pixel[2, 1] = c1
412416
pixel[3, 1] = d1
413417
area = area4p(a0, a1, b0, b1, c0, c1, d0, d1)
414418
return area
415419

416-
def recenter(position_t[:, ::1] pixel, bint chiDiscAtPi=1):
420+
def recenter(position_t[:, ::1] pixel,
421+
position_t pos1_period=twopi,
422+
bint chiDiscAtPi=True):
417423
"""This function checks the pixel to be on the azimuthal discontinuity
418424
via the sign of its algebraic area and recenters the corner coordinates in a
419425
consistent manner to have all azimuthal coordinate in
@@ -424,7 +430,7 @@ def recenter(position_t[:, ::1] pixel, bint chiDiscAtPi=1):
424430
:param chiDiscAtPi: set to 0 to indicate the range goes from 0-2π instead of the default -π:π
425431
:return: signed area (approximate & negative)
426432
"""
427-
return _recenter(pixel, chiDiscAtPi)
433+
return _recenter(pixel, pos1_period, chiDiscAtPi)
428434

429435

430436
cdef inline any_t _clip(any_t value, any_t min_val, any_t max_val) noexcept nogil:

src/pyFAI/ext/splitBBox.pyx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Splitting is done on the pixel's bounding box similar to fit2D
3636

3737
__author__ = "Jérôme Kieffer"
3838
__contact__ = "Jerome.kieffer@esrf.fr"
39-
__date__ = "15/06/2024"
39+
__date__ = "18/11/2025"
4040
__status__ = "stable"
4141
__license__ = "MIT"
4242

@@ -124,6 +124,7 @@ def histoBBox1d(weights,
124124
acc_t inv_area, delta_right, delta_left
125125
acc_t[::1] sum_data, sum_count
126126

127+
127128
cdata = numpy.ascontiguousarray(weights.ravel(), dtype=data_d)
128129
cpos0 = numpy.ascontiguousarray(pos0.ravel(), dtype=position_d)
129130
dpos0 = numpy.ascontiguousarray(delta_pos0.ravel(), dtype=position_d)
@@ -526,11 +527,11 @@ def histoBBox2d(weights,
526527
solidangle=None,
527528
polarization=None,
528529
bint allow_pos0_neg=0,
530+
position_t pos1_period=twopi,
529531
bint chiDiscAtPi=1,
530532
empty=0.0,
531533
double normalization_factor=1.0,
532534
int coef_power=1,
533-
bint clip_pos1=1,
534535
**back_compat_kwargs):
535536
"""
536537
Calculate 2D histogram of pos0(tth),pos1(chi) weighted by weights
@@ -557,8 +558,8 @@ def histoBBox2d(weights,
557558
:param empty: value of output bins without any contribution when dummy is None
558559
:param normalization_factor: divide the result by this value
559560
:param coef_power: set to 2 for variance propagation, leave to 1 for mean calculation
560-
:param clip_pos1: clip the azimuthal range to -pi/pi (or 0-2pi), set to False to deactivate behavior
561-
561+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
562+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
562563
563564
:return: I, bin_centers0, bin_centers1, weighted histogram(2D), unweighted histogram (2D)
564565
"""
@@ -611,6 +612,7 @@ def histoBBox2d(weights,
611612
Py_ssize_t bin0_max, bin0_min, bin1_max, bin1_min
612613
bint check_mask = False, check_dummy = False
613614
bint do_dark = False, do_flat = False, do_polarization = False, do_solidangle = False
615+
bint clip_pos1 = True if pos1_period>0 else False
614616

615617
if mask is not None:
616618
assert mask.size == size, "mask size"
@@ -810,12 +812,11 @@ def histoBBox2d_engine(weights,
810812
polarization=None,
811813
absorption=None,
812814
bint allow_pos0_neg=False,
815+
position_t pos1_period=twopi,
813816
bint chiDiscAtPi=1,
814817
data_t empty=0.0,
815818
double normalization_factor=1.0,
816-
bint weighted_average=True,
817-
bint clip_pos1=True
818-
):
819+
bint weighted_average=True):
819820
"""
820821
Calculate 2D histogram of pos0(tth),pos1(chi) weighted by weights
821822
@@ -845,7 +846,8 @@ def histoBBox2d_engine(weights,
845846
:param empty: value of output bins without any contribution when dummy is None
846847
:param normalization_factor: divide the result by this value
847848
:param bool weighted_average: set to False to use an unweighted mean (similar to legacy) instead of the weighted average.
848-
:param clip_pos1: clip the azimuthal range to [-pi pi] (or [0 2pi]), set to False to deactivate behavior
849+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
850+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
849851
:return: Integrate2dtpl namedtuple: "radial azimuthal intensity error signal variance normalization count"
850852
"""
851853

@@ -887,6 +889,7 @@ def histoBBox2d_engine(weights,
887889
bint do_dark = False, do_flat = False, do_polarization = False, do_solidangle = False
888890
bint do_absorption = False
889891
preproc_t value
892+
bint clip_pos1 = True if pos1_period>0 else False
890893

891894
if variance is not None:
892895
assert variance.size == size, "variance size"

src/pyFAI/ext/splitBBoxCSC.pyx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Serial implementation based on a sparse CSC matrix multiplication
3737

3838
__author__ = "Jérôme Kieffer"
3939
__contact__ = "Jerome.kieffer@esrf.fr"
40-
__date__ = "26/01/2024"
40+
__date__ = "18/11/2025"
4141
__status__ = "stable"
4242
__license__ = "MIT"
4343

@@ -79,7 +79,7 @@ class HistoBBox1d(CscIntegrator, SplitBBoxIntegrator):
7979
unit="undefined",
8080
empty=None,
8181
bint chiDiscAtPi=True,
82-
bint clip_pos1=False):
82+
position_t pos1_period=twopi):
8383
"""
8484
:param pos0: 1D array with pos0: tth or q_vect or r ...
8585
:param delta_pos0: 1D array with delta pos0: max center-corner distance
@@ -93,14 +93,16 @@ class HistoBBox1d(CscIntegrator, SplitBBoxIntegrator):
9393
:param unit: can be 2th_deg or r_nm^-1 ...
9494
:param empty: value for bins without contributing pixels
9595
:param chiDiscAtPi: tell if azimuthal discontinuity is at 0° or 180°
96-
:param clip_pos1: clip the azimuthal range to [-π π] (or [0 2π] depending on chiDiscAtPi), set to False to deactivate behavior
96+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
97+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
9798
"""
9899
self.unit = unit
99100
self.space = tuple(str(u).split("_")[0] for u in unit) if isinstance(unit, (tuple, list)) else str(unit).split("_")[0]
100101
SplitBBoxIntegrator.__init__(self, pos0, delta_pos0, pos1, delta_pos1,
101102
bins, pos0_range, pos1_range,
102103
mask, mask_checksum,
103-
allow_pos0_neg, chiDiscAtPi, clip_pos1=clip_pos1)
104+
allow_pos0_neg, chiDiscAtPi,
105+
pos1_period=pos1_period)
104106

105107

106108
self.delta = (self.pos0_max - self.pos0_min) / (<position_t> (self.bins))
@@ -155,7 +157,7 @@ class HistoBBox2d(CscIntegrator, SplitBBoxIntegrator):
155157
unit="undefined",
156158
empty=None,
157159
bint chiDiscAtPi=True,
158-
bint clip_pos1=True):
160+
position_t pos1_period=twopi):
159161
"""
160162
:param pos0: 1D array with pos0: tth or q_vect
161163
:param delta_pos0: 1D array with delta pos0: max center-corner distance
@@ -169,11 +171,12 @@ class HistoBBox2d(CscIntegrator, SplitBBoxIntegrator):
169171
:param unit: can be 2th_deg or r_nm^-1 ...
170172
:param empty: value for bins where no pixels are contributing
171173
:param chiDiscAtPi: tell if azimuthal discontinuity is at 0 (0° when False) or π (180° when True)
172-
:param clip_pos1: clip the azimuthal range to [-π π] (or [0 2π] depending on chiDiscAtPi), set to False to deactivate behavior
174+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
175+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
173176
"""
174177
SplitBBoxIntegrator.__init__(self, pos0, delta_pos0, pos1, delta_pos1,
175178
bins, pos0_range, pos1_range, mask, mask_checksum, allow_pos0_neg, chiDiscAtPi,
176-
clip_pos1)
179+
pos1_period=pos1_period)
177180
self.unit = unit
178181
self.space = tuple(str(u).split("_")[0] for u in unit) if isinstance(unit, (tuple, list)) else str(unit).split("_")[0]
179182
self.bin_centers = None

src/pyFAI/ext/splitBBoxCSR.pyx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ reverse implementation based on a sparse matrix multiplication
3737

3838
__author__ = "Jérôme Kieffer"
3939
__contact__ = "Jerome.kieffer@esrf.fr"
40-
__date__ = "04/10/2023"
40+
__date__ = "18/11/2025"
4141
__status__ = "stable"
4242
__license__ = "MIT"
4343

@@ -79,7 +79,7 @@ class HistoBBox1d(CsrIntegrator, SplitBBoxIntegrator):
7979
unit="undefined",
8080
empty=None,
8181
bint chiDiscAtPi=True,
82-
bint clip_pos1=False):
82+
position_t pos1_period=twopi):
8383
"""
8484
:param pos0: 1D array with pos0: tth or q_vect or r ...
8585
:param delta_pos0: 1D array with delta pos0: max center-corner distance
@@ -93,14 +93,15 @@ class HistoBBox1d(CsrIntegrator, SplitBBoxIntegrator):
9393
:param unit: can be 2th_deg or r_nm^-1 ...
9494
:param empty: value for bins without contributing pixels
9595
:param chiDiscAtPi: tell if azimuthal discontinuity is at 0° or 180°
96-
:param clip_pos1: clip the azimuthal range to [-π π] (or [0 2π] depending on chiDiscAtPi), set to False to deactivate behavior
96+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
97+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
9798
"""
9899
self.unit = unit
99100
self.space = tuple(str(u).split("_")[0] for u in unit) if isinstance(unit, (list, tuple)) else str(unit).split("_")[0]
100101
SplitBBoxIntegrator.__init__(self, pos0, delta_pos0, pos1, delta_pos1,
101102
bins, pos0_range, pos1_range,
102103
mask, mask_checksum,
103-
allow_pos0_neg, chiDiscAtPi, clip_pos1=clip_pos1)
104+
allow_pos0_neg, chiDiscAtPi, pos1_period=pos1_period)
104105

105106

106107
self.delta = (self.pos0_max - self.pos0_min) / (<position_t> (self.bins))
@@ -153,7 +154,7 @@ class HistoBBox2d(CsrIntegrator, SplitBBoxIntegrator):
153154
unit="undefined",
154155
empty=None,
155156
bint chiDiscAtPi=True,
156-
bint clip_pos1=True):
157+
position_t pos1_period=twopi):
157158
"""
158159
:param pos0: 1D array with pos0: tth or q_vect
159160
:param delta_pos0: 1D array with delta pos0: max center-corner distance
@@ -167,11 +168,12 @@ class HistoBBox2d(CsrIntegrator, SplitBBoxIntegrator):
167168
:param unit: can be 2th_deg or r_nm^-1 ...
168169
:param empty: value for bins where no pixels are contributing
169170
:param chiDiscAtPi: tell if azimuthal discontinuity is at 0 (0° when False) or π (180° when True)
170-
:param clip_pos1: clip the azimuthal range to [-π π] (or [0 2π] depending on chiDiscAtPi), set to False to deactivate behavior
171+
:param pos1_period: periodicity of dim1, 2π, or 0 to non-periodic dimension
172+
If pos1_period: clip_pos1 is enforced, the azimuthal range is set to [-π π] (or [0 2π] depending on chiDiscAtPi)
171173
"""
172174
SplitBBoxIntegrator.__init__(self, pos0, delta_pos0, pos1, delta_pos1,
173175
bins, pos0_range, pos1_range, mask, mask_checksum, allow_pos0_neg, chiDiscAtPi,
174-
clip_pos1)
176+
pos1_period=pos1_period)
175177
self.unit = unit
176178
self.space = tuple(str(u).split("_")[0] for u in unit) if isinstance(unit, tuple) else str(unit).split("_")[0]
177179
self.bin_centers = None

0 commit comments

Comments
 (0)