Skip to content

Commit

Permalink
Merge pull request #2374 from kif/2372_rebin1d
Browse files Browse the repository at this point in the history
rebin in 1d from 2d data
  • Loading branch information
kif authored Jan 8, 2025
2 parents d079e15 + 50ace79 commit 5c9b4ec
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 15 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 @@ -8,6 +8,7 @@ Change-log of versions
Next Version: 2025
------------------
- Refactoring of the integrator classes
- 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__ = "19/11/2024"
__date__ = "07/01/2025"
__status__ = "development"

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

Integrate1dtpl = namedtuple("Integrate1dtpl", "position intensity sigma signal variance normalization count std sem norm_sq", defaults=(None,) * 3)
Integrate2dtpl = namedtuple("Integrate2dtpl", "radial azimuthal intensity sigma signal variance normalization count std sem norm_sq", defaults=(None,) * 3)
Expand Down Expand Up @@ -903,7 +904,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 @@ -1029,3 +1030,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
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
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())

0 comments on commit 5c9b4ec

Please sign in to comment.