Custom background

This example defines a Python background object whose background method transforms the complete simulated Datafield. It divides the detector into four regions and applies no background, two constant offsets, or Poisson noise according to the bin coordinates. The result is compared with the unmodified simulation.

The callback contract is described in the background reference.

Result

Custom background result

Sample

Custom background sample

Python script

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = ["bornagain>=25,<26"]
# ///
"""
Coordinate-dependent custom background in a GISAS simulation.
"""
import bornagain as ba
import numpy as np
ba.require_versions("bornagain>=25,<26")
from bornagain import deg, nm


def get_sample():
    """
    A dilute random assembly of cylinders on a substrate.
    """
    substrate_color = (0.28, 0.57, 0.82)
    substrate_mat = ba.RefractiveMaterial("Substrate", substrate_color, 6e-6, 2e-8)
    particle_color = (0.86, 0.24, 0.18)
    particle_mat = ba.RefractiveMaterial("Particle", particle_color, 6e-4, 2e-8)
    particle = ba.Particle(particle_mat, ba.Cylinder(5*nm, 5*nm))

    particle_layer = ba.Layer(ba.Vacuum())
    particle_layer.deposit2D(ba.Dilute2D(0.001, particle))
    substrate_layer = ba.Layer(substrate_mat)

    sample = ba.Sample()
    sample.addLayer(particle_layer)
    sample.addLayer(substrate_layer)
    return sample


class DetectorRegionBackground:
    """
    Different background models for four detector regions.
    """

    def __init__(self, phi_boundary, alpha_boundary, seed=0):
        self.phi_boundary = phi_boundary
        self.alpha_boundary = alpha_boundary
        self.rng = np.random.default_rng(seed)

    def background(self, field):
        """
        Use two constant backgrounds, Poisson noise, and no background in the
        four quadrants.
        """
        values = field.intensities().copy()
        phi_axis = field.xCenters()
        alpha_axis = field.yCenters()
        for i_alpha, i_phi in np.ndindex(values.shape):
            alpha = alpha_axis[i_alpha]
            phi = phi_axis[i_phi]

            # No background in the lower-left quadrant.
            if alpha < self.alpha_boundary and phi < self.phi_boundary:
                values[i_alpha, i_phi] += 0.

            # Small constant background in the lower-right quadrant.
            elif alpha < self.alpha_boundary and phi >= self.phi_boundary:
                values[i_alpha, i_phi] += 0.03

            # Poisson noise in the upper-left quadrant.
            elif alpha >= self.alpha_boundary and phi < self.phi_boundary:
                intensity = values[i_alpha, i_phi]
                if np.isfinite(intensity):
                    values[i_alpha, i_phi] = self.rng.poisson(intensity)

            # Large constant background in the upper-right quadrant.
            else:
                values[i_alpha, i_phi] += 0.3

        return ba.Datafield(field.title(), field.frame(), values.ravel().tolist())


def get_simulation(sample, background=None):
    beam = ba.Beam(1e6, 0.1*nm, 0.2*deg)
    n = 100
    detector = ba.SphericalDetector(n, 0., 2*deg, n, 0., 2*deg)
    simulation = ba.ScatteringSimulation(beam, sample, detector)
    if background is not None:
        simulation.setBackground(background)
    return simulation


def simulate(sample, background, title):
    simulation = get_simulation(sample, background)
    result = simulation.simulate()
    result.setTitle(title)
    return result


if __name__ == '__main__':
    sample = get_sample()
    background = DetectorRegionBackground(1*deg, 0.63*deg)
    results = [
        simulate(sample, None, "No background"),
        simulate(sample, background, "Custom background"),
    ]
    ba.showSample3D(sample, sample_size=120*nm, seed=0)
    ba.plot2d_to_row(results, log_range=5, unit_aspect=1)
    ba.plt.show()
auto/Examples/gisas/setup/CustomBackground.py