Skip to content

Commit 241793f

Browse files
authored
Merge pull request #2467 from kif/2644_automatic_dynamic_mask
automatic dynamic mask for Dectris detetors
2 parents 68f23a4 + 7d7b28a commit 241793f

9 files changed

+104
-41
lines changed

src/pyFAI/detectors/_common.py

+19
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,25 @@ def calc_mask(self):
949949
# logger.debug("Detector.calc_mask is not implemented for generic detectors")
950950
return None
951951

952+
def get_dummies(self, img):
953+
"""Calculate the actual dummy value from dtype of the img
954+
955+
:param img: numpy array (or actually its dtype)
956+
:return: actual (dummy, delta_dummy) values as data_d (i.e. float32)
957+
"""
958+
if self.dummy is None:
959+
return None, None
960+
if isinstance(img, numpy.ndarray):
961+
dtype = numpy.dtype(img.dtype)
962+
else:
963+
dtype = numpy.dtype(img)
964+
actual_dummy = numpy.float32(numpy.dtype(img.dtype).type(numpy.int64(self.dummy)))
965+
if self.delta_dummy is None:
966+
actual_delta_dummy = numpy.finfo("float32").eps
967+
else:
968+
actual_delta_dummy = numpy.float32(self.delta_dummy)
969+
return actual_dummy, actual_delta_dummy
970+
952971
def dynamic_mask(self, img):
953972
"""Calculate the dynamic mask for the given image.
954973

src/pyFAI/ext/CSR_common.pxi

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Project: Azimuthal integration
44
# https://github.com/silx-kit/pyFAI
55
#
6-
# Copyright (C) 2015-2024 European Synchrotron Radiation Facility, Grenoble, France
6+
# Copyright (C) 2015-2025 European Synchrotron Radiation Facility, Grenoble, France
77
#
88
# Principal author: Jérôme Kieffer ([email protected])
99
#
@@ -29,7 +29,7 @@
2929

3030
__author__ = "Jérôme Kieffer"
3131
__contact__ = "[email protected]"
32-
__date__ = "05/12/2024"
32+
__date__ = "12/03/2025"
3333
__status__ = "stable"
3434
__license__ = "MIT"
3535

@@ -347,7 +347,7 @@ cdef class CsrIntegrator(object):
347347
bint do_azimuthal_variance = error_model is ErrorModel.AZIMUTHAL
348348
bint do_variance = error_model is not ErrorModel.NO
349349
assert weights.size == self.input_size, "weights size"
350-
empty = dummy if dummy is not None else self.empty
350+
empty = self.empty if dummy is None else dummy
351351
#Call the preprocessor ...
352352
preproc4 = preproc(weights.ravel(),
353353
dark=dark,

src/pyFAI/integrator/azimuthal.py

+21-5
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
__contact__ = "[email protected]"
3131
__license__ = "MIT"
3232
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
33-
__date__ = "10/03/2025"
33+
__date__ = "12/03/2025"
3434
__status__ = "stable"
3535
__docformat__ = 'restructuredtext'
3636

@@ -128,7 +128,12 @@ def integrate1d(self, data, npt, filename=None,
128128
method = self._normalize_method(method, dim=1, default=self.DEFAULT_METHOD_1D)
129129
assert method.dimension == 1
130130
unit = units.to_unit(unit)
131-
empty = numpy.float32(dummy) if dummy is not None else self._empty
131+
if dummy is None:
132+
dummy, delta_dummy = self.detector.get_dummies(data)
133+
empty = self._empty if dummy is None else dummy
134+
else:
135+
dummy = empty = numpy.float32(dummy)
136+
delta_dummy = None if delta_dummy is None else numpy.float32(delta_dummy)
132137
shape = data.shape
133138
pos0_scale = unit.scale
134139

@@ -605,7 +610,12 @@ def integrate_radial(self, data, npt, npt_rad=100,
605610
sum_normalization = res._sum_normalization.sum(axis=-1)
606611

607612
mask = numpy.where(count == 0)
608-
empty = numpy.float32(dummy) if dummy is not None else self._empty
613+
if dummy is None:
614+
dummy, delta_dummy = self.detector.get_dummies(data)
615+
empty = self._empty if dummy is None else dummy
616+
else:
617+
dummy = empty = numpy.float32(dummy)
618+
delta_dummy = None if delta_dummy is None else numpy.float32(delta_dummy)
609619
intensity = sum_signal / sum_normalization
610620
intensity[mask] = empty
611621

@@ -703,7 +713,13 @@ def integrate2d_ng(self, data, npt_rad, npt_azim=360,
703713
space = (radial_unit.space, azimuth_unit.space)
704714
pos0_scale = radial_unit.scale
705715
pos1_scale = azimuth_unit.scale
706-
empty = numpy.float32(dummy) if dummy is not None else self._empty
716+
if dummy is None:
717+
dummy, delta_dummy = self.detector.get_dummies(data)
718+
empty = self._empty if dummy is None else dummy
719+
else:
720+
dummy = empty = numpy.float32(dummy)
721+
delta_dummy = None if delta_dummy is None else numpy.float32(delta_dummy)
722+
707723
if mask is None:
708724
has_mask = "from detector"
709725
mask = self.mask
@@ -1103,7 +1119,7 @@ def integrate2d_ng(self, data, npt_rad, npt_azim=360,
11031119
dummy=dummy,
11041120
delta_dummy=delta_dummy,
11051121
normalization_factor=normalization_factor,
1106-
empty=self._empty,
1122+
empty=empty,
11071123
variance=variance,
11081124
dark_variance=None,
11091125
error_model=error_model,

src/pyFAI/test/test_azimuthal_integrator.py

+16
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,22 @@ def test_guess_polarization(self):
390390
ai = AzimuthalIntegrator.sload(UtilsTest.getimage("Eiger4M.poni"))
391391
self.assertLess(abs(ai.guess_polarization(img) - 0.5), 0.1)
392392

393+
def test_detector_dynamic_mask(self):
394+
"""Check if the dummy value from detector is actually used #2466"""
395+
det = detector_factory("Pilatus CdTe 300k")
396+
corners = det.get_pixel_corners()
397+
config = {"detector": det.name,
398+
"dist": 1,
399+
"poni1": corners[...,1].max()/4,
400+
"poni2": corners[...,2].max()/4}
401+
ai1 = AzimuthalIntegrator.sload(config)
402+
img1 = det.mask.astype("uint16")*(65535-1)+1
403+
img1[300:400, 300:400] = 65535
404+
result = ai1.integrate1d(img1, 100, unit="r_mm",
405+
method=("no", "histogram", "cython"),
406+
correctSolidAngle=False)
407+
self.assertTrue(numpy.allclose(result.intensity, numpy.ones_like(result.radial)))
408+
393409

394410
class TestSaxs(unittest.TestCase):
395411
saxsPilatus = "bsa_013_01.edf"

src/pyFAI/test/test_csr.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,16 @@ def test_2d_nosplit(self):
195195
result_nosplit = self.ai.integrate2d(self.data, self.N, unit="2th_deg", method=("no", "csr", "cython"))
196196
self.assertTrue(numpy.allclose(result_histo.radial, result_nosplit.radial), " 2Th are the same")
197197
self.assertTrue(numpy.allclose(result_histo.azimuthal, result_nosplit.azimuthal, atol=1e-5), " Chi are the same")
198-
if False:
199-
print(result_histo.method, result_histo.method_called)
200-
print(result_histo.sum_signal.min(), result_histo.sum_signal.max(), result_histo.sum_signal.mean(), result_histo.sum_signal.std())
201-
print(result_histo.sum_normalization.min(), result_histo.sum_normalization.max(), result_histo.sum_normalization.mean(), result_histo.sum_normalization.std())
202-
print(result_histo.intensity.min(), result_histo.intensity.max(), result_histo.intensity.mean(), result_histo.intensity.std())
203-
print(result_nosplit.intensity.min(), result_nosplit.intensity.max(), result_nosplit.intensity.mean(), result_nosplit.intensity.std())
204-
print(result_nosplit.intensity)
198+
# print("result_histo", result_histo.method, result_histo.method_called)
199+
# print("result_histo", result_histo.sum_signal.min(), result_histo.sum_signal.max(), result_histo.sum_signal.mean(), result_histo.sum_signal.std())
200+
# print("result_histo",result_histo.sum_normalization.min(), result_histo.sum_normalization.max(), result_histo.sum_normalization.mean(), result_histo.sum_normalization.std())
201+
# print("result_histo",result_histo.intensity.min(), result_histo.intensity.max(), result_histo.intensity.mean(), result_histo.intensity.std())
202+
# print("result_nosplit",result_nosplit.intensity.min(), result_nosplit.intensity.max(), result_nosplit.intensity.mean(), result_nosplit.intensity.std())
203+
# print("result_nosplit",result_nosplit.method)
205204
error = (result_histo.intensity - result_nosplit.intensity)
206205
logger.debug("ref: %s; obt: %s", result_histo.intensity.shape, result_nosplit.intensity.shape)
207206
logger.debug("error mean: %s, std: %s", error.mean(), error.std())
207+
208208
self.assertLess(error.mean(), 1e-3, "img are almost the same")
209209
self.assertLess(error.std(), 3, "img are almost the same")
210210

src/pyFAI/test/test_geometry.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
__contact__ = "[email protected]"
3535
__license__ = "MIT"
3636
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
37-
__date__ = "20/02/2025"
37+
__date__ = "12/03/2025"
3838

3939
import unittest
4040
import random
@@ -559,18 +559,21 @@ class TestCalcFrom(unittest.TestCase):
559559
def test_calcfrom12d(self):
560560
det = detector_factory("pilatus300k")
561561
ai = AzimuthalIntegrator(0.1, 0.05, 0.04, detector=det)
562-
prof_1d = ai.integrate1d_ng(UtilsTest.get_rng().random(det.shape), 200, unit="2th_deg")
563-
sig = numpy.sinc(prof_1d.radial * 10) ** 2
564-
img1 = ai.calcfrom1d(prof_1d.radial, sig, dim1_unit="2th_deg", mask=det.mask, dummy=-1)
562+
img0 = UtilsTest.get_rng().random(det.shape)
563+
prof_1d = ai.integrate1d_ng(img0, 200, unit="2th_deg")
564+
sig = 1e6*numpy.sinc(prof_1d.radial / 10) ** 2
565+
img1 = ai.calcfrom1d(prof_1d.radial, sig, dim1_unit="2th_deg",
566+
mask=det.mask, dummy=-1)
565567
new_prof_1d = ai.integrate1d_ng(img1, 200, unit="2th_deg")
566568
delta = abs((new_prof_1d.intensity - sig)).max()
567-
self.assertLess(delta, 2e-3, "calcfrom1d works delta=%s" % delta)
569+
self.assertLess(delta, 600, "calcfrom1d works delta=%s" % delta)
568570
prof_2d = ai.integrate2d(img1, 400, 360, unit="2th_deg")
569571
img2 = ai.calcfrom2d(prof_2d.intensity, prof_2d.radial, prof_2d.azimuthal,
570572
mask=det.mask,
571573
dim1_unit="2th_deg", correctSolidAngle=True, dummy=-1)
572-
delta2 = abs(img2 - img1).max()
573-
self.assertLess(delta2, 1e-3, "calcfrom2d works delta=%s" % delta2)
574+
delta2 = img2 - img1
575+
self.assertLess(abs(delta2.mean()), 2, "calcfrom2d works delta.mean=%s" % abs(delta2.mean()))
576+
self.assertLess(delta2.std(), 100, "calcfrom2d works delta.std=%s" % delta2.std())
574577

575578

576579
class TestBugRegression(unittest.TestCase):

src/pyFAI/test/test_integrate.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Project: Azimuthal integration
55
# https://github.com/silx-kit/pyFAI
66
#
7-
# Copyright (C) 2015-2022 European Synchrotron Radiation Facility, Grenoble, France
7+
# Copyright (C) 2015-2025 European Synchrotron Radiation Facility, Grenoble, France
88
#
99
# Principal author: Jérôme Kieffer ([email protected])
1010
#
@@ -31,7 +31,7 @@
3131
__contact__ = "[email protected]"
3232
__license__ = "MIT"
3333
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
34-
__date__ = "21/05/2024"
34+
__date__ = "12/03/2025"
3535

3636
import contextlib
3737
import os
@@ -190,7 +190,12 @@ def setUpClass(cls):
190190
cls.img = UtilsTest.getimage("Pilatus1M.edf")
191191
with fabio.open(cls.img) as fimg:
192192
cls.data = fimg.data
193-
cls.ai = AzimuthalIntegrator(1.58323111834, 0.0334170169115, 0.0412277798782, 0.00648735642526, 0.00755810191106, 0.0, detector=Pilatus1M())
193+
class DummyLessPilatus(Pilatus1M):
194+
DUMMY = None
195+
DELTA_DUMMY = None
196+
197+
cls.ai = AzimuthalIntegrator(1.58323111834, 0.0334170169115, 0.0412277798782, 0.00648735642526, 0.00755810191106, 0.0,
198+
detector=DummyLessPilatus())
194199
cls.ai.wavelength = 1e-10
195200
cls.Rmax = 30
196201
cls.delta_pos_azim_max = 0.28

src/pyFAI/test/test_mask.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Project: Azimuthal integration
55
# https://github.com/silx-kit/pyFAI
66
#
7-
# Copyright (C) 2015-2018 European Synchrotron Radiation Facility, Grenoble, France
7+
# Copyright (C) 2015-2025 European Synchrotron Radiation Facility, Grenoble, France
88
#
99
# Principal author: Jérôme Kieffer ([email protected])
1010
#
@@ -32,7 +32,7 @@
3232
__contact__ = "[email protected]"
3333
__license__ = "MIT"
3434
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
35-
__date__ = "21/05/2024"
35+
__date__ = "12/03/2024"
3636

3737
import unittest
3838
import numpy
@@ -48,21 +48,25 @@
4848

4949

5050
class TestMask(unittest.TestCase):
51-
dataFile = "testMask.edf"
52-
poniFile = "Pilatus1M.poni"
5351

54-
def setUp(self):
52+
@classmethod
53+
def setUpClass(cls):
5554
"""Download files"""
56-
self.dataFile = UtilsTest.getimage(self.__class__.dataFile)
57-
self.poniFile = UtilsTest.getimage(self.__class__.poniFile)
58-
self.ai = load(self.poniFile)
59-
with fabio.open(self.dataFile) as fimg:
60-
self.data = fimg.data
61-
self.mask = self.data < 0
62-
63-
def tearDown(self):
64-
unittest.TestCase.tearDown(self)
65-
self.dataFile = self.data = self.ai = self.mask = self.poniFile = None
55+
cls.dataFile = UtilsTest.getimage("testMask.edf")
56+
cls.poniFile = UtilsTest.getimage("Pilatus1M.poni")
57+
cls.ai = load(cls.poniFile)
58+
# hack to prevent dynamic masking: this is a Pilatus without dummy values
59+
class MyPilatus(cls.ai.detector.__class__):
60+
DUMMY=None
61+
DELTA_DUMMY=None
62+
cls.ai.detector = MyPilatus()
63+
with fabio.open(cls.dataFile) as fimg:
64+
cls.data = fimg.data
65+
cls.mask = cls.data < 0
66+
67+
@classmethod
68+
def tearDownClass(cls):
69+
cls.dataFile = cls.data = cls.ai = cls.mask = cls.poniFile = None
6670

6771
def test_mask_hist(self):
6872
"""

src/pyFAI/test/test_utils_mathutil.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_quality_of_fit(self):
170170
cal = calibrant.get_calibrant("AgBh")
171171
cal.wavelength = ai.wavelength
172172
res = mathutil.quality_of_fit(img, ai, cal, rings=[0,1], npt_azim=36, npt_rad=100)
173-
self.assertLess(res, 0.3, "Fit of good quality")
173+
self.assertLess(res, 0.31, "Fit of good quality")
174174

175175
def test_nan_equal(self):
176176
nan_equal = mathutil.nan_equal

0 commit comments

Comments
 (0)