Skip to content

Commit

Permalink
Merge branch 'main' into 2162_openmp
Browse files Browse the repository at this point in the history
  • Loading branch information
kif authored May 17, 2024
2 parents 5f5c149 + 9a941c3 commit 63c646c
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 61 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,7 @@ jobs:
env:
# Use silx wheelhouse: needed for ppc64le
CIBW_ENVIRONMENT_LINUX: "PIP_FIND_LINKS=https://www.silx.org/pub/wheelhouse/ PIP_TRUSTED_HOST=www.silx.org"
# diable OpenMP under windows
CIBW_ENVIRONMENT_WINDOWS: "PYFAI_WITH_OPENMP=False"

CIBW_BUILD: cp37-* cp38-* cp39-* cp310-* cp311-* cp312-*
# Do not build for pypy and muslinux
CIBW_SKIP: pp* *-musllinux_*
Expand Down
16 changes: 15 additions & 1 deletion doc/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
:Author: Jérôme Kieffer
:Date: 01/02/2024
:Date: 17/05/2024
:Keywords: changelog

Change-log of versions
======================

2024.5 XX/05/2024
-----------------
- Implemented unweighted average for 2D integration
+ Integration engines now handle the boolean 'weighted_average' to switch to unweighted mean, similar to legacy methods
- Implementation of pilx (diff-map-view): interactive viewer for pyFAI-diffmap files (thanks Loic Huder)
- Creation of a RingExtraction class based on multi-threading (thanks Emily Massahud)
- Flat-field and dark current corrections for pyFAI-calib2
- Tunable units and integration methods for fiber/grazing-incidence scattering
- Fix several bugs related to pyFAI-diffmap GUI/no-GUI options
- Compatibility with numpy2
- Fix numerical precision issue on MacOS/arm64
- Build system moved from bob to cibuildwheels
+ Windows wheels are build with openmp disabled

2024.2 01/02/2024
-----------------
- Include grazing-incidence capabilities + tutorial (thanks Edgar)
Expand Down
125 changes: 71 additions & 54 deletions src/pyFAI/test/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Project: Azimuthal integration
# https://github.com/silx-kit/pyFAI
#
# Copyright (C) 2015-2023 European Synchrotron Radiation Facility, Grenoble, France
# Copyright (C) 2015-2024 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
#
Expand Down Expand Up @@ -34,7 +34,7 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "07/05/2024"
__date__ = "16/05/2024"

import unittest
import random
Expand All @@ -43,7 +43,6 @@
import itertools
import logging
import os.path
import platform

from . import utilstest
logger = logging.getLogger(__name__)
Expand All @@ -54,6 +53,7 @@
from ..detectors import detector_factory
from ..third_party import transformations
from .utilstest import UtilsTest
from ..utils.mathutil import allclose_mod
import fabio


Expand Down Expand Up @@ -627,72 +627,89 @@ def test_array_from_unit_chi_center(self):
self.assertTrue(179 < r4.max() < 180, "Orientation 4 upperrange matches")

def test_array_from_unit_tth_corner(self):
r1 = self.ai1.array_from_unit(unit="2th_deg", typ="corner")
r2 = self.ai2.array_from_unit(unit="2th_deg", typ="corner")
r3 = self.ai3.array_from_unit(unit="2th_deg", typ="corner")
r4 = self.ai4.array_from_unit(unit="2th_deg", typ="corner")
r1 = self.ai1.array_from_unit(unit="2th_rad", typ="corner")
r2 = self.ai2.array_from_unit(unit="2th_rad", typ="corner")
r3 = self.ai3.array_from_unit(unit="2th_rad", typ="corner")
r4 = self.ai4.array_from_unit(unit="2th_rad", typ="corner")

tth1 = r1[..., 0]
tth2 = r2[..., 0]
tth3 = r3[..., 0]
tth4 = r4[..., 0]

chi1 = r1[..., 1]
chi2 = r2[..., 1]
chi3 = r3[..., 1]
chi4 = r4[..., 1]


sin_chi1 = numpy.sin(chi1)
sin_chi2 = numpy.sin(chi2)
sin_chi3 = numpy.sin(chi3)
sin_chi4 = numpy.sin(chi4)

cos_chi1 = numpy.cos(chi1)
cos_chi2 = numpy.cos(chi2)
cos_chi3 = numpy.cos(chi3)
cos_chi4 = numpy.cos(chi4)

# Here we use complex numbers
z1 = tth1*cos_chi1 + tth1*sin_chi1 *1j
z2 = tth2*cos_chi2 + tth2*sin_chi2 *1j
z3 = tth3*cos_chi3 + tth3*sin_chi3 *1j
z4 = tth4*cos_chi4 + tth4*sin_chi4 *1j

# the mean is not sensitive to 2pi discontinuity in azimuthal direction
z1 = z1.mean(axis=-1)
z2 = z2.mean(axis=-1)
z3 = z3.mean(axis=-1)
z4 = z4.mean(axis=-1)

self.assertFalse(numpy.allclose(z1, z2), "orientation 1,2 differ")
self.assertFalse(numpy.allclose(z1, z3), "orientation 1,3 differ")
self.assertFalse(numpy.allclose(z1, z3), "orientation 1,3 differ")
self.assertFalse(numpy.allclose(z1, z4), "orientation 1,4 differ")
self.assertFalse(numpy.allclose(z2, z3), "orientation 2,3 differ")
self.assertFalse(numpy.allclose(z2, z4), "orientation 2,4 differ")
self.assertFalse(numpy.allclose(z3, z4), "orientation 3,4 differ")

#Check that the tranformation is OK. This is with complex number thus dense & complicated !
self.assertTrue(numpy.allclose(z1, -numpy.fliplr(z2.conj())), "orientation 1,2 flipped")
self.assertTrue(numpy.allclose(z1, -z3[-1::-1, -1::-1]), "orientation 1,3 inversed")
self.assertTrue(numpy.allclose(z1, numpy.flipud(z4.conj())), "orientation 1,4 flipped")
self.assertTrue(numpy.allclose(z2, numpy.flipud(z3.conj())), "orientation 2,3 flipped")
self.assertTrue(numpy.allclose(z2, -z4[-1::-1, -1::-1]), "orientation 2,4 inversion")
self.assertTrue(numpy.allclose(z3, -numpy.fliplr(z4.conj())), "orientation 3,4 flipped")

tth1 = r1[..., 0].mean(axis=-1)
chi1 = r1[..., 1].mean(axis=-1) / numpy.pi
tth2 = r2[..., 0].mean(axis=-1)
chi2 = r2[..., 1].mean(axis=-1) / numpy.pi
tth3 = r3[..., 0].mean(axis=-1)
chi3 = r3[..., 1].mean(axis=-1) / numpy.pi
tth4 = r4[..., 0].mean(axis=-1)
chi4 = r4[..., 1].mean(axis=-1) / numpy.pi

self.assertFalse(numpy.allclose(tth1, tth2), "orientation 1,2 differ tth")
self.assertFalse(numpy.allclose(chi1, chi2), "orientation 1,2 differ chi")
self.assertFalse(numpy.allclose(tth1, tth3), "orientation 1,3 differ tth")
self.assertFalse(numpy.allclose(chi1, chi3), "orientation 1,3 differ chi")
self.assertFalse(numpy.allclose(tth1, tth4), "orientation 1,4 differ tth")
self.assertFalse(numpy.allclose(chi1, chi4), "orientation 1,4 differ chi")

self.assertTrue(numpy.allclose(tth1, numpy.fliplr(tth2)), "orientation 1,2 flipped match tth")
self.assertTrue(numpy.allclose(tth1, numpy.flipud(tth4)), "orientation 1,4 flipped match tth")
self.assertTrue(numpy.allclose(tth2, numpy.flipud(tth3)), "orientation 2,3 flipped match tth")
self.assertTrue(numpy.allclose(chi2, -numpy.flipud(chi3)), "orientation 2,3 flipped match chi")
self.assertTrue(numpy.allclose(tth1, tth3[-1::-1, -1::-1]), "orientation 1,3 inversion match tth")
self.assertTrue(numpy.allclose(tth2, tth4[-1::-1, -1::-1]), "orientation 2,4 inversion match tth")

# Something fishy on mac-arm64 where this test fails ... correct on all other platforms !
if platform.system() == "Darwin" and platform.machine()=="arm64":
def angular_distance(a, b, modulo):
return numpy.minimum((a-b)%modulo,(b-a)%modulo)
self.assertLess(angular_distance(chi1 + 1, -numpy.fliplr(chi2), 1).mean(), 0.0001, "orientation 1,2 flipped match chi")
self.assertLess(angular_distance(chi1, -numpy.flipud(chi4), 1).mean(), 0.0001, "orientation 1,4 flipped match chi")
self.assertLess(angular_distance(chi1+1, chi3[-1::-1, -1::-1], 1).mean(), 0.0001, "orientation 1,3 inversion match chi")
self.assertLess(angular_distance(chi2 + 1, chi4[-1::-1, -1::-1], 1).mean(), 0.0001, "orientation 2,4 inversion match chi")
else:
self.assertTrue(numpy.allclose(chi1 + 1, -numpy.fliplr(chi2), atol=0.0001), "orientation 1,2 flipped match chi")
self.assertTrue(numpy.allclose(chi1, -numpy.flipud(chi4)), "orientation 1,4 flipped match chi")
self.assertTrue(numpy.allclose(chi1 + 1, chi3[-1::-1, -1::-1], atol=0.0001), "orientation 1,3 inversion match chi")
self.assertTrue(numpy.allclose(chi2 + 1, chi4[-1::-1, -1::-1]), "orientation 2,4 inversion match chi")

def test_chi(self):
epsilon = 6e-3
orient = {}
for i in range(1, 5):
ai = geometry.Geometry.sload({"detector":"pilatus100k", "detector_config":{"orientation":i},
"poni1":0.01, "poni2":0.01,
"wavelength":1e-10})
ai = geometry.Geometry.sload({"detector":"Imxpad S10", "detector_config":{"orientation":i},
"poni1":0.005, "poni2":0.005, "wavelength":1e-10})
chi_c = ai.center_array(unit="chi_rad") / numpy.pi
chi_m = ai.corner_array(unit="chi_rad")[..., 0].mean(axis=-1) / numpy.pi
corners = ai.corner_array(unit="r_mm")
corners_rad = corners[..., 0]
corners_ang = corners[..., 1]
z = corners_rad*numpy.cos(corners_ang) + corners_rad*numpy.sin(corners_ang)*1j
chi_m = numpy.angle(z.mean(axis=-1)) / numpy.pi

orient[i] = {"ai": ai, "chi_c": chi_c, "chi_m": chi_m}

for o, orien in orient.items():
self.assertLess(numpy.median(abs(orien["chi_m"] - orien["chi_c"])), 1e-7, f"Orientation {o} matches")
self.assertTrue(allclose_mod(orien["chi_m"], orien["chi_c"], 2), f"Orientation {o} matches")
ai = orien["ai"]
self.assertLess(numpy.median(ai.delta_array(unit="chi_rad")) / numpy.pi, 1e-3, f"Orientation {o} delta chi small #0")
self.assertLess(numpy.median(ai.deltaChi()) / numpy.pi, 1e-3, f"Orientation {o} delta chi small #1")
self.assertLess(numpy.median(ai.delta_array(unit="chi_rad")) / numpy.pi, epsilon, f"Orientation {o} delta chi small #0")
self.assertLess(numpy.median(ai.deltaChi()) / numpy.pi, epsilon, f"Orientation {o} delta chi small #1")
ai.reset()
self.assertLess(numpy.median(ai.delta_array(unit="chi_rad")) / numpy.pi, 1e-3, f"Orientation {o} delta chi small #2")
self.assertLess(numpy.median(ai.delta_array(unit="chi_rad")) / numpy.pi, epsilon, f"Orientation {o} delta chi small #2")
ai.reset()
self.assertLess(numpy.median(ai.deltaChi()) / numpy.pi, 1e-3, f"Orientation {o} delta chi small #3")
self.assertLess(numpy.median(ai.deltaChi()) / numpy.pi, epsilon, f"Orientation {o} delta chi small #3")
ai.reset()
chiArray = ai.chiArray() / numpy.pi
chi_center = orien["chi_c"]
self.assertTrue(numpy.allclose(chiArray, chi_center, atol=1e-5), f"Orientation {o} chiArray == center_array(chi)")
self.assertTrue(allclose_mod(chiArray, chi_center), f"Orientation {o} chiArray == center_array(chi)")


class TestOrientation2(unittest.TestCase):
Expand All @@ -701,7 +718,7 @@ class TestOrientation2(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
super(TestOrientation2, cls).setUpClass()
p = detector_factory("Pilatus100k")
p = detector_factory("pilatus100k")
c = p.get_pixel_corners()
d1 = c[..., 1].max()
d2 = c[..., 2].max()
Expand Down
9 changes: 7 additions & 2 deletions src/pyFAI/test/test_utils_mathutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Project: Azimuthal integration
# https://github.com/silx-kit/pyFAI
#
# Copyright (C) 2015-2021 European Synchrotron Radiation Facility, Grenoble, France
# Copyright (C) 2015-2024 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
#
Expand Down Expand Up @@ -32,7 +32,7 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "20/02/2024"
__date__ = "16/05/2024"

import unittest
import numpy
Expand Down Expand Up @@ -157,6 +157,11 @@ def test_is_far_from_group_cython(self):
res = is_far_from_group_cython(pt, pts, dst2)
self.assertEqual(ref, res, "cython implementation matches *is_far_from_group*")

def test_allclose_mod(self):
from ..utils.mathutil import allclose_mod
self.assertTrue(allclose_mod(numpy.arctan2(+1e-10, -1), numpy.arctan2(-1e-10, -1)),"angles matches modulo 2pi")


def suite():
loader = unittest.defaultTestLoader.loadTestsFromTestCase
testsuite = unittest.TestSuite()
Expand Down
15 changes: 13 additions & 2 deletions src/pyFAI/utils/mathutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Project: Fast Azimuthal integration
# https://github.com/silx-kit/pyFAI
#
# Copyright (C) 2017-2018 European Synchrotron Radiation Facility, Grenoble, France
# Copyright (C) 2017-2024 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
#
Expand Down Expand Up @@ -34,7 +34,7 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "20/02/2024"
__date__ = "16/05/2024"
__status__ = "production"

import logging
Expand Down Expand Up @@ -394,6 +394,7 @@ def unBinning(*args, **kwargs):
return unbinning(*args, **kwargs)



def shift_fft(input_img, shift_val, method="fft"):
"""Do shift using FFTs
Expand Down Expand Up @@ -928,3 +929,13 @@ def interp_filter(ary, out=None):
out[mask_invalid] = numpy.interp(x[mask_invalid], x[mask_valid], ary[mask_valid],
left=first, right=last)
return out


def allclose_mod(a, b, modulo=2*numpy.pi, **kwargs):
"""Returns True if the two arrays a & b are equal within the given
tolerance modulo `modulo`; False otherwise.
Thanks to "Serguei Sokol" <sokol@insa-toulouse.fr>
"""
di = numpy.minimum((a-b)%modulo, (b-a)%modulo)
return numpy.allclose(modulo*0.5, (di+modulo*0.5), **kwargs)

0 comments on commit 63c646c

Please sign in to comment.