Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2360_dataclass_for_worker_config…
Browse files Browse the repository at this point in the history
…' into 2360_dataclass_for_worker_config
  • Loading branch information
kif committed Jan 10, 2025
2 parents 35547aa + 6f72eed commit cba8e79
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 37 deletions.
1 change: 1 addition & 0 deletions ci/requirements_gh.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ scipy
matplotlib
mako
pybind11
siphash24
pyopencl
numexpr != 2.8.6
silx >= 2
Expand Down
3 changes: 2 additions & 1 deletion doc/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
:Author: Jérôme Kieffer
:Date: 12/09/2024
:Date: 07/01/2025
:Keywords: changelog

Change-log of versions
Expand All @@ -9,6 +9,7 @@ Next Version: 2025
------------------
- Refactoring of the integrator classes
- New dataclass `pyFAI.io.integration_config.WorkerConfig` which is in charge of the serialization of worker configuration.
- Tool to rebin 2d-result into 1d-result (`pyFAI.containers.rebin1d`)

2024.09 12/09/2024
------------------
Expand Down
46 changes: 43 additions & 3 deletions src/pyFAI/containers.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) 2013-2020 European Synchrotron Radiation Facility, Grenoble, France
# Copyright (C) 2013-2025 European Synchrotron Radiation Facility, Grenoble, France
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -30,12 +30,13 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "20/12/2024"
__date__ = "07/01/2025"
__status__ = "development"

from collections import namedtuple
from enum import IntEnum
from .utils.decorators import deprecated_warning
import numpy

PolarizationArray = namedtuple("PolarizationArray",
["array", "checksum"])
Expand Down Expand Up @@ -908,7 +909,7 @@ def __init__(self, index, intensity):
self._dtype = None
self._mask = None
self._dummy = None
self._radial = None
self._radius = None
self._background_avg = None
self._background_std = None
self._unit = None
Expand Down Expand Up @@ -1034,3 +1035,42 @@ def peak_connected(self):
@property
def unit(self):
return self._unit

def rebin1d(res2d):
"""Function that rebins an Integrate2dResult into a Integrate1dResult
:param res2d: Integrate2dResult instance obtained from ai.integrate2d
:return: Integrate1dResult
"""
bins_rad = res2d.radial
sum_signal = res2d.sum_signal.sum(axis=0)
sum_normalization = res2d.sum_normalization.sum(axis=0)
I = sum_signal / sum_normalization
if res2d.sum_variance is not None:
sum_variance = res2d.sum_variance.sum(axis=0)
sem = numpy.sqrt(sum_variance) / sum_normalization
result = Integrate1dResult(bins_rad, I, sem)
result._set_sum_normalization2(res2d.sum_normalization2.sum(axis=0))
result._set_sum_variance(sum_variance)
result._set_std(numpy.sqrt(sum_variance) / sum_normalization )
result._set_std(sem)
else:
result = Integrate1dResult(bins_rad, I)

result._set_sum_signal(sum_signal)
result._set_sum_normalization(sum_normalization)

result._set_method_called("integrate1d")
result._set_compute_engine(res2d.compute_engine)
result._set_method(res2d.method)
result._set_unit(res2d.radial_unit)
# result._set_azimuthal_unit(res2d.azimuth_unit)
result._set_count(res2d.count.sum(axis=0))
# result._set_sum(sum_)
result._set_has_dark_correction(res2d.has_dark_correction)
result._set_has_flat_correction(res2d.has_flat_correction)
result._set_has_mask_applied(res2d.has_mask_applied)
result._set_polarization_factor(res2d.polarization_factor)
result._set_normalization_factor(res2d.normalization_factor)
result._set_metadata(res2d.metadata)
return result
25 changes: 17 additions & 8 deletions src/pyFAI/diffmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "13/12/2024"
__date__ = "19/12/2024"
__status__ = "development"
__docformat__ = 'restructuredtext'

Expand Down Expand Up @@ -291,18 +291,27 @@ def parse(self, sysargv=None, with_config=False):
config["input_data"] = [(i, None) for i in self.inputfiles]

if options.mask:
mask = urlparse(options.mask).path
urlmask = urlparse(options.mask)
elif ai.get("do_mask", False) or ai.get("mask_file", None):
urlmask = urlparse(ai.get("mask_file", None))
elif config.get("do_mask", False) or config.get("mask_file", None):
mask = urlparse(config.get("mask_file", None)).path
# compatibility with elder config files...
deprecated_warning("Config of mask no more top-level, but in ai config group", "mask_file", deprecated_since="2024.12.0")
urlmask = urlparse(config.get("mask_file", None))
else:
mask = ""
if os.path.isfile(mask):
logger.info("Reading Mask file from: %s", mask)
self.mask = os.path.abspath(mask)
urlmask = urlparse("")
if "::" in urlmask.path:
mask_filename, idx = urlmask.path.split("::", 1)
mask_filename = os.path.abspath(mask_filename)
urlmask = urlparse(f"fabio://{mask_filename}?slice={idx}")

if os.path.isfile(urlmask.path):
logger.info("Reading Mask file from: %s", urlmask.path)
self.mask = urlmask.geturl()
ai["mask_file"] = self.mask
ai["do_mask"] = True
else:
logger.warning("No such mask file %s", mask)
logger.warning("No such mask file %s", urlmask.path)
if options.poni:
if os.path.isfile(options.poni):
logger.info("Reading PONI file from: %s", options.poni)
Expand Down
12 changes: 9 additions & 3 deletions src/pyFAI/geometry/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ class Geometry(object):
"chiDiscAtPi", "_wavelength", "_dssa_order",
'_oversampling', '_correct_solid_angle_for_spline',
'_transmission_normal')

PROMOTION = {"AzimuthalIntegrator": "pyFAI.integrator.azimuthal.AzimuthalIntegrator",
"FiberIntegrator": "pyFAI.integrator.fiber.FiberIntegrator",
"GeometryRefinement": "pyFAI.geometryRefinement.GeometryRefinement",
"Geometry": "pyFAI.geometry.core.Geometry"}

def __init__(self, dist=1, poni1=0, poni2=0, rot1=0, rot2=0, rot3=0,
pixel1=None, pixel2=None, splineFile=None, detector=None, wavelength=None,
Expand Down Expand Up @@ -2086,7 +2089,7 @@ def calcfrom2d(self, I, tth, chi, shape=None, mask=None,
calcimage[numpy.where(mask)] = dummy
return calcimage

def promote(self, type_="pyFAI.azimuthalIntegrator.AzimuthalIntegrator", kwargs=None):
def promote(self, type_="pyFAI.integrator.azimuthal.AzimuthalIntegrator", kwargs=None):
"""Promote this instance into one of its derived class (deep copy like)
:param type_: Fully qualified name of the class to promote to, or the class itself
Expand All @@ -2097,6 +2100,9 @@ def promote(self, type_="pyFAI.azimuthalIntegrator.AzimuthalIntegrator", kwargs=
"""
GeometryClass = self.__class__.__mro__[-2] # actually pyFAI.geometry.core.Geometry
if isinstance(type_, str):
if "." not in type_:
if type_ in self.PROMOTION:
type_ = self.PROMOTION[type_]
import importlib
modules = type_.split(".")
module_name = ".".join(modules[:-1])
Expand All @@ -2105,7 +2111,7 @@ def promote(self, type_="pyFAI.azimuthalIntegrator.AzimuthalIntegrator", kwargs=
elif isinstance(type_, type):
klass = type_
else:
raise ValueError("`type_` must be a class (or a fully qualified class name) of a Geometry derived class")
raise ValueError("`type_` must be a class (or class name) of a Geometry derived class")

if kwargs == None:
kwargs = {}
Expand Down
24 changes: 14 additions & 10 deletions src/pyFAI/integrator/azimuthal.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "05/12/2024"
__date__ = "07/01/2025"
__status__ = "stable"
__docformat__ = 'restructuredtext'

Expand Down Expand Up @@ -2047,17 +2047,19 @@ def sigma_clip(self, data,

sigma_clip_ng = sigma_clip

def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="splitpixel",
def separate(self, data, npt=1024,
unit="2th_deg", method=("full", "csr", "cython"),
polarization_factor=None,
percentile=50, mask=None, restore_mask=True):
"""
Separate bragg signal from powder/amorphous signal using azimuthal integration,
median filering and projected back before subtraction.
Separate bragg signal from powder/amorphous signal using azimuthal median filering and projected back before subtraction.
:param data: input image as numpy array
:param npt_rad: number of radial points
:param npt_azim: number of azimuthal points
:param npt: number of radial points
# :param npt_azim: number of azimuthal points
:param unit: unit to be used for integration
:param IntegrationMethod method: IntegrationMethod instance or 3-tuple with (splitting, algorithm, implementation)
:param polarization_factor: Value of the polarization factor (from -1 to +1), None to disable correction.
:param percentile: which percentile use for cutting out
:param mask: masked out pixels array
:param restore_mask: masked pixels have the same value as input data provided
Expand All @@ -2067,14 +2069,16 @@ def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="spl
SeparateResult.radial and SeparateResult.intensity
"""

filter_result = self.medfilt1d(data, npt_rad=npt_rad, npt_azim=npt_azim,
filter_result = self.medfilt1d_ng(data, npt=npt,
unit=unit, method=method,
percentile=percentile, mask=mask)
percentile=percentile,
polarization_factor=polarization_factor,
mask=mask)
# This takes 100ms and is the next to be optimized.
amorphous = self.calcfrom1d(filter_result.radial, filter_result.intensity,
data.shape, mask=None,
dim1_unit=unit,
correctSolidAngle=True)
correctSolidAngle=True, polarization_factor=polarization_factor)
bragg = data - amorphous
if restore_mask:
wmask = numpy.where(mask)
Expand All @@ -2095,7 +2099,7 @@ def separate(self, data, npt_rad=1024, npt_azim=512, unit="2th_deg", method="spl
result._set_method_called("medfilt1d")
result._set_compute_engine(str(method))
result._set_percentile(percentile)
result._set_npt_azim(npt_azim)
result._set_npt_azim(npt)
result._set_unit(unit)
result._set_has_mask_applied(filter_result.has_mask_applied)
result._set_metadata(filter_result.metadata)
Expand Down
2 changes: 1 addition & 1 deletion src/pyFAI/io/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "11/12/2024"
__date__ = "19/12/2024"
__status__ = "development"


Expand Down
1 change: 1 addition & 0 deletions src/pyFAI/test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ py.install_sources(
'test_watershed.py',
'test_worker.py',
'test_medfilt_engine.py',
'test_containers.py',
'utilstest.py'],
pure: false, # Will be installed next to binaries
subdir: 'pyFAI/test' # Folder relative to site-packages to install to
Expand Down
4 changes: 3 additions & 1 deletion src/pyFAI/test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "19/11/2024"
__date__ = "07/01/2025"

import sys
import unittest
Expand Down Expand Up @@ -97,6 +97,7 @@
from . import test_ring_extraction
from . import test_fiber_integrator
from . import test_medfilt_engine
from . import test_containers

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -160,6 +161,7 @@ def suite():
testsuite.addTest(test_ring_extraction.suite())
testsuite.addTest(test_fiber_integrator.suite())
testsuite.addTest(test_medfilt_engine.suite())
testsuite.addTest(test_containers.suite())
return testsuite


Expand Down
90 changes: 90 additions & 0 deletions src/pyFAI/test/test_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python
# coding: utf-8
#
# Project: Azimuthal integration
# https://github.com/silx-kit/pyFAI
#
# Copyright (C) 2025-2025 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer ([email protected])
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""Test suite for container module"""

__author__ = "Jérôme Kieffer"
__contact__ = "Jérô[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "07/01/2025"

import unittest
import numpy
import logging
logger = logging.getLogger(__name__)
import fabio
from .utilstest import UtilsTest
from .. import load as pyFAI_load
from .. import containers


class TestContainer(unittest.TestCase):

def test_rebin1d(self):
img = fabio.open(UtilsTest.getimage("moke.tif")).data
ai = pyFAI_load({
"poni_version": 2.1,
"detector": "Detector",
"detector_config": {
"pixel1": 1e-4,
"pixel2": 1e-4,
"max_shape": [
500,
600
],
"orientation": 3
},
"dist": 0.1,
"poni1": 0.03,
"poni2": 0.03,
"rot1": 0.0,
"rot2": 0.0,
"rot3": 0.0,
"wavelength": 1.0178021533473887e-10
})
method = ("no", "histogram", "cython")
res2d = ai.integrate2d(img, 500, 360, method=method, error_model="poisson")
ref1d = ai.integrate1d(img, 500, method=method, error_model="poisson")
res1d = containers.rebin1d(res2d)
self.assertTrue(numpy.allclose(res1d[0], ref1d[0]), "radial matches")
self.assertTrue(numpy.allclose(res1d[1], ref1d[1]), "intensity matches")
self.assertTrue(numpy.allclose(res1d[2], ref1d[2]), "sem matches")



def suite():
loader = unittest.defaultTestLoader.loadTestsFromTestCase
testsuite = unittest.TestSuite()
testsuite.addTest(loader(TestContainer))
return testsuite


if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
4 changes: 3 additions & 1 deletion src/pyFAI/test/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,9 @@ def test_promotion(self):
idmask = id(g.detector.mask)
ai = g.promote()
self.assertEqual(type(ai).__name__, "AzimuthalIntegrator", "Promote to AzimuthalIntegrator by default")
gr = g.promote("pyFAI.geometryRefinement.GeometryRefinement")
ai = g.promote("FiberIntegrator")
self.assertEqual(type(ai).__name__, "FiberIntegrator", "Promote to FiberIntegrator when requested")
gr = g.promote("GeometryRefinement")
self.assertEqual(type(gr).__name__, "GeometryRefinement", "Promote to GeometryRefinement when requested")
gr = ai.promote("pyFAI.geometryRefinement.GeometryRefinement")
self.assertEqual(type(gr).__name__, "GeometryRefinement", "Promote to GeometryRefinement when requested")
Expand Down
Loading

0 comments on commit cba8e79

Please sign in to comment.