Example #1
0
    def test_optical_density_histogram(self):
        """
        Test the optical density calculation is correct and stuffed
        with numpy.inf when the intensity is zero.
        """
        bins = (200, 60)
        size = np.array([[-1, 1], [-1, 1]]) * 30 * u.cm

        intensity_results = cpr.synthetic_radiograph(self.sim_results,
                                                     size=size,
                                                     bins=bins)
        od_results = cpr.synthetic_radiograph(self.sim_results,
                                              size=size,
                                              bins=bins,
                                              optical_density=True)

        assert np.allclose(intensity_results[0], od_results[0])
        assert np.allclose(intensity_results[1], od_results[1])

        intensity = intensity_results[2]
        zero_mask = intensity == 0
        i0 = np.mean(intensity[~zero_mask])
        od = -np.log10(intensity / i0)

        assert np.allclose(od[~zero_mask], od_results[2][~zero_mask])
        assert np.all(np.isposinf(od_results[2][zero_mask]))
Example #2
0
 def test_warns(self):
     """
     Test warning when less than half the particles reach the detector plane.
     """
     sim_results = self.sim_results.copy()
     sim_results["nparticles"] *= 3
     with pytest.warns(RuntimeWarning):
         cpr.synthetic_radiograph(sim_results)
Example #3
0
def run_1D_example(name):
    """
    Run a simulation through an example with parameters optimized to
    sum up to a lineout along x. The goal is to run a relatively fast
    sim with a quasi-1D field grid that can then be summed to get good
    enough statistics to use as a test.
    """
    grid = _test_grid(name, L=1 * u.mm, num=50)

    # Cartesian
    source = (0 * u.mm, -10 * u.mm, 0 * u.mm)
    detector = (0 * u.mm, 200 * u.mm, 0 * u.mm)

    # Expect warnings because these fields aren't well-behaved at the edges
    with pytest.warns(
            RuntimeWarning,
            match="Fields should go to zero at edges of grid to avoid "):
        sim = cpr.Tracker(grid, source, detector, verbose=False)
    sim.create_particles(1e4, 3 * u.MeV, max_theta=0.1 * u.deg)
    sim.run()

    size = np.array([[-1, 1], [-1, 1]]) * 10 * u.cm
    bins = [200, 60]
    hax, vax, values = cpr.synthetic_radiograph(sim, size=size, bins=bins)

    values = np.mean(values[:, 20:40], axis=1)

    return hax, values
Example #4
0
    def test_intensity_histogram(self, args, kwargs, expected):
        """Test several valid use cases."""
        results = cpr.synthetic_radiograph(*args, **kwargs)

        assert len(results) == 3

        x = results[0]
        assert isinstance(x, u.Quantity)
        assert x.unit == u.m
        assert x.shape == (expected["bins"][0], )
        assert np.isclose(np.min(x), expected["xrange"][0], rtol=1e4)
        assert np.isclose(np.max(x), expected["xrange"][1], rtol=1e4)

        y = results[1]
        assert isinstance(y, u.Quantity)
        assert y.unit == u.m
        assert y.shape == (expected["bins"][1], )
        assert np.isclose(np.min(y), expected["yrange"][0], rtol=1e4)
        assert np.isclose(np.max(y), expected["yrange"][1], rtol=1e4)

        histogram = results[2]
        assert isinstance(histogram, np.ndarray)
        assert histogram.shape == expected["bins"]
Example #5
0
def test_add_wire_mesh():

    # ************************************************************
    # Test various input configurations
    # ************************************************************

    # Test a circular mesh
    run_mesh_example(extent=1 * u.mm)

    # Test providing hdir
    run_mesh_example(mesh_hdir=np.array([0.5, 0, 0.5]))

    # Test providing hdir and vdir
    run_mesh_example(mesh_hdir=np.array([0.5, 0, 0.5]),
                     mesh_vdir=np.array([0, 0.1, 1]))

    # ************************************************************
    # Test invalid inputs
    # ************************************************************

    # Test invalid extent (too many elements)
    with pytest.raises(ValueError):
        run_mesh_example(extent=(1 * u.mm, 2 * u.mm, 3 * u.mm))

    # Test wire mesh completely blocks all particles (in this case because
    # the wire diameter is absurdly large)
    with pytest.raises(ValueError):
        run_mesh_example(wire_diameter=5 * u.mm)

    # Test if wire mesh is not between the source and object
    with pytest.raises(ValueError):
        run_mesh_example(location=np.array([0, 3, 0]) * u.mm)

    # ************************************************************
    # Test that mesh is the right size in the detector plane, and that
    # the wire spacing images correctly.
    # This is actually a good overall test of the whole proton radiography
    # particle tracing algorithm.
    # ************************************************************
    loc = np.array([0, -2, 0]) * u.mm
    extent = (1 * u.mm, 1 * u.mm)
    wire_diameter = 30 * u.um
    nwires = 9
    sim = run_mesh_example(
        problem="empty",
        nparticles=1e5,
        location=loc,
        extent=extent,
        wire_diameter=wire_diameter,
        nwires=nwires,
    )

    # Calculate the width that the grid SHOULD have on the image plane
    src_to_mesh = np.linalg.norm(loc.si.value - sim.source)
    mesh_to_det = np.linalg.norm(sim.detector - loc.si.value)
    mag = 1 + mesh_to_det / src_to_mesh
    true_width = mag * extent[0].to(u.mm).value
    true_spacing = true_width / (nwires - 1)

    # Create a synthetic radiograph
    size = np.array([[-1, 1], [-1, 1]]) * 2 * u.cm
    bins = [100, 50]
    # Expect a warning because many particles are off the radiograph
    # (Chose max_theta so corners are covered)
    with pytest.warns(RuntimeWarning):
        h, v, i = cpr.synthetic_radiograph(sim, size=size, bins=bins)

    # Sum up the vertical direction
    line = np.sum(i, axis=1)

    # Determine the points that are on gridlines: where 1/line is above the
    # median by a lot
    ind = np.argwhere(1 / line > 2 * np.median(1 / line))
    hwhere = h.to(u.mm).value[ind]
    measured_width = np.max(hwhere) - np.min(hwhere)

    # Calculate the max spatial frequency (should be close to the grid spacing)
    dx = np.abs(size[0][1] - size[0][0]).to(u.mm).value / bins[0]
    fnyquist = int(bins[0] / 2)
    freqs = np.fft.fftfreq(h.size, d=dx)
    freqs = freqs[:fnyquist]
    # Calculate the positive frequency power spectrum
    pspect = np.abs(np.fft.fft(1 / line))**2
    pspect = pspect[:fnyquist]
    pspect = np.where(np.abs(freqs) < 0.1, 0,
                      pspect)  # Mask the low frequencies

    # Measured spacing is the inverse of the maximum spatial frequency
    measured_spacing = 1 / freqs[np.argmax(pspect)]

    # This test is somewhat tricky, so here's a matplotlib plot
    # that can be uncommented for debugging
    """
    fig, ax = plt.subplots(nrows=3, figsize=(4,15))
    ax[0].pcolormesh(h.to(u.mm).value, v.to(u.mm).value, i.T, cmap='Blues_r')
    ax[0].set_aspect('equal')
    ax[0].axvline(x=np.max(hwhere), color='red')
    ax[0].axvline(x=np.min(hwhere), color='red')

    ax[1].plot(h.to(u.mm).value, 1/line)
    ax[1].axhline(y=np.median(1/line))
    ax[1].axvline(x=np.max(hwhere), color='red')
    ax[1].axvline(x=np.min(hwhere), color='red')

    ax[2].plot(freqs, pspect)
    """

    # Verify that the edges of the mesh are imaged correctly
    assert np.isclose(measured_width, true_width, 1)

    # Verify that the spacing is correct by checking the FFT
    assert np.isclose(measured_spacing, true_spacing, 0.5)
Example #6
0
def test_gaussian_sphere_analytical_comparison():
    """
    This test runs a known example problem and compares to a theoretical
    model for small deflections.

    Still under construction (comparing the actual form of the radiograph
    is possible but tricky to implement).
    """

    # The Gaussian sphere problem for small deflection potentials
    # is solved in Kugland2012relation, and the equations referenced
    # below are from that paper.
    # https://doi.org/10.1063/1.4750234

    a = (1 * u.mm / 3).to(u.mm).value
    phi0 = 1.4e5
    W = 15e6

    l = 10
    L = 200

    # Define and run the problem
    # Setting b to be much larger than the problem so that the field is not
    # cut off at the edges. This is required to be directly
    # comparable to the theoretical result.
    grid = _test_grid(
        "electrostatic_gaussian_sphere",
        num=100,
        phi0=phi0 * u.V,
        a=a * u.mm,
        b=20 * u.mm,
    )
    source = (0 * u.mm, -l * u.mm, 0 * u.mm)
    detector = (0 * u.mm, L * u.mm, 0 * u.mm)

    with pytest.warns(
            RuntimeWarning,
            match="Fields should go to zero at edges of grid to avoid "):
        sim = cpr.Tracker(grid, source, detector, verbose=False)

    sim.create_particles(1e3, W * u.eV, max_theta=12 * u.deg)
    sim.run()

    size = np.array([[-1, 1], [-1, 1]]) * 4 * u.cm
    bins = [100, 100]
    h, v, i = cpr.synthetic_radiograph(sim, size=size, bins=bins)
    h = h.to(u.mm).value / sim.mag
    v = v.to(u.mm).value / sim.mag
    r0 = h

    # Calculate a lineout across the center of the plane (y=0)
    v0 = np.argmin(np.abs(v))

    line = np.mean(i[:, v0 - 6:v0 + 6], axis=1)
    # Zero the edge of the radiograph
    line += -np.mean(line)
    line *= 1 / np.max(np.abs(line))

    # Calculate the theoretical deflection angles (Eq. 28)
    theory = phi0 / W * np.sqrt(np.pi) * (r0 / a) * np.exp(-((r0 / a)**2))

    max_deflection = np.max(np.abs(theory))
    mu = np.sqrt(np.pi) * (phi0 / W) * (l / a)

    # sim_mu = sim.max_deflection.to(u.rad).value*(l/a)

    # Calculate the theoretical inversion (Eq. 31 )
    theory_deflect = -2 * mu * (1 - (r0 / a)**2) * np.exp(-((r0 / a)**2))
    theory_deflect *= 1 / np.max(np.abs(theory_deflect))

    # Uncomment for debug
    """
    print(f"Theory max deflection: {max_deflection:.6f}")
    print(f"Theory mu: {mu:.3f}")
    print(f"Sim max deflection: {sim.max_deflection.to(u.rad).value:.6f}")
    print(f"Sim mu: {sim_mu:.3f}")

    import matplotlib.pyplot as plt
    print(f"Theory max deflection: {max_deflection:.6f}")
    print(f"Theory mu: {mu:.3f}")
    print(f"Sim max deflection: {sim.max_deflection.to(u.rad).value:.6f}")
    print(f"Sim mu: {sim_mu:.3f}")

    fig, ax = plt.subplots()
    ax.pcolormesh(h, v, i.T, shading='auto', cmap='Blues_r')
    ax.set_aspect('equal')

    fig, ax = plt.subplots()
    ax.plot(h, line )
    ax.plot(h, theory_deflect)
    """

    assert np.isclose(max_deflection,
                      sim.max_deflection.to(u.rad).value,
                      atol=1e-3)
Example #7
0
 def test_raises(self, args, kwargs, _raises):
     """Test scenarios the raise an Exception."""
     with pytest.raises(_raises):
         cpr.synthetic_radiograph(*args, **kwargs)
Example #8
0
def test_input_validation():
    """
    Intentionally raise a number of errors.
    """

    # ************************************************************************
    # During initialization
    # ************************************************************************

    grid = _test_grid("electrostatic_gaussian_sphere")
    source = (-10 * u.mm, 90 * u.deg, 45 * u.deg)
    detector = (100 * u.mm, 90 * u.deg, 45 * u.deg)

    # Check that an error is raised when an input grid has a nan or infty value
    # First check NaN
    Ex = grid["E_x"]
    Ex[0, 0, 0] = np.nan * u.V / u.m
    grid.add_quantities(E_x=Ex)
    with pytest.raises(ValueError):
        sim = cpr.Tracker(grid, source, detector, verbose=False)
    Ex[0, 0, 0] = 0 * u.V / u.m

    Ex[0, 0, 0] = np.inf * u.V / u.m  # Reset element for the rest of the tests
    grid.add_quantities(E_x=Ex)
    with pytest.raises(ValueError):
        sim = cpr.Tracker(grid, source, detector, verbose=False)
    Ex[0, 0, 0] = 0 * u.V / u.m

    # Check what happens if a value is large relative to the rest of the array
    Ex[0, 0, 0] = 0.5 * np.max(Ex)
    grid.add_quantities(E_x=Ex)
    # with pytest.raises(ValueError):
    with pytest.warns(RuntimeWarning):
        sim = cpr.Tracker(grid, source, detector, verbose=False)
    Ex[0, 0, 0] = 0 * u.V / u.m

    # Raise error when source-to-detector vector doesn't pass through the
    # field grid
    source_bad = (10 * u.mm, -10 * u.mm, 0 * u.mm)
    detector_bad = (10 * u.mm, 100 * u.mm, 0 * u.mm)
    with pytest.raises(ValueError):
        sim = cpr.Tracker(grid, source_bad, detector_bad, verbose=False)

    # Test raises warning when one (or more) of the required fields is missing
    grid_bad = CartesianGrid(-1 * u.mm, 1 * u.mm, num=50)
    with pytest.warns(RuntimeWarning,
                      match="is not specified for the provided grid."):
        sim = cpr.Tracker(grid_bad, source, detector, verbose=True)

    # ************************************************************************
    # During create_particles
    # ************************************************************************
    sim = cpr.Tracker(grid, source, detector, verbose=False)
    sim.create_particles(1e3, 15 * u.MeV, max_theta=0.99 * np.pi / 2 * u.rad)

    # ************************************************************************
    # During runtime
    # ************************************************************************

    sim = cpr.Tracker(grid, source, detector, verbose=False)
    sim.create_particles(1e3, 15 * u.MeV)

    # Test an invalid field weighting keyword
    with pytest.raises(ValueError):
        sim.run(field_weighting="not a valid field weighting")

    # ************************************************************************
    # During runtime
    # ************************************************************************
    # SYNTHETIC RADIOGRAPH ERRORS
    sim.run()

    # Choose a very small synthetic radiograph size that misses most of the
    # particles
    with pytest.warns(
            RuntimeWarning,
            match="of the particles are shown on this synthetic radiograph."):
        size = np.array([[-1, 1], [-1, 1]]) * 1 * u.mm
        hax, vax, values = cpr.synthetic_radiograph(sim, size=size)