Exemplo n.º 1
0
def test_5(hu=False):
    # creates an image of the absolute error in the reconstruction

    # set test size, angles, scale and source energy
    n, angles, scale, E = 256, 256, 0.01, 0.1
    s = fake_source(source.mev, E, method='ideal')

    # create a phantom and reconstruction
    p = ct_phantom(material.name, n, 7)
    y = scan_and_reconstruct(s, material, p, scale, angles)

    # convert the phantom indices to material coefficients at the source energy
    p_mu = np.zeros_like(p)
    for index, name in enumerate(material.name):
        if index in p:
            p_mu += np.where(p == index, material.coeff(name)[np.argmax(s)], 0)

    # convert to HU if reconstruction in HU
    if hu:
        p_mu = 1000 * (p_mu - material.coeff('Water')[np.argmax(s)]
                       ) / material.coeff('Water')[np.argmax(s)]
        p_mu = np.clip(p_mu, -1024, 3071)

    # calculate square error for each pixel
    err = np.where(y >= (0, -1024)[hu], np.abs(p_mu - y), 0)

    # save some meaningful results
    save_draw(err, 'results', 'test_5_image')
Exemplo n.º 2
0
def test_4():
    # This test compares the data values for the phantom and reconstructed image across
    # the 128th row of pixels - type 6 phantom

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 6)
    s = source.photon('80kVp, 2mm Al')
    y = scan_and_reconstruct(s, material, p, 0.01, 256)

    # save some meaningful results

    # let's compare the values when we scale to the same range
    y_scaled = y[127, :]
    y_scaled = np.interp(y_scaled, (y_scaled.min(), y_scaled.max()), (-1, +10))

    p_scaled = p[127, :]
    p_scaled = np.interp(p_scaled, (p_scaled.min(), p_scaled.max()), (-1, +10))

    plt.plot(y_scaled)
    plt.plot(p_scaled)
    plt.title('Comparing the data values on the 128th row - Scaled')
    plt.legend(['Reconstruced', 'Phantom'])
    plt.ylabel('Data value')
    plt.xlabel('Sample')
    #plt.show()
    plt.savefig('results/test_4_plot_scaled')
    plt.close()
Exemplo n.º 3
0
def test_6(hu=False):
    # assesses the accuracy of a rounding-based material reconstruction

    # set test size, angles, scale and source energy
    n, angles, scale, E = 256, 256, 0.01, 0.1
    s = fake_source(source.mev, E, method='ideal')

    # create a phantom and reconstruction
    p = ct_phantom(material.name, n, 6)
    y = scan_and_reconstruct(s, material, p, scale, angles)

    # get material coefficients at the source energy
    mu_E = material.coeffs[:, np.argmax(s)].flatten()

    # convert to HU if reconstruction in HU
    if hu:
        mu_E = 1000 * (mu_E - material.coeff('Water')[np.argmax(s)]
                       ) / material.coeff('Water')[np.argmax(s)]

    # round each reconstructed value to the nearest material
    p_reconstruction = np.argmin(np.abs(np.subtract.outer(y, mu_E)), axis=-1)

    # find areas where the reconstruction identifies the correct materials
    correct = np.where(p_reconstruction == p, 1, 0)

    # save some meaningful results
    save_draw(correct, 'results', 'test_6_image')
Exemplo n.º 4
0
def test_4(hu=False):
    # compare the reconstructed and real attenuation coefficients

    # set test size and scale
    n, angles, scale = 128, 128, 0.01
    s = fake_source(source.mev, 0.1, method='ideal')

    o_str = ''
    for name in material.name[:9]:
        # create a phantom and reconstruction
        p = ct_phantom(material.name, n, 1, metal=name)
        y = scan_and_reconstruct(s, material, p, scale, angles)

        # get attenuation coefficients
        mu_E = material.coeff(name)[np.argmax(s)]
        mu_reconstruction = np.mean(y[n // 4:3 * n // 4, n // 4:3 * n // 4])

        # convert to HU if reconstruction in HU
        if hu:
            mu_E = 1000 * (mu_E - material.coeff('Water')[np.argmax(s)]
                           ) / material.coeff('Water')[np.argmax(s)]

        # record actual and expected coefficients
        o_str += '{0:<35} {1:<20} {2:>12}'.format(
            'Mean value for ' + name + ' is ', mu_reconstruction,
            '(Expected: ' + str(mu_E) + ')\n')

    # save some meaningful results
    with open('results/test_4_output.txt', mode='w') as f:
        f.write('Test with image size: ' + str(n) + 'x' + str(n) +
                ' and scale: ' + str(scale) + ' cm/pixel\n\n')
        f.write(o_str)
Exemplo n.º 5
0
def test_2():
    # explain what this test is for

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 2)
    s = source.photon('80kVp, 1mm Al')
    y = scan_and_reconstruct(s, material, p, 0.01, 256)

    # save some meaningful results
    save_plot(y[128, :], 'results', 'test_2_plot')
Exemplo n.º 6
0
def test_1():
    # explain what this test is for

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 3)
    s = source.photon('100kVp, 3mm Al')
    y = scan_and_reconstruct(s, material, p, 0.01, 256)

    # save some meaningful results
    save_draw(y, 'results', 'test_1_image')
    save_draw(p, 'results', 'test_1_phantom')
Exemplo n.º 7
0
def test_3():
    # explain what this test is for

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 1)
    s = fake_source(source.mev, 0.1, method='ideal')
    y = scan_and_reconstruct(s, material, p, 0.1, 256)

    # save some meaningful results
    f = open('results/test_3_output.txt', mode='w')
    f.write('Mean value is ' + str(np.mean(y[64:192, 64:192])))
    f.close()
Exemplo n.º 8
0
def test_1():
    # For this test, view the plots and compare visually that they have similar geometry

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 3)
    s = source.photon('100kVp, 3mm Al')
    y = scan_and_reconstruct(s, material, p, 0.01, 256)

    # save some meaningful results
    save_draw(y, 'results', 'test_1_image')
    save_draw(p, 'results', 'test_1_phantom')
    plt.close()
Exemplo n.º 9
0
def test_2():
    # plots the 1D impulse response of the reconstruction process

    # set test size and scale
    n, angles, scale = 256, 256, 0.01
    s = source.photon('80kVp, 1mm Al')

    # create a phantom and reconstruction
    p = ct_phantom(material.name, n, 2)
    y = scan_and_reconstruct(s, material, p, scale, angles)

    # save some meaningful results
    save_plot(y[128, :], 'results', 'test_2_plot')
Exemplo n.º 10
0
def test_3():
    # calculates the mean reconstructed attenuation of tissue

    # set test size and scale
    n, angles, scale = 256, 256, 0.01
    s = fake_source(source.mev, 0.1, method='ideal')

    # create a phantom and reconstruction
    p = ct_phantom(material.name, n, 1)
    y = scan_and_reconstruct(s, material, p, scale, angles)

    # save some meaningful results
    with open('results/test_3_output.txt', mode='w') as f:
        f.write('Mean value is ' + str(np.mean(y[64:192, 64:192])))
Exemplo n.º 11
0
def test_1():
    # produces a phantom and reconstruction for geometric comparison

    # set test size and scale
    n, angles, scale = 256, 256, 0.01
    s = source.photon('100kVp, 3mm Al')

    # create a phantom and reconstruction
    p = ct_phantom(material.name, n, 3)
    y = scan_and_reconstruct(s, material, p, scale, angles)

    # save some meaningful results
    save_draw(y, 'results', 'test_1_image')
    save_draw(p, 'results', 'test_1_phantom')
Exemplo n.º 12
0
def test_3():
    ''' 
	Test for reconstruction accuracy:

	reconstruct the pelvic fixation pins phantom, check that the error
	between the phantom and the reconstruction is sufficiently small
	'''

    # INITIAL CONDITIONS

    # pelvic fixation pins phantom
    p = ct_phantom(material.name, 256, 7)
    # ideal source used for converting phantom to predictable attenuation value
    source_energy = 0.14
    s = fake_source(source.mev, source_energy, method='ideal')

    # SETUP

    # generate scan
    scan = scan_and_reconstruct(s, material, p, 0.1, 256)

    # Find material attenuation coefficients for chosen ideal source
    material_attenuations = material.coeffs[:,
                                            np.where(
                                                material.mev ==
                                                round(0.7 *
                                                      source_energy, 3))[0][0]]
    # Convert phantom image from indices to true attenuation values
    p = convert_phantom(p, material_attenuations)

    # Find the error between the scaled phantom and reconstructed image
    error_image = scan - p
    # Measure RMS error, ignoring pixels outside of scanning circle
    rms_error = np.sqrt(np.mean(error_image[np.where(error_image > -1)]**2))

    # TESTS

    # save scan, scaled phantom and error images
    save_draw(scan, 'results/test_3', 'test_3_image')
    save_draw(p, 'results/test_3', 'test_3_phantom_scaled')
    save_draw(error_image, 'results/test_3', 'test_3_error')

    # save RMS reconstruction error
    f = open('results/test_3/test_3_output.txt', mode='w')
    f.write(f"RMS reconstruction error is {round(rms_error, 5)} \n")
    f.close()

    # expect that measured RMS error is sufficiently small
    assert rms_error < 0.06, f"RMS error too large, got {rms_error}"
Exemplo n.º 13
0
def test_3():
    # This test compares the mean value of the central area of pixels between the
    # phantom and the reconstructed image for the point impule phantom

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 1)
    s = fake_source(source.mev, 0.1, method='ideal')
    y = scan_and_reconstruct(s, material, p, 0.1, 256)

    # save some meaningful results
    f = open('results/test_3_output.txt', mode='w')
    f.write('Mean value is of middle part of reconstructed is ' +
            str(np.mean(y[64:192, 64:192])))
    f.write("\n")
    f.write('Mean value is of middle part of phantom is ' +
            str(np.mean(p[64:192, 64:192])))
    f.close()
Exemplo n.º 14
0
    def run_trial(material_name, energy, mean=True, accuracy=0.05):
        """run_trial returns the mean value of the linear attenuation coefficient from a region of the measured
        distribution, and compares it to the actual value by means of a % error.
        The function takes in a material (string) and an energy (float). It creates a type 1 image phantom
        (circular disk) of size 256x256, scale 0.1 cm per pixel and specified material, which is scanned using an ideal
        fake source of specified MeVp energy. The mean coeff is found for a 128x128 square in the centre of the image,
        and compared to the actual coeff data from an xlsx spreadsheet. The results are stored in test_3_output.txt with
        an error range (percentage difference between experimental and exact attenuation values)
        """
        # construct a phantom of size 256x256 and given material
        p = ct_phantom(material.name, 256, 1, metal=material_name)
        # use an ideal source to negate the effects of a polyenergetic beam on the measured attenuation coefficients
        s = fake_source(source.mev, energy, method='ideal')
        # scan the phantom, assuming a pixel scale of 0.1 cm per pixel, and reconstruct the image
        y = scan_and_reconstruct(s, material, p, 0.1, 256)

        if mean:
            # calculate the mean of the centre 128x128 pixels
            experimental_value = np.mean(y[64:192, 64:192])
        else:
            # find the max if material is a metal or with low energies
            max_value = np.max(y)
            # average the coeff values which lie within a given accuracy of this max
            samples = np.where(y > max_value * (1 - accuracy), y,
                               0)  # Select values accuracy% from max value
            samples = np.ma.masked_equal(
                samples, 0)  # Remove zeros for mean calculation
            experimental_value = np.mean(samples)

        # obtain the peak energy in the ideal source
        shifted_energy = np.round(energy * 0.7, 3)
        # obtain actual coeff value from excel spreadsheet
        actual_value = material.coeff(material_name)[np.where(
            material.mev == shifted_energy)[0][0]]

        # save the mean, actual and % difference results in a txt file
        f.write(material_name + ' (' + str(energy) + 'kVp): \n')
        f.write('Mean experimental value: ' + str(experimental_value) +
                ' cm^-1\n')
        f.write('Actual linear attenuation coefficient: ' + str(actual_value) +
                ' cm^-1\n')
        f.write('Difference between the values: ' + str(
            np.round((abs(actual_value - experimental_value) / actual_value) *
                     100, 2)) + '%\n\n')
Exemplo n.º 15
0
def test_2():
    # This test compares the data values on the 128th row of the phantom and the
    # reconstructed image for the point impulse phantom

    # work out what the initial conditions should be
    p = ct_phantom(material.name, 256, 2)
    s = source.photon('80kVp, 1mm Al')
    y = scan_and_reconstruct(s, material, p, 0.01, 256)

    # save some meaningful results
    #save_plot(y[128,:], 'results', 'test_2_plot')

    plt.plot(y[127, :])
    plt.plot(p[127, :])
    plt.title('Comparing the data values on the 128th row')
    plt.legend(['Reconstruced', 'Phantom'])
    plt.ylabel('Data value')
    plt.xlabel('Sample')
    #plt.show()
    plt.savefig('results/test_2_plot')
    plt.close()
Exemplo n.º 16
0
def test_2():
    ''' 
	Test for reconstruction values:

	reconstruct a hip implant phantom, check that the attenuation 
	value of the implant most closely matches the attenuation value 
	of the phantom implant material and evaluate the attenuation error of the implant
	'''

    # INITIAL CONDITIONS

    # hip implant phantom
    p = ct_phantom(material.name, 256, 3)
    # ideal source used for comparing material values
    source_energy = 0.10
    s = fake_source(material.mev, source_energy, method='ideal')

    # SETUP

    # generate scan
    scan = scan_and_reconstruct(s, material, p, 0.01, 256)

    # find location of implant using phantom as reference
    implant_location = np.where(p == 7)
    # average attenuation values of implant on reconstructed image
    implant_attenuation = np.mean(scan[implant_location])
    # find material attenuation coefficients for chosen ideal source
    material_attenuations = material.coeffs[:,
                                            np.where(
                                                material.mev ==
                                                round(0.7 *
                                                      source_energy, 3))[0][0]]

    # find the difference between the material coefficients and the computed average and find the min
    error = np.abs(material_attenuations - implant_attenuation)
    material_idx = error.argmin()

    # use the closest material attenuation value to infer material of implant
    predicted_material = material.name[material_idx]

    # Set parameters for drawing rectangle
    x1 = min(implant_location[1])
    y1 = implant_location[0][0]

    lx = implant_location[0][-1] - y1
    ly = max(implant_location[1]) - min(implant_location[1])

    # TESTS

    # save diagram with highlighted implant location and material
    save_rectangle(scan, 'results/test_2', 'test_2_image', (x1, y1), (lx, ly),
                   predicted_material)

    # save implant attenuation value, error and inferred material
    f = open('results/test_2/test_2_output.txt', mode='w')
    f.write(
        f"Implant attenuation value is  {round(implant_attenuation, 3)} \n")
    f.write(f"Predicted material is {predicted_material} \n")
    f.write(f"Attenuation error is {round(error[material_idx], 3)}\n")
    f.write(
        f"As a percentage is {round(error[material_idx]*100/implant_attenuation, 3)} %\n"
    )
    f.close()

    # Expect predicted material for implant to be Titanium
    assert predicted_material == "Titanium", f"Implant should be Titanium, got {predicted_material}"
    # Expect attenuation error is sufficiently small
    assert error[
        material_idx] < 0.05, f"Attenuation error too large, got {error[material_idx]}"
Exemplo n.º 17
0
def test_7(hu=False):
    # calculates the structural similarity index between the attenuation
    # phantom and reconstruction

    # set test size, angles, scale, source energy and filter thickness
    n, angles, scale, E, t = 256, 256, 0.01, 0.1, 2
    s_i = fake_source(source.mev,
                      E,
                      material.coeff('Aluminium'),
                      t,
                      method='ideal')
    s_r = source.photon('{:d}kVp, {:d}mm Al'.format(int(1000 * E), t))

    # create a phantom and reconstructions
    p = ct_phantom(material.name, n, 5)
    y_i = scan_and_reconstruct(s_i, material, p, scale, angles)
    y_r = scan_and_reconstruct(s_r, material, p, scale, angles)

    # convert the phantom indices to material coefficients at the source energy
    p_mu = np.zeros_like(p)
    for index, name in enumerate(material.name):
        if index in p:
            p_mu += np.where(p == index,
                             material.coeff(name)[np.argmax(s_i)], 0)

    # convert to HU if reconstruction in HU
    if hu:
        p_mu = 1000 * (p_mu - material.coeff('Water')[np.argmax(s_i)]
                       ) / material.coeff('Water')[np.argmax(s_i)]
        p_mu = np.clip(p_mu, -1024, 3071)

        # remap to 8-bit values
        y_i = np.interp(y_i, (-1024, 3071), (0, 255))
        y_r = np.interp(y_r, (-1024, 3071), (0, 255))
        p_mu = np.interp(p_mu, (-1024, 3071), (0, 255))
    else:
        y_i = np.clip(y_i, 0, None)
        y_r = np.clip(y_r, 0, None)

        # remap to 8-bit values
        low, high = np.min(np.concatenate(
            (y_i, y_r, p_mu))), np.max(np.concatenate((y_i, y_r, p_mu)))
        y_i = np.interp(y_i, (low, high), (0, 255))
        y_r = np.interp(y_r, (low, high), (0, 255))
        p_mu = np.interp(p_mu, (low, high), (0, 255))

    # calculate the SSIM
    L = 2**8 - 1
    c1, c2 = (0.01 * L)**2, (0.03 * L)**2
    cov = lambda x, y: np.mean((y - np.mean(y)) * (x - np.mean(x)))
    SSIM = lambda x, y: (2 * np.mean(y) * np.mean(x) + c1) * (2 * cov(
        x, y) + c2) / ((np.mean(y)**2 + np.mean(x)**2 + c1) *
                       (np.var(y) + np.var(x) + c2))

    # save some meaningful results
    with open('results/test_7_output.txt', mode='w') as f:
        f.write(
            'SSIM between the attenuation phantom and ideal source reconstruction is        '
            + str(SSIM(p_mu, y_i)) + '\n')
        f.write(
            'SSIM between the attenuation phantom and real source reconstruction is         '
            + str(SSIM(p_mu, y_r)) + '\n')
        f.write(
            'SSIM between the ideal source reconstruction and real source reconstruction is '
            + str(SSIM(y_i, y_r)))
Exemplo n.º 18
0
def test_1():
    """This test plots the phantom image next to the reconstructed CT data, allowing for a direct visual comparison
    between the original and returned image. This high level comparison demonstrates the CT simulator is generally
    working as expected if features in the phantom appear in the appropriate positions on the reconstructed image"""
    def run_trial(case, material_name=None):
        """run_trial plots a phantom alongside a CT reconstructed image
        x = run_trial(case, material) creates a CT phantom of a given type and material, of size 256 x 256 and
        scale 0.1, from the ct_phantom function. It is then scanned and reconstructed using 100kVp source with a
        1mm Al filter, and the reconstructed image plot beside the phantom."""

        # work out what the initial conditions should be
        # construct the phantom and X-ray source
        p = ct_phantom(material.name, 256, case, metal=material_name)
        s = source.photon('100kVp, 1mm Al')
        # obtain the reconstructed CT image
        y = scan_and_reconstruct(s, material, p, 0.1, 256)

        # plot the phantom and CT image side-by-side
        map = 'gray'
        fig, (ax1, ax2) = plt.subplots(1, 2)
        ax1.set_title('Phantom (' + material_name + ')')
        im = ax1.imshow(p, cmap=map)
        cbar = plt.colorbar(im,
                            ax=ax1,
                            fraction=0.046,
                            pad=0.04,
                            orientation='vertical')

        ax2.set_title('Reconstruction')
        im2 = ax2.imshow(y, cmap=map)
        cbar2 = plt.colorbar(im2,
Exemplo n.º 19
0
def test_1():
    ''' 
	Test for reconstruction geometry:

	reconstruct a point attenuator phantom away from the origin,
	check the location of the attenuator matches the phantom and 
	evaluate the radius over which the attenuation value spreads
	'''

    # INITIAL CONDITIONS

    # point attenuator phantom used for comparing geometry
    p = ct_phantom(material.name, 256, 2, 'Titanium')

    # ideal source used for evaluating error and spread
    source_energy = 0.12
    s = fake_source(material.mev, source_energy, method='ideal')

    # SETUP

    # generate scan
    scan = scan_and_reconstruct(s, material, p, 0.1, 256)

    # find material attenuation coefficients for chosen ideal source
    material_attenuations = material.coeffs[:,
                                            np.where(
                                                material.mev ==
                                                round(0.7 *
                                                      source_energy, 3))[0][0]]
    # convert phantom image from indices to true attenuation values
    p = convert_phantom(p, material_attenuations)

    # find location of the pixel with maximum value in the reconstructed image
    max_index = np.unravel_index(scan.argmax(), scan.shape)
    # find absolute error
    error = abs(scan - p)

    # measure radius of spread on error image
    radius = measure_spread(error, max_index, 0.01)

    # TESTS

    # save diagram with highlighted attenuation area
    save_circle(scan, 'results/test_1', 'test_1_image', max_index, radius,
                'Error')

    # find location of the pixel with maximum value in the phantom image
    max_index_phantom = np.unravel_index(p.argmax(), p.shape)

    # save location of maximum value and area of attenuation circle
    area = np.pi * (radius**2)
    f = open('results/test_1/test_1_output.txt', mode='w')
    f.write(
        f"Position of max attenuation, Scan: {max_index}, Phantom: {max_index_phantom} \n"
    )
    f.write(f"Area of circle { round(area, 3) }")
    f.close()

    # expect that location of maximum attenuation in reconstructed image matches location in phantom
    assert max_index == max_index_phantom, f'max attenuation location does not \
						match, got {max_index}, expected {max_index_phantom}'

    # expect that attenuation spread area is sufficiently small
    assert area < 40, f'area is above threshold, got {area}'