def forward_spectral(vol, proj, geometry, materials, energy, spectrum, n_phot=1e8): """ Simulate spectral data using labeled volume. """ max_label = int(vol.max()) if max_label != len(materials): raise ValueError( 'Number of materials is not the same as the number of labels in the volume!' ) # Normalize spectrum: spectrum /= (spectrum * energy).sum() # Simulate intensity images: lab_proj = [] for jj in range(max_label): # Forward project: proj_j = numpy.zeros_like(proj) vol_j = numpy.float32(vol == (jj + 1)) projector.forwardproject(proj_j, vol_j, geometry) lab_proj.append(proj_j) for ii in range(len(energy)): # Monochromatic components: monochrome = numpy.ones_like(proj) for jj in range(max_label): mu = linear_attenuation(energy, materials[jj]['material'], materials[jj]['density']) monochrome *= numpy.exp(-lab_proj[jj] * mu[ii]) monochrome *= spectrum[ii] monochrome = apply_noise(monochrome, 'poisson', n_phot) / n_phot # Detector response is assumed to be proportional to E proj += energy[ii] * monochrome
import numpy #%% Create volume and forward project (32 projections): # Initialize images: proj = numpy.zeros([30, 64, 256], dtype = 'float32') # Define a simple projection geometry: geom = geometry.circular(src2obj = 100, det2obj = 100, det_pixel = 0.01, ang_range = [0, 360]) # Create phantom and project into proj: vol = phantom.abstract_nudes([30, 256, 256], geom, complexity = 8) display.slice(vol, title = 'Phantom') # Forward project: projector.forwardproject(proj, vol, geom) display.slice(proj, title = 'Sinogram') #%% Unfiltered back-project # Make volume: vol_rec = numpy.zeros_like(vol) # Backproject: projector.backproject(proj, vol_rec, geom) display.slice(vol_rec, title = 'Backprojection') #%% Filtered back-project # Make volume: vol_rec = numpy.zeros_like(vol)
geom_b.parameters['vol_rot'] = [0.0, 0.0, 90.0] geom_b.parameters['vol_tra'] = [0, -0.5, 0] # Create a phantom: vol = phantom.random_spheroids([100, 100, 100], geom_a, 10) display.slice(vol, dim=1, title='Phantom') #%% Forward model: # Initialize images: proj_a = numpy.zeros([128, 32, 128], dtype='float32') proj_b = numpy.zeros([128, 32, 128], dtype='float32') # Forward project: projector.forwardproject(proj_a, vol, geom_a) projector.forwardproject(proj_b, vol, geom_b) display.slice(proj_a, dim=1, title='Proj A') display.slice(proj_b, dim=1, title='Proj B') #%% Preview reconstructions: geom_b = geom_a.copy() # First volume: vola = projector.init_volume(proj_a) projector.FDK(proj_a, vola, geom_a) # Second volume: volb = projector.init_volume(proj_b) projector.FDK(proj_b, volb, geom_b)
def calibrate_spectrum(projections, volume, geometry, compound='Al', density=2.7, threshold=None, iterations=1000, n_bin=10): ''' Use the projection stack of a homogeneous object to estimate system's effective spectrum. Can be used by process.equivalent_thickness to produce an equivalent thickness projection stack. Please, use conventional geometry. ''' #import random # Find the shape of the object: if threshold: t = binary_threshold(volume, mode='constant', threshold=threshold) segmentation = numpy.float32() else: t = binary_threshold(volume, mode='otsu') segmentation = numpy.float32(volume > t) display.slice(segmentation, dim=0, title='Segmentation') # Crop: #height = segmentation.shape[0] #w = 15 #length = length[height//2-w:height//2 + w, : ,:] # Forward project the shape: print('Calculating the attenuation length.') length = numpy.zeros_like(projections) length = numpy.ascontiguousarray(length) projector.forwardproject(length, segmentation, geometry) projections[projections < 0] = 0 intensity = numpy.exp(-projections) # Crop to avoid cone artefacts: height = intensity.shape[0] // 2 window = 10 intensity = intensity[height - window:height + window, :, :] length = length[height - window:height + window, :, :] # Make 1D: intensity = intensity[length > 0].ravel() length = length[length > 0].ravel() lmax = length.max() lmin = length.min() print('Maximum reprojected length:', lmax) print('Minimum reprojected length:', lmin) print('Selecting a random subset of points.') # Rare the sample to avoid slow times: #index = random.sample(range(length.size), 1000000) #length = length[index] #intensity = intensity[index] print('Computing the intensity-length transfer function.') # Bin number for lengthes: bin_n = 128 bins = numpy.linspace(lmin, lmax, bin_n) # Sample the midslice: #segmentation = segmentation[height//2-w:height//2 + w, : ,:] #projections_ = projections[height//2-w:height//2 + w, : ,:] #import flexModel #ctf = flexModel.get_ctf(length.shape[::2], 'gaussian', [1, 1]) #length = flexModel.apply_ctf(length, ctf) # TODO: Some cropping might be needed to avoid artefacts at the edges #flexUtil.slice(length, title = 'length sinogram') #flexUtil.slice(projections_, title = 'apparent sinogram') # Rebin: idx = numpy.digitize(length, bins) # Rebin length and intensity: length_0 = bins + (bins[1] - bins[0]) / 2 intensity_0 = [numpy.median(intensity[idx == k]) for k in range(bin_n)] # In case some bins are empty: intensity_0 = numpy.array(intensity_0) length_0 = numpy.array(length_0) length_0 = length_0[numpy.isfinite(intensity_0)] intensity_0 = intensity_0[numpy.isfinite(intensity_0)] # Get rid of tales: rim = len(length_0) // 20 length_0 = length_0[rim:-rim] intensity_0 = intensity_0[rim:-rim] # Dn't trust low counts! length_0 = length_0[intensity_0 > 0.05] intensity_0 = intensity_0[intensity_0 > 0.05] # Get rid of long rays (they are typically wrong...) #intensity_0 = intensity_0[length_0 < 35] #length_0 = length_0[length_0 < 35] # Enforce zero-one values: length_0 = numpy.insert(length_0, 0, 0) intensity_0 = numpy.insert(intensity_0, 0, 1) #flexUtil.plot(length_0, intensity_0, title = 'Length v.s. Intensity') print('Intensity-length curve rebinned.') print('Computing the spectrum by Expectation Maximization.') volts = geometry.description.get('voltage') if not volts: volts = 100 energy = numpy.linspace(5, max(100, volts), n_bin) mu = model.linear_attenuation(energy, compound, density) exp_matrix = numpy.exp(-numpy.outer(length_0, mu)) # Initial guess of the spectrum: spec = model.bremsstrahlung(energy, volts) spec *= model.scintillator_efficiency(energy, 'Si', rho=5, thickness=0.5) spec *= model.total_transmission(energy, 'H2O', 1, 1) spec *= energy spec /= spec.sum() #spec = numpy.ones_like(energy) #spec[0] = 0 #spec[-1] = 0 norm_sum = exp_matrix.sum(0) spec0 = spec.copy() #spec *= 0 # exp_matrix at length == 0 is == 1. Sum of that is n_bin # EM type: for ii in range(iterations): frw = exp_matrix.dot(spec) epsilon = frw.max() / 100 frw[frw < epsilon] = epsilon spec = spec * exp_matrix.T.dot(intensity_0 / frw) / norm_sum # Make sure that the total count of spec is 1 - that means intensity at length = 0 is equal to 1 spec = spec / spec.sum() print('Spectrum computed.') #flexUtil.plot(length_0, title = 'thickness') #flexUtil.plot(mu, title = 'mu') #flexUtil.plot(_intensity, title = 'synth_counts') # synthetic intensity for a check: _intensity = exp_matrix.dot(spec) import matplotlib.pyplot as plt # Display: plt.figure() plt.semilogy(length[::200], intensity[::200], 'b.', lw=4, alpha=.8) plt.semilogy(length_0, intensity_0, 'g--') plt.semilogy(length_0, _intensity, 'r-', lw=3, alpha=.6) #plt.scatter(length[::100], -numpy.log(intensity[::100]), color='b', alpha=.2, s=4) plt.axis('tight') plt.title('Log intensity v.s. absorption length.') plt.legend(['raw', 'binned', 'solution']) plt.show() # Display: plt.figure() plt.plot(energy, spec, 'b') plt.plot(energy, spec0, 'r:') plt.axis('tight') plt.title('Calculated spectrum') plt.legend(['computed', 'initial guess']) plt.show() return energy, spec