def backproject(projections, volume, geometry, algorithm='BP3D_CUDA'): """ Backproject using standard ASTRA functionality. If data array is memmap, backprojection is done in blocks to save RAM. Args: projections : input numpy.array (dtype = float32) with the following dimensions: [vrt, rot, hrz] volume : output numpy.array (dtype = float32) with the following dimensions: [vrt, mag, hrz] geometry : geometry description - one of threee types: 'simple', 'static_offsets', 'linear_offsets' algorithm : ASTRA algorithm type ['BP3D_CUDA', 'FDK_CUDA' etc.] """ global settings block_number = settings['block_number'] mode = settings['mode'] # Check if projections should be subsampled: sam = geometry['proj_sample'] if sum(sam) > 3: projections = projections[::sam[0], ::sam[1], ::sam[2]] # Weight correction prj_weight = _astra_norm_(projections, volume, geometry, algorithm) # If algorithm is FDK we use single-block projection unless data is a memmap if block_number == 1: projections = _contiguous_check_(projections) # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, volume.shape) proj_geom = io.astra_proj_geom(geometry, projections.shape) # Progress bar: pbar = _pbar_start_(1) projections *= prj_weight # ASTRA here... _backproject_block_add_(projections, volume, proj_geom, vol_geom, algorithm) projections /= prj_weight _pbar_update_(pbar) _pbar_close_(pbar) else: # Here is the multi-block version: # Initialize ASTRA volume geometry: vol_geom = io.astra_vol_geom(geometry, volume.shape) # Progress bar: pbar = _pbar_start_(block_number, 'block') # Loop over blocks: for ii in range(block_number): # Extract a block: index = _block_index_(ii, block_number, projections.shape[1], mode) if len(index) > 0: proj_geom = io.astra_proj_geom(geometry, projections.shape, index) # number of projections in a block can vary a bit and FDK is not aware of that... block = projections[:, index, :] * prj_weight * len(index) # BP and FDK behave differently in terms of normalization: if (algorithm == 'BP3D_CUDA'): block *= block_number block = _contiguous_check_(block) # Backproject: _backproject_block_add_(block, volume, proj_geom, vol_geom, algorithm) _pbar_update_(pbar) _pbar_close_(pbar) # ASTRA is not aware of the number of blocks: volume /= projections.shape[1]
#%% Prepro: proj = (proj - dark) / (flat.mean(0) - dark) proj = -numpy.log(proj) proj = array.raw2astra(proj) display.display_slice(proj, title='Sinogram. What else?') #%% Recon: vol = numpy.zeros([50, 2000, 2000], dtype='float32') # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(meta['geometry'], vol.shape) proj_geom = io.astra_proj_geom(meta['geometry'], proj.shape) # This is ASTRAAA!!! sin_id = astra.data3d.link('-sino', proj_geom, numpy.ascontiguousarray(proj)) vol_id = astra.data3d.link('-vol', vol_geom, numpy.ascontiguousarray(vol)) cfg = astra.astra_dict('FDK_CUDA') cfg['ReconstructionDataId'] = vol_id cfg['ProjectionDataId'] = sin_id alg_id = astra.algorithm.create(cfg) astra.algorithm.run(alg_id, 1) astra.algorithm.delete(alg_id) astra.data3d.delete(sin_id) astra.data3d.delete(vol_id)
def EM_step(projections, volume, geometry): """ A single Expecrtation Maximization step. Supports blocking and subsets. """ global settings norm_update = settings['norm_update'] block_number = settings['block_number'] bounds = settings['bounds'] mode = settings['mode'] prj_weight = _astra_norm_(projections, volume, geometry, 'BP3D_CUDA') * 2 # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, volume.shape) # Norm update: norm = 0 for ii in range(block_number): # Create index slice to address projections: index = _block_index_(ii, block_number, projections.shape[1], mode) if index is []: break # Extract a block: proj_geom = io.astra_proj_geom(geometry, projections.shape, index=index) # Copy data to a block or simply pass a pointer to data itself if block is one. if (mode == 'sequential') & (block_number == 1): block = projections else: block = (projections[:, index, :]).copy() # Reserve memory for a forward projection (keep it separate): resid = _contiguous_check_(numpy.zeros_like(block)) # Forwardproject: _forwardproject_block_add_(resid, volume, proj_geom, vol_geom) # Compute residual: resid[resid < resid.max() / 100] = numpy.inf resid = (block / resid) # L2 norm (use the last block to update): if norm_update: res_pos = resid[resid > 0] norm += res_pos.std() / res_pos.mean() # Project _backproject_block_mult_(resid * prj_weight * block_number, volume, proj_geom, vol_geom, 'BP3D_CUDA') if norm_update: settings['norm'].append(norm / block_number) # Apply bounds if bounds: numpy.clip(volume, a_min=bounds[0], a_max=bounds[1], out=volume)
def FISTA_step(projections, vol, vol_old, vol_t, t, geometry): """ A single FISTA step. Supports blocking and subsets. """ global settings norm_update = settings['norm_update'] block_number = settings['block_number'] bounds = settings['bounds'] poisson_weight = settings['poisson_weight'] mode = settings['mode'] prj_weight = _astra_norm_(projections, vol, geometry, 'BP3D_CUDA') # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, vol.shape) vol_old[:] = vol.copy() t_old = t t = (1 + numpy.sqrt(1 + 4 * t**2)) / 2 vol[:] = vol_t.copy() norm = 0 for ii in range(block_number): # Create index slice to address projections: index = _block_index_(ii, block_number, projections.shape[1], mode) if index is []: break # Extract a block: proj_geom = io.astra_proj_geom(geometry, projections.shape, index=index) # Copy data to a block or simply pass a pointer to data itself if block is one. if (mode == 'sequential') & (block_number == 1): block = projections.copy() else: block = (projections[:, index, :]).copy() block = numpy.ascontiguousarray(block) # Forwardproject: _forwardproject_block_add_(block, vol_t, proj_geom, vol_geom, negative=True) # Take into account Poisson: if poisson_weight: # Some formula representing the effect of photon starvation... block *= numpy.exp(-projections[:, index, :]) # Normalization of the backprojection (depends on ASTRA): block *= prj_weight * block_number # Apply ramp to reduce boundary effects: #block = block = flexData.ramp(block, 2, 5, mode = 'linear') #block = block = flexData.ramp(block, 0, 5, mode = 'linear') # L2 norm (use the last block to update): if norm_update: norm += numpy.sqrt((block**2).mean()) # Project _backproject_block_add_(block, vol, proj_geom, vol_geom, 'BP3D_CUDA') vol_t[:] = vol + ((t_old - 1) / t) * (vol - vol_old) if norm_update: settings['norm'].append(norm / block_number) # Apply bounds if bounds is not None: numpy.clip(vol, a_min=bounds[0], a_max=bounds[1], out=vol)
def L2_step(projections, volume, geometry): """ A single L2 minimization step. Supports blocking and subsets. """ global settings norm_update = settings['norm_update'] block_number = settings['block_number'] bounds = settings['bounds'] poisson_weight = settings['poisson_weight'] mode = settings['mode'] prj_weight = _astra_norm_(projections, volume, geometry, 'BP3D_CUDA') # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, volume.shape) norm = 0 for ii in range(block_number): # Create index slice to address projections: index = _block_index_(ii, block_number, projections.shape[1], mode) if index is []: break # Extract a block: proj_geom = io.astra_proj_geom(geometry, projections.shape, index=index) # The block will contain the discrepancy eventually (that's why we need a copy): if (mode == 'sequential') & (block_number == 1): block = projections.copy() else: block = (projections[:, index, :]).copy() block = _contiguous_check_(block) # Forwardproject: _forwardproject_block_add_(block, volume, proj_geom, vol_geom, negative=True) # Take into account Poisson: if poisson_weight: # Some formula representing the effect of photon starvation... block *= numpy.exp(-projections[:, index, :]) block *= prj_weight * block_number # Apply ramp to reduce boundary effects: #block = array.ramp(block, 0, 5, mode = 'linear') #block = array.ramp(block, 2, 5, mode = 'linear') # L2 norm (use the last block to update): if norm_update: norm = numpy.sqrt((block**2).mean()) # Project _backproject_block_add_(block, volume, proj_geom, vol_geom, 'BP3D_CUDA') if norm_update: settings['norm'].append(norm / block_number) # Apply bounds if bounds: numpy.clip(volume, a_min=bounds[0], a_max=bounds[1], out=volume)
def MULTI_PWLS(projections, volume, geometries, iterations=10, student=False, weight_power=1): ''' Penalized Weighted Least Squares based on multiple inputs. ''' #error log: global settings norm = settings['norm'] block_number = settings['block_number'] mode = settings['mode'] norm = [] fac = volume.shape[2] * geometries[0]['img_pixel'] * numpy.sqrt(2) print('PWLS-ing in progress...') sleep(0.5) # Iterations: for ii in tqdm(range(iterations)): # Error: L_mean = 0 #Blocks: for jj in range(block_number): # Volume update: vol_tmp = numpy.zeros_like(volume) bwp_w = numpy.zeros_like(volume) for kk, projs in enumerate(projections): index = _block_index_(jj, block_number, projs.shape[1], mode) proj = numpy.ascontiguousarray(projs[:, index, :]) geom = geometries[kk] proj_geom = io.astra_proj_geom(geom, projs.shape, index=index) vol_geom = io.astra_vol_geom(geom, volume.shape) prj_tmp = numpy.zeros_like(proj) # Compute weights: if student: fwp_w = numpy.ones_like(proj) else: me = proj.max() * weight_power / 5 fwp_w = numpy.exp(-proj * weight_power / me) #fwp_w = scipy.ndimage.morphology.grey_erosion(fwp_w, size=(3,1,3)) _backproject_block_add_(fwp_w, bwp_w, proj_geom, vol_geom, 'BP3D_CUDA') _forwardproject_block_add_(prj_tmp, volume, proj_geom, vol_geom) prj_tmp = (proj - prj_tmp) * fwp_w / fac if student: prj_tmp = _studentst_(prj_tmp, 5) _backproject_block_add_(prj_tmp, vol_tmp, proj_geom, vol_geom, 'BP3D_CUDA') # Mean L for projection L_mean += (prj_tmp**2).mean() eps = bwp_w.max() / 1000 bwp_w[bwp_w < eps] = eps volume += vol_tmp / bwp_w volume[volume < 0] = 0 #print((volume<0).sum()) norm.append(L_mean / block_number / len(projections)) display.plot(numpy.array(norm), semilogy=True)
def forwardproject(projections, volume, geometry): """ Forwardproject using standard ASTRA functionality. If data array is memmap, projection is done in blocks to save RAM. Args: projections : output numpy.array (dtype = float32) with the following dimensions: [vrt, rot, hrz] volume : input numpy.array (dtype = float32) with the following dimensions: [vrt, mag, hrz] geometry : geometry description - one of threee types: 'simple', 'static_offsets', 'linear_offsets' """ global settings block_number = settings['block_number'] mode = settings['mode'] # Check if projections should be subsampled: sam = geometry['vol_sample'] if sum(sam) > 3: volume = volume[sam[0], sam[1], sam[2]] # Non-memmap case is a single block: volume = _contiguous_check_(volume) # Forward project will always use blocks: if block_number == 1: # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, volume.shape) proj_geom = io.astra_proj_geom(geometry, projections.shape) # Progress bar: pbar = _pbar_start_(1) _forwardproject_block_add_(projections, volume, proj_geom, vol_geom) _pbar_update_(pbar) _pbar_close_(pbar) else: # Multi-block: # Initialize ASTRA geometries: vol_geom = io.astra_vol_geom(geometry, volume.shape) # Progress bar: pbar = _pbar_start_(unit='block', total=block_number) # Loop over blocks: for ii in range(block_number): index = _block_index_(ii, block_number, projections.shape[1], mode) if len(index) > 0: # Extract a block: proj_geom = io.astra_proj_geom(geometry, projections.shape, index) block = projections[:, index, :] block = _contiguous_check_(block) # Backproject: _forwardproject_block_add_(block, volume, proj_geom, vol_geom) projections[:, index, :] = block _pbar_update_(pbar) _pbar_close_(pbar)