Skip to content

azint

AzimuthalIntegrator

This class is an azimuthal integrator

Source code in azint/azint.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
class AzimuthalIntegrator():
    """
    This class is an azimuthal integrator 
    """
    def __init__(self,
                 poni: Union[str, Poni],
                 n_splitting: int, 
                 radial_bins: Union[int, Sequence],
                 azimuth_bins: Optional[Union[int, Sequence]] = None,
                 unit: str = 'q',
                 mask: Optional[Union[np.ndarray, str]] = None,
                 solid_angle: bool = True,
                 polarization_factor: Optional[float] = None,
                 normalized: bool = True,
                 error_model: Optional[str] = None):
        """
        Initializes the integration settings for a 1D or 2D azimuthal integration.

        Attributes:
            poni (str or dict or Poni): Path to a PONI file or a Poni instance or a dictionary that defines the detector geometry.
            n_splitting (int): Number of subpixels per dimension for pixel splitting. Each image pixel is split into (n_splitting x n_splitting) subpixels.
            radial_bins (int or Sequence): Number of radial bins or an explicit sequence of radial bin edges (e.g., in Å⁻¹ or degrees).
            azimuth_bins (int or Sequence, optional): Number of azimuthal bins or a sequence of azimuthal bin edges (in degrees from 0 to 360). Required for 2D integration.
            unit (str): Unit for the radial axis. Typically 'q' (Å⁻¹) or '2theta' (degrees).
            mask (ndarray or str, optional): Mask array or path to mask file. Pixels marked with 1 will be excluded from the integration.
            solid_angle (bool): If True, applies solid angle correction.
            polarization_factor (float, optional): Polarization correction factor.
                Use 1 for horizontal polarization, -1 for vertical polarization.
            normalized (bool, optional): If True, the output will be normalized by the number of contributing pixels.
            error_model (str, optional): Error model used to propagate uncertainties. Currently, only 'poisson' is supported.

            radial_axis (ndarray): Array of radial coordinates in the specified unit ('q' or '2theta').
            azimuth_axis (ndarray, optional): Array of azimuthal coordinates in degrees, present if performing 2D integration.
        """
        self.poni = poni
        self.n_splitting = n_splitting
        self.radial_bins = radial_bins
        self.azimuth_bins = azimuth_bins
        self.solid_angle = solid_angle
        self.polarization_factor = polarization_factor
        self.normalized = normalized
        self.mask_path = ''
        if not isinstance(mask, np.ndarray):
            if mask is not None:
                if mask == '':
                    mask = None
                else:
                    fname = mask
                    self.mask_path = fname
                    ending = os.path.splitext(fname)[1]
                    if ending == '.npy':
                        mask = np.load(fname)
                    else:
                        mask = fabio.open(fname).data
        self.mask = mask
        if error_model and error_model != 'poisson':
            raise RuntimeError('Only poisson error model is supported')

        if unit not in ('q', '2th'):
            raise RuntimeError('Wrong radial unit. Allowed units: q, 2th')

        if isinstance(poni, str):
            poni = Poni.from_file(poni)

        if isinstance(poni, dict):
            poni = Poni.from_dict(poni)

        self.unit = unit
        self.error_model = error_model

        pixel_centers = np.mean(poni.det.pixel_corners, axis=2)
        p1 = pixel_centers[..., 1] - poni.poni1
        p2 = pixel_centers[..., 2] - poni.poni2
        p3 = pixel_centers[..., 0] + poni.dist
        tth, phi = transform(poni, p1, p2, p3)

        radial_bins = setup_radial_bins(poni, radial_bins, unit, tth)
        self.radial_axis = 0.5*(radial_bins[1:] + radial_bins[:-1])
        azimuth_bins = setup_azimuth_bins(azimuth_bins)
        self.azimuth_axis = 0.5*(azimuth_bins[1:] + azimuth_bins[:-1]) if azimuth_bins is not None else None

        shape = pixel_centers.shape[:2]
        self.input_size = np.prod(shape)
        if mask is None:
            mask = np.zeros(shape, dtype=np.int8)

        if azimuth_bins is None:
            self.output_shape = [len(radial_bins) - 1]
        else:
            self.output_shape = [len(azimuth_bins) - 1, len(radial_bins) - 1]

        self.sparse_matrix = Sparse(poni, poni.det.pixel_corners, n_splitting, 
                                    mask, unit, radial_bins, azimuth_bins)
        self.norm = self.sparse_matrix.spmv(np.ones(shape[0]*shape[1], dtype=np.float32))
        corrections = setup_corrections(poni, solid_angle, polarization_factor, p1, p2, tth, phi)
        self.sparse_matrix.set_correction(corrections)


    def integrate(self, 
                  img: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
        """
        Performs azimuthal integration on the input image.

        This function computes the azimuthally averaged intensity from the input image
        using a sparse matrix-based integration approach. Optionally applies a mask
        and computes error estimates if an error model is defined.

        Args:
            img (ndarray): Input image to be integrated. Must match expected input size.

        Returns:
            tuple:
                - I (ndarray): 1D azimuthally integrated intensity.
                - errors_1d (ndarray or None): Standard error of the mean (SEM) for 1D integration if error model is specified, else None.
                - I_2d (None): 2D "cake" integration result.
                - errors_2d (None): 2D error result.

        Raises:
            RuntimeError: If the input image size does not match the expected size.
        """
        img = np.ascontiguousarray(img)

        if img.size != self.input_size:
            raise RuntimeError('Size of image is wrong!\nExpected %d\nActual size %d' %(self.input_size, img.size))
        if self.mask is None:
            norm = self.norm
        else:
            inverted_mask = 1 - self.mask
            img = img*inverted_mask
            norm = self.sparse_matrix.spmv(inverted_mask.reshape(-1))

        signal = self.sparse_matrix.spmv_corrected(img).reshape(self.output_shape)
        norm = norm.reshape(self.output_shape)

        errors = None
        errors_1d = None
        errors_2d = None
        if self.error_model:
            # poisson error model
            errors = np.sqrt(self.sparse_matrix.spmv_corrected2(img)).reshape(self.output_shape)
            if self.normalized:
                errors = np.divide(errors, norm, out=np.zeros_like(errors), where=norm!=0.0)

        if signal.ndim == 1: # must be radial bins only, no eta, ie 1d.
            if self.normalized:
                signal = np.divide(signal, norm, out=np.zeros_like(signal), where=norm!=0.0)
            self.norm_1d = norm
            self.norm_2d = None
            I = signal
            if errors is not None:
                errors_1d = errors
            return I, errors_1d, None, None
        else:  # will have eta bins
            signal_1d =  np.sum(signal, axis=0)
            self.norm_1d = np.sum(norm, axis=0)
            if self.normalized:
                signal_1d = np.divide(signal_1d, self.norm_1d, out=np.zeros_like(signal_1d), where=self.norm_1d!=0.0)
                signal = np.divide(signal, norm, out=np.zeros_like(signal), where=norm!=0.0)
            I = signal_1d
            self.norm_2d = norm
            I_2d = signal
            if errors is not None:
                errors_1d = np.sum(errors, axis=0)
                errors_2d = errors
                if self.normalized:
                    errors_1d = np.divide(errors_1d, self.norm_1d, out=np.zeros_like(errors_1d), where=self.norm_1d!=0.0)
                    errors_2d = np.divide(errors_2d, norm, out=np.zeros_like(errors_2d), where=norm!=0.0)
            return I, errors_1d, I_2d, errors_2d

__init__(poni, n_splitting, radial_bins, azimuth_bins=None, unit='q', mask=None, solid_angle=True, polarization_factor=None, normalized=True, error_model=None)

Initializes the integration settings for a 1D or 2D azimuthal integration.

Attributes:

Name Type Description
poni str or dict or Poni

Path to a PONI file or a Poni instance or a dictionary that defines the detector geometry.

n_splitting int

Number of subpixels per dimension for pixel splitting. Each image pixel is split into (n_splitting x n_splitting) subpixels.

radial_bins int or Sequence

Number of radial bins or an explicit sequence of radial bin edges (e.g., in Å⁻¹ or degrees).

azimuth_bins int or Sequence

Number of azimuthal bins or a sequence of azimuthal bin edges (in degrees from 0 to 360). Required for 2D integration.

unit str

Unit for the radial axis. Typically 'q' (Å⁻¹) or '2theta' (degrees).

mask ndarray or str

Mask array or path to mask file. Pixels marked with 1 will be excluded from the integration.

solid_angle bool

If True, applies solid angle correction.

polarization_factor float

Polarization correction factor. Use 1 for horizontal polarization, -1 for vertical polarization.

normalized bool

If True, the output will be normalized by the number of contributing pixels.

error_model str

Error model used to propagate uncertainties. Currently, only 'poisson' is supported.

radial_axis ndarray

Array of radial coordinates in the specified unit ('q' or '2theta').

azimuth_axis ndarray

Array of azimuthal coordinates in degrees, present if performing 2D integration.

Source code in azint/azint.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def __init__(self,
             poni: Union[str, Poni],
             n_splitting: int, 
             radial_bins: Union[int, Sequence],
             azimuth_bins: Optional[Union[int, Sequence]] = None,
             unit: str = 'q',
             mask: Optional[Union[np.ndarray, str]] = None,
             solid_angle: bool = True,
             polarization_factor: Optional[float] = None,
             normalized: bool = True,
             error_model: Optional[str] = None):
    """
    Initializes the integration settings for a 1D or 2D azimuthal integration.

    Attributes:
        poni (str or dict or Poni): Path to a PONI file or a Poni instance or a dictionary that defines the detector geometry.
        n_splitting (int): Number of subpixels per dimension for pixel splitting. Each image pixel is split into (n_splitting x n_splitting) subpixels.
        radial_bins (int or Sequence): Number of radial bins or an explicit sequence of radial bin edges (e.g., in Å⁻¹ or degrees).
        azimuth_bins (int or Sequence, optional): Number of azimuthal bins or a sequence of azimuthal bin edges (in degrees from 0 to 360). Required for 2D integration.
        unit (str): Unit for the radial axis. Typically 'q' (Å⁻¹) or '2theta' (degrees).
        mask (ndarray or str, optional): Mask array or path to mask file. Pixels marked with 1 will be excluded from the integration.
        solid_angle (bool): If True, applies solid angle correction.
        polarization_factor (float, optional): Polarization correction factor.
            Use 1 for horizontal polarization, -1 for vertical polarization.
        normalized (bool, optional): If True, the output will be normalized by the number of contributing pixels.
        error_model (str, optional): Error model used to propagate uncertainties. Currently, only 'poisson' is supported.

        radial_axis (ndarray): Array of radial coordinates in the specified unit ('q' or '2theta').
        azimuth_axis (ndarray, optional): Array of azimuthal coordinates in degrees, present if performing 2D integration.
    """
    self.poni = poni
    self.n_splitting = n_splitting
    self.radial_bins = radial_bins
    self.azimuth_bins = azimuth_bins
    self.solid_angle = solid_angle
    self.polarization_factor = polarization_factor
    self.normalized = normalized
    self.mask_path = ''
    if not isinstance(mask, np.ndarray):
        if mask is not None:
            if mask == '':
                mask = None
            else:
                fname = mask
                self.mask_path = fname
                ending = os.path.splitext(fname)[1]
                if ending == '.npy':
                    mask = np.load(fname)
                else:
                    mask = fabio.open(fname).data
    self.mask = mask
    if error_model and error_model != 'poisson':
        raise RuntimeError('Only poisson error model is supported')

    if unit not in ('q', '2th'):
        raise RuntimeError('Wrong radial unit. Allowed units: q, 2th')

    if isinstance(poni, str):
        poni = Poni.from_file(poni)

    if isinstance(poni, dict):
        poni = Poni.from_dict(poni)

    self.unit = unit
    self.error_model = error_model

    pixel_centers = np.mean(poni.det.pixel_corners, axis=2)
    p1 = pixel_centers[..., 1] - poni.poni1
    p2 = pixel_centers[..., 2] - poni.poni2
    p3 = pixel_centers[..., 0] + poni.dist
    tth, phi = transform(poni, p1, p2, p3)

    radial_bins = setup_radial_bins(poni, radial_bins, unit, tth)
    self.radial_axis = 0.5*(radial_bins[1:] + radial_bins[:-1])
    azimuth_bins = setup_azimuth_bins(azimuth_bins)
    self.azimuth_axis = 0.5*(azimuth_bins[1:] + azimuth_bins[:-1]) if azimuth_bins is not None else None

    shape = pixel_centers.shape[:2]
    self.input_size = np.prod(shape)
    if mask is None:
        mask = np.zeros(shape, dtype=np.int8)

    if azimuth_bins is None:
        self.output_shape = [len(radial_bins) - 1]
    else:
        self.output_shape = [len(azimuth_bins) - 1, len(radial_bins) - 1]

    self.sparse_matrix = Sparse(poni, poni.det.pixel_corners, n_splitting, 
                                mask, unit, radial_bins, azimuth_bins)
    self.norm = self.sparse_matrix.spmv(np.ones(shape[0]*shape[1], dtype=np.float32))
    corrections = setup_corrections(poni, solid_angle, polarization_factor, p1, p2, tth, phi)
    self.sparse_matrix.set_correction(corrections)

integrate(img)

Performs azimuthal integration on the input image.

This function computes the azimuthally averaged intensity from the input image using a sparse matrix-based integration approach. Optionally applies a mask and computes error estimates if an error model is defined.

Parameters:

Name Type Description Default
img ndarray

Input image to be integrated. Must match expected input size.

required

Returns:

Name Type Description
tuple tuple[ndarray, ndarray]
  • I (ndarray): 1D azimuthally integrated intensity.
  • errors_1d (ndarray or None): Standard error of the mean (SEM) for 1D integration if error model is specified, else None.
  • I_2d (None): 2D "cake" integration result.
  • errors_2d (None): 2D error result.

Raises:

Type Description
RuntimeError

If the input image size does not match the expected size.

Source code in azint/azint.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
def integrate(self, 
              img: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """
    Performs azimuthal integration on the input image.

    This function computes the azimuthally averaged intensity from the input image
    using a sparse matrix-based integration approach. Optionally applies a mask
    and computes error estimates if an error model is defined.

    Args:
        img (ndarray): Input image to be integrated. Must match expected input size.

    Returns:
        tuple:
            - I (ndarray): 1D azimuthally integrated intensity.
            - errors_1d (ndarray or None): Standard error of the mean (SEM) for 1D integration if error model is specified, else None.
            - I_2d (None): 2D "cake" integration result.
            - errors_2d (None): 2D error result.

    Raises:
        RuntimeError: If the input image size does not match the expected size.
    """
    img = np.ascontiguousarray(img)

    if img.size != self.input_size:
        raise RuntimeError('Size of image is wrong!\nExpected %d\nActual size %d' %(self.input_size, img.size))
    if self.mask is None:
        norm = self.norm
    else:
        inverted_mask = 1 - self.mask
        img = img*inverted_mask
        norm = self.sparse_matrix.spmv(inverted_mask.reshape(-1))

    signal = self.sparse_matrix.spmv_corrected(img).reshape(self.output_shape)
    norm = norm.reshape(self.output_shape)

    errors = None
    errors_1d = None
    errors_2d = None
    if self.error_model:
        # poisson error model
        errors = np.sqrt(self.sparse_matrix.spmv_corrected2(img)).reshape(self.output_shape)
        if self.normalized:
            errors = np.divide(errors, norm, out=np.zeros_like(errors), where=norm!=0.0)

    if signal.ndim == 1: # must be radial bins only, no eta, ie 1d.
        if self.normalized:
            signal = np.divide(signal, norm, out=np.zeros_like(signal), where=norm!=0.0)
        self.norm_1d = norm
        self.norm_2d = None
        I = signal
        if errors is not None:
            errors_1d = errors
        return I, errors_1d, None, None
    else:  # will have eta bins
        signal_1d =  np.sum(signal, axis=0)
        self.norm_1d = np.sum(norm, axis=0)
        if self.normalized:
            signal_1d = np.divide(signal_1d, self.norm_1d, out=np.zeros_like(signal_1d), where=self.norm_1d!=0.0)
            signal = np.divide(signal, norm, out=np.zeros_like(signal), where=norm!=0.0)
        I = signal_1d
        self.norm_2d = norm
        I_2d = signal
        if errors is not None:
            errors_1d = np.sum(errors, axis=0)
            errors_2d = errors
            if self.normalized:
                errors_1d = np.divide(errors_1d, self.norm_1d, out=np.zeros_like(errors_1d), where=self.norm_1d!=0.0)
                errors_2d = np.divide(errors_2d, norm, out=np.zeros_like(errors_2d), where=norm!=0.0)
        return I, errors_1d, I_2d, errors_2d