def create_fdk(filename: str): nib_volume = nib.load(pjoin(DATA_DIRS['datasets'], filename)) nib_shape = nib_volume.header.get_data_shape() nib_dims = tuple([float(f) for f in nib_volume.header['pixdim'][1:4]]) nib_volume = nib_volume.get_fdata() print(nib_dims) system = ctl.CTSystem() system.add_component( ctl.FlatPanelDetector( (NUM_DET_PIXELS, NUM_DET_PIXELS), (DET_PIXEL_DIM, DET_PIXEL_DIM), )) system.add_component(ctl.TubularGantry(SDD, SID)) system.add_component(ctl.XrayTube()) setup = ctl.AcquisitionSetup(system, NUM_VIEWS) setup.apply_preparation_protocol(ctl.protocols.AxialScanTrajectory()) ctl_volume = ctl.VoxelVolumeF.from_numpy(nib_volume.transpose()) ctl_volume.set_voxel_size(nib_dims) projector = ctl.ocl.RayCasterProjector() projections = projector.configure_and_project(setup, ctl_volume) rec = ctl.ocl.FDKReconstructor() reco = ctl.VoxelVolumeF(nib_shape, nib_dims) reco.fill(0) rec.configure_and_reconstruct_to(setup, projections, reco) img = nib.Nifti1Image(reco, np.eye(4)) nib.save(img, f'fdk{NUM_VIEWS}/{filename}')
def main(): # create ball phantom made of cortical bone volume = ctl.SpectralVolumeData.ball( 50., 0.5, 1.0, ctl.database.attenuation_model(ctl.database.Composite.Bone_Cortical)) # create a C-arm CT system and a short scan protocol with 10 views system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) setup = ctl.AcquisitionSetup(system, 10) setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(750.0)) # create the pipeline with a ray caster projector pipe = ctl.ProjectionPipeline(ctl.ocl.RayCasterProjector()) # create a SpectralEffectsExtension and set the energy resolution to 7.5 keV spectral_ext = ctl.SpectralEffectsExtension() spectral_ext.set_spectral_sampling_resolution(7.5) # add the spectral effects extension and a Poisson noise extension to the pipeline pipe.append_extension(spectral_ext) pipe.append_extension(ctl.PoissonNoiseExtension()) # pass the acquisition setup and run the simulation projections = pipe.configure_and_project(setup, volume) # show projection #1 proj = projections.view(1).module(0).numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(): # Construct the composite volume already containing the cube volume. volume = ctl.CompositeVolume(ctl.VoxelVolumeF.cube(150, 1.0, 0.02)) # We now construct the two ball volumes. sub_volume_1 = ctl.VoxelVolumeF.ball(10.0, 1.0, 0.05) sub_volume_2 = ctl.VoxelVolumeF.ball(25.0, 1.0, 0.10) # Here, we shift the ball volumes to the desired positions. sub_volume_1.set_volume_offset((0.0, -20.0, 0.0)) sub_volume_2.set_volume_offset((0.0, 30.0, 0.0)) # Now, we add the two balls as sub-volumes to our final volume. volume.add_sub_volume(sub_volume_1) volume.add_sub_volume(sub_volume_2) # First, we need to define an acquisition setup # (with a CT system and the number of views; 10 in this case)) setup = ctl.AcquisitionSetup( ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4)), 10) # We also need to specify the acquisition geometry, here we set a simple short scan trajectory setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(750.0)) # Now, we create our projector, here we simply use the standard pipeline. projector = ctl.StandardPipeline() # Pass the acquisition setup to the projector and create the projections: projector.configure(setup) projections = projector.project_composite(volume) # show projection #0 proj = projections.view(0).module(0).to_numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(): # define volume and acquisition setup (incl. system) volume = ctl.VoxelVolumeF.cube(100, 1.0, 0.02) system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4)) # create a simple short scan setup with 10 views acquisition_setup = ctl.AcquisitionSetup(system, 10) acquisition_setup.apply_preparation_protocol( ctl.protocols.ShortScanTrajectory(750.0)) simple_projector = ctl.ocl.RayCasterProjector() # our simple projector # optional parameter settings for the projector # e.g. simple_projector.settings().ray_sampling = 0.1 # This is what we do without the extension: # simple_projector.configure(acquisition_setup) # projections = simple_projector.project(volume) # Instead we now do the following: extension = ctl.ArealFocalSpotExtension() extension.use(simple_projector) # tell the extension to use the ray caster extension.set_discretization( (5, 5)) # set discretization grid to 5x5 points extension.configure(acquisition_setup) # configure the simulation projections = extension.project( volume) # (compute and) get the final projections # show projection #0 proj = projections.view(0).module(0).to_numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(): # define volume and acquisition setup (incl. system) volume = ctl.VoxelVolumeF.cube(100, 1.0, 0.02) system = ctl.SimpleCTSystem.from_ctsystem(ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4))) # reduce default radiation output of the source component (-> make noise more prominent) system.source().set_milliampere_seconds(0.001) # create a simple short scan setup with 10 views acquisition_setup = ctl.AcquisitionSetup(system, 10) acquisition_setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(750.0)) simple_projector = ctl.ocl.RayCasterProjector() # our simple projector # optional parameter settings for the projector # e.g. simple_projector.settings().ray_sampling = 0.1 # This is what we do without the extension: # projections = simple_projector.configure_and_project(acquisition_setup, volume) # Instead we now do the following: extension = ctl.PoissonNoiseExtension() extension.use(simple_projector) # tell the extension to use the ray caster extension.set_fixed_seed(42) # set discretization grid to 5x5 points # (compute and) get the final projections projections = extension.configure_and_project(acquisition_setup, volume) # show projection #0 proj = projections.view(0).module(0).numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def create_projection_matrices(): # create a tubular system with a flat panel detector system = ctl.CTSystemBuilder.create_from_blueprint(ctl.blueprints.GenericTubularCT()) system.remove_component(system.detectors()[0]) system.add_component(ctl.FlatPanelDetector((640, 480), (1.0, 1.0))) # create an acquisition setup setup = ctl.AcquisitionSetup(system, 10) setup.apply_preparation_protocol(ctl.protocols.AxialScanTrajectory()) # encode the setup and save the projection matrices proj_mats = ctl.GeometryEncoder.encode_full_geometry(setup) nrrd.write(PROJECTION_MATRICES_PATH, proj_mats.numpy().transpose(), header={'encoding': 'raw'}) print("Wrote projection matrices")
def main(): # define volume as a ball filled with attenuation 0.081/mm (approx. bone @ 50 keV) volume = ctl.VoxelVolumeF.ball(50, 0.5, 0.081) # create a spectral volume using the voxel data from volume and # the correct attenuation model (for bone) spectral_vol = ctl.SpectralVolumeData.from_mu_volume( volume, ctl.database.attenuation_model(ctl.database.Composite.Bone_Cortical)) system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4)) acquisition_setup = ctl.AcquisitionSetup(system, 10) acquisition_setup.apply_preparation_protocol( ctl.protocols.ShortScanTrajectory(750.0)) simple_projector = ctl.ocl.RayCasterProjector() # our simple projector # optional parameter settings for the projector # e.g. simple_projector.settings().ray_sampling = 0.1 # This is what we do without the extension: # projections = simple_projector.configure_and_project(acquisition_setup, volume) # print(projections.min(), projections.max()) # output: 0 2.79263 # Instead we now do the following: extension = ctl.SpectralEffectsExtension() extension.use(simple_projector) # tell the extension to use the ray caster extension.set_spectral_sampling_resolution( 10.0) # set the energy resolution extension.configure(acquisition_setup) # configure the simulation # (compute and) get the final projections with and w/o spectral effects spectral_projections = extension.project(spectral_vol) projections = extension.project(volume) # plot differences proj = projections.view(0).module(0).numpy() spectral_proj = spectral_projections.view(0).module(0).numpy() _ = plt.plot(proj[proj.shape[0] // 2]) _ = plt.plot(spectral_proj[proj.shape[0] // 2]) plt.show()
def main(): # create a cylinder as a volume volume = ctl.VoxelVolumeF.cylinder_x(radius=60.0, height=100.0, voxel_size=0.5, fill_value=0.03) # alternatively: # volume = ctl.VoxelVolumeF.from_numpy(np.ones((128, 128, 128))) # volume.set_voxel_size((1.0, 1.0, 1.0)) # use of a predefined system from ctl.blueprints system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) # create an acquisition setup nb_views = 100 my_carm_setup = ctl.AcquisitionSetup(system, nb_views) # add a predefined trajectory to the setup from ctl.protocols source_to_isocenter = 750.0 # mm is the standard unit for length dimensions start_angle = ctl.deg2rad(42.0) # rad is the standard unit for angles my_carm_setup.apply_preparation_protocol( ctl.protocols.WobbleTrajectory(source_to_isocenter, start_angle)) if not my_carm_setup.is_valid(): sys.exit(-1) # configure a projector and project volume my_projector = ctl.ocl.RayCasterProjector( ) # an ideal projector with default settings projections = my_projector.configure_and_project(my_carm_setup, volume) # plot the projections ctl.gui.plot(projections) ctl.gui.show() # show the 20th projection of detector module 0 using numpy proj20 = projections.numpy()[20, 0] # alternatively: proj20 = projections.view(20).module(0).numpy() _ = plt.imshow(proj20, cmap='gray'), plt.show()
def main(): # create a volume to project and reconstruct volume = ctl.VoxelVolumeF.cube(10, 1.0, 0.02) # build a c-arm setup with 90 views and a short scan trajectory system = ctl.CTSystemBuilder.create_from_blueprint(ctl.blueprints.GenericCarmCT()) setup = ctl.AcquisitionSetup(system, 90) setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(500.)) # project the volume projector = ctl.ocl.RayCasterProjector() projections = projector.configure_and_project(setup, volume) # use fdk to reconstruct the volume reconstructor = ctl.ocl.FDKReconstructor() reco = ctl.VoxelVolumeF.cube(50, 1.0, 0.0) reconstructor.configure_and_reconstruct_to(setup, projections, reco) # plot the reconstruction ctl.gui.plot(reco) ctl.gui.show()
def main(): # create a volume to project and reconstruct volume = ctl.VoxelVolumeF.cube(10, 1.0, 0.02) # build a c-arm setup with 90 views and a short scan trajectory system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) setup = ctl.AcquisitionSetup(system, 90) setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(500.)) # project the volume projector = ctl.ocl.RayCasterProjector() projections = projector.configure_and_project(setup, volume) # use art to reconstruct the volume, set some constraints reconstructor = ctl.ARTReconstructor() reconstructor.set_positivity_constraint_enabled(True) reconstructor.set_relaxation_estimation_enabled(True) reconstructor.set_min_relative_projection_error(3e-2) reconstructor.set_max_nb_iterations(20) # use a modified subset generator subset_gen = ctl.DefaultSubsetGenerator() subset_gen.set_order(ctl.DefaultSubsetGenerator.Orthogonal180) reconstructor.set_subset_generator(subset_gen) # reconstruct reco = ctl.VoxelVolumeF.cube(100, 0.5, 0.0) reconstructor.configure_and_reconstruct_to(setup, projections, reco) # output: # "IterativeReconstructor: Terminated due relative projection error below tolerance level # (rel. error: 0.029831 | threshold: 0.03)." print(f'estimated relaxation: {reconstructor.relaxation()}') # plot the reconstruction ctl.gui.plot(reco) ctl.gui.show()
def main(): # define volume and acquisition setup (incl. system) volume = ctl.VoxelVolumeF.cube(100, 1.0, 0.02) system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4)) # set a detector saturation model (operating in extinction domain, clamps values to [0.1, 2.5]) saturation_model = ctl.DetectorSaturationLinearModel(0.1, 2.5) acquisition_setup = ctl.AcquisitionSetup(system, 10) acquisition_setup.apply_preparation_protocol( ctl.protocols.ShortScanTrajectory(750.0)) acquisition_setup.system().detector().set_saturation_model( saturation_model, ctl.AbstractDetector.Extinction) simple_projector = ctl.ocl.RayCasterProjector() # our simple projector # optional parameter settings for the projector # e.g. simple_projector.settings().ray_sampling = 0.1 # This is what we do without the extension: # simple_projector.configure(acquisition_setup) # projections = simple_projector.project(volume) # print(projections.min(), projections.max()) # output: 0 2.79263 # Instead we now do the following: extension = ctl.DetectorSaturationExtension() extension.use(simple_projector) # tell the extension to use the ray caster extension.configure(acquisition_setup) # configure the simulation projections = extension.project( volume) # (compute and) get the final projections print(projections.min(), projections.max()) # output: 0.1 2.5 # show projection #0 proj = projections.view(0).module(0).to_numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(): # create a water ball volume = ctl.SpectralVolumeData.ball( 50., 0.5, 1.0, ctl.database.attenuation_model(ctl.database.Composite.Water)) # create a C-arm CT system and a short scan protocol with 10 views system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) setup = ctl.AcquisitionSetup(system, 10) setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(750.0)) # create the standard pipeline and adjust the desired settings (focal spot & energy resolution) pipe = ctl.StandardPipeline() pipe.enable_areal_focal_spot() pipe.settings_spectral_effects().set_sampling_resolution(5.0) # pass the acquisition setup and run the simulation projections = pipe.configure_and_project(setup, volume) # show projection #1 proj = projections.view(1).module(0).numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(): # Our starting volume remains the same. This is the sub-volume without spectral information. volume = ctl.CompositeVolume(ctl.VoxelVolumeF.cube(150, 1.0, 0.02)) # We now create two balls with spectral information (one representing blood, the other bone). sub_volume_1 = ctl.SpectralVolumeData.ball( 10.0, 1.0, 0.05, ctl.database.attenuation_model(ctl.database.Composite.Blood)) sub_volume_2 = ctl.SpectralVolumeData.ball( 25.0, 1.0, 0.10, ctl.database.attenuation_model(ctl.database.Composite.Bone_Cortical)) # Again, the shift to the desired positions... sub_volume_1.set_volume_offset((0.0, -20.0, 0.0)) sub_volume_2.set_volume_offset((0.0, 30.0, 0.0)) # ... and adding to the final volume. volume.add_sub_volume(sub_volume_1) volume.add_sub_volume(sub_volume_2) # In the projection code, we only change the setting for the # standard pipeline to 'No_Approximation'... setup = ctl.AcquisitionSetup( ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT(ctl.DetectorBinning.Binning4x4)), 10) setup.apply_preparation_protocol(ctl.protocols.ShortScanTrajectory(750.0)) # ... here comes the changed line: projector = ctl.StandardPipeline(ctl.StandardPipeline.No_Approximation) # Pass the acquisition setup to the projector and create the projections: projector.configure(setup) projections = projector.project_composite(volume) # show projection #0 proj = projections.view(0).module(0).to_numpy() _ = plt.imshow(proj, cmap='gray'), plt.show()
def main(path_to_nrrd): # load the nrrd file nrrd_volume, nrrd_header = nrrd.read(path_to_nrrd) if 'spacings' not in nrrd_header or 'units' not in nrrd_header: print( '\'spacings\' or \'units\' not set. Assuming spacing of 1x1x1mm.') nrrd_header['spacings'] = [1.0] * 3 nrrd_header['units'] = ['mm'] * 3 # create the ctl volume volume = ctl.VoxelVolumeF.from_numpy(nrrd_volume) volume.set_voxel_size(tuple(nrrd_header['spacings'])) # use of a predefined system from ctl.blueprints system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) # create an acquisition setup nb_views = 100 setup = ctl.AcquisitionSetup(system, nb_views) # add a predefined trajectory to the setup from ctl.protocols angle_span = ctl.deg2rad(200.0) # rad is the standard unit for angles source_to_isocenter = 750.0 # mm is the standard unit for length dimensions setup.apply_preparation_protocol( ctl.protocols.WobbleTrajectory(angle_span, source_to_isocenter)) assert setup.is_valid() # configure a projector and project volume projector = ctl.ocl.RayCasterProjector( ) # the projector (uses its default settings) projections = projector.configure_and_project(setup, volume) # save to nrrd file (from detector module #0) nrrd.write('projections.nrrd', projections.numpy()[:, 0].transpose()) print('Wrote projections.nrrd')
def main(): # create a volume of size 128x128x128px with a voxel size of 1x1x1mm volume = ctl.VoxelVolumeF((128, 128, 128), (1.0, 1.0, 1.0)) volume.fill(1.0) # alternatively: # volume = ctl.VoxelVolumeF.from_numpy(np.ones((128, 128, 128))) # volume.set_voxel_size((1.0, 1.0, 1.0)) # use of a predefined system from ctl.blueprints system = ctl.CTSystemBuilder.create_from_blueprint( ctl.blueprints.GenericCarmCT()) # create an acquisition setup nb_views = 100 my_carm_setup = ctl.AcquisitionSetup(system, nb_views) # add a predefined trajectory to the setup from ctl.protocols angle_span = np.deg2rad(200.0) # rad is the standard unit for angles source_to_isocenter = 750.0 # mm is the standard unit for length dimensions my_carm_setup.apply_preparation_protocol( ctl.protocols.WobbleTrajectory(angle_span, source_to_isocenter)) if not my_carm_setup.is_valid(): sys.exit(-1) # configure a projector and project volume my_projector = ctl.ocl.RayCasterProjector( ) # the projector (uses its default settings) my_projector.configure(my_carm_setup) # configure the projector projections = my_projector.project(volume) # project # show the 20th projection of detector module 0 proj20 = projections.to_numpy()[20, 0] # alternatively: proj20 = projections.view(20).module(0).to_numpy() _ = plt.imshow(proj20, cmap='gray'), plt.show()
def test_approximal_b_coronal(filename, data_dir, num_views, sdd, sid, num_det_pixels, det_pix_dim, out_dir): nib_volume = nib.load(pjoin(data_dir, filename)) spacings = np.array([float(f) for f in nib_volume.header['pixdim'][1:4]] + [1.]) nib_volume = nib_volume.get_fdata() volume = ctl.VoxelVolumeF.from_numpy(nib_volume.transpose()) volume.set_voxel_size(tuple(spacings)) angle_diff = 360 / num_views dlambda = np.deg2rad(angle_diff) system = ctl.CTSystem() system.add_component( ctl.FlatPanelDetector((num_det_pixels, num_det_pixels), (det_pix_dim, det_pix_dim))) system.add_component(ctl.TubularGantry(sdd, sid)) system.add_component(ctl.XrayTube()) setup = ctl.AcquisitionSetup(system, num_views) setup.apply_preparation_protocol(ctl.protocols.AxialScanTrajectory()) projection_matrices = ctl.GeometryEncoder.encode_full_geometry(setup) projection_matrices = [p[0] for p in projection_matrices] print("create the projections") projector = ctl.ocl.RayCasterProjector() projections = projector.configure_and_project(setup, volume).numpy() projections = torch.from_numpy(projections).float().cuda() print("calculate the derivatives") der_grid = torch.stack(torch.meshgrid(torch.arange(num_det_pixels), torch.arange(num_det_pixels)), dim=-1) + .5 der_grid = der_grid.double() projections_der = [ compute_g_prime_absolute(projections[i][0], projections[(i - 1) % num_views][0], projections[(i + 1) % num_views][0], projection_matrices[i], projection_matrices[(i - 1) % num_views], projection_matrices[(i + 1) % num_views], der_grid, 2e-3).transpose(0, 1).cpu().numpy() for i in range(num_views) ] gfs = projections_der gfs = [torch.from_numpy(gf).cuda() for gf in gfs] gfs_and_pmats = list(zip(gfs, projection_matrices)) b_meshgrid = torch.meshgrid( (torch.arange( nib_volume.shape[1], dtype=torch.float, device=gfs[0].device) - nib_volume.shape[1] / 2) * spacings[1], (torch.arange( nib_volume.shape[2], dtype=torch.float, device=gfs[0].device) - nib_volume.shape[2] / 2) * spacings[2], ) hilbert_volume = torch.zeros(*nib_volume.shape, 2, dtype=torch.float, device=gfs[0].device) for y_idx in range(nib_volume.shape[1]): print(y_idx) s = (y_idx - nib_volume.shape[1] / 2) * spacings[1] angle = np.rad2deg(np.arcsin(s / sid)) idx_angle = int(angle // angle_diff) idx_180 = int((180 - angle) // angle_diff) if angle >= 0: b_hat = approximal_b_coronal(s, dlambda, gfs_and_pmats[idx_angle:idx_180 + 1], b_meshgrid) b_hat_rev = approximal_b_coronal( s, dlambda, gfs_and_pmats[idx_180:] + gfs_and_pmats[:idx_angle], b_meshgrid, ) else: b_hat = approximal_b_coronal( s, dlambda, gfs_and_pmats[idx_angle:] + gfs_and_pmats[:idx_180 + 1], b_meshgrid, ) b_hat_rev = approximal_b_coronal( s, dlambda, gfs_and_pmats[idx_180:idx_angle], b_meshgrid, ) hilbert_volume[:, y_idx, :, 0] = b_hat hilbert_volume[:, y_idx, :, 1] = b_hat_rev hdr = nib.Nifti1Header() hdr.set_xyzt_units(xyz=2) # mm img = nib.Nifti1Image(hilbert_volume.cpu().numpy(), np.diag(spacings), header=hdr) nib.save(img, pjoin(out_dir, f'{filename[:-7]}_coronal.nii.gz'))