Beispiel #1
0
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}')
Beispiel #2
0
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()
Beispiel #5
0
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()
Beispiel #6
0
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")
Beispiel #7
0
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()
Beispiel #8
0
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()
Beispiel #9
0
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()
Beispiel #10
0
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()
Beispiel #12
0
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()
Beispiel #13
0
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()
Beispiel #14
0
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')
Beispiel #15
0
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()
Beispiel #16
0
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'))