diff --git a/README.md b/README.md index 8249404..cc21b74 100644 --- a/README.md +++ b/README.md @@ -81,59 +81,69 @@ These final equations are the ones used in the function **compute_bz**, which ca ## Installation -First, clone the repository +- Clone the repository ``` git clone https://github.com/shimming-toolbox/susceptibility-to-fieldmap-fft.git +cd susceptibility-to-fieldmap-fft ``` -Navigate to the project directory +- Create a virtual environnement ``` -cd susceptibility-to-fieldmap-fft +conda create --name python=3.9 +conda activate ``` -Install the requirements +- Install the package ``` -pip install -r requirements.txt +pip install . ``` +You will need to ```conda activate ``` each time you want to use the package. + ## Usage -To execute the scripts, you need to naviagte into the fft_simulation folder. +Once the package is installed, the commands can be run directly from the terminal. Here is the description of the two commands available. -### Analytical cases +### compute_fieldmap -The _analytical_case.py_ script allows for comparaison between simulated and analytical results for a spherical and cylindrical phantom. +The `compute_fieldmap` command allows computation of a $B_0$ fieldmap based on a susceptibility distribution given as an input. -**Arguments** -- -t, type : 'spherical' or 'cylindrical' -- -b, buffer (optional, default=2): Buffer value for zero-padding around the phantom +**Inputs** +- input_file : path to the susceptibility distribution (NIfTI file) +- output_file : path for the fieldmap (NIfTI file) -**Return** -Plots to visialize the results +**Output** +The calculated fieldmap at the specified path. Example: ``` -python analytical_cases.py -t "spherical" +compute_fieldmap "inpath/susceptibility_distribution.nii.gz" "outpath/fieldmap.nii.gz" ``` +### analytical_cases -### fft_simulation - -The _fft_simulation.py_ script allow computation of a $B_0$ fieldmap based on a susceptibility distribution given as an input. +The _analytical_cases_ command allows for comparaison between simulated and analytical results for a spherical and cylindrical phantom. -**Arguments** -- -input_file : path to the susceptibility distribution (NIfTI file) -- -output_file : path for the fieldmap (NIfTI file) +**Inputs** +- -t, geometry type : 'spherical' or 'cylindrical' +- -b, buffer (optional, default=2): Buffer value for zero-padding around the phantom -**Return** -The calculated fieldmap at the specified path. +**Outputs** +Plots to visualize the results Example: ``` -python fft_simulation.py "\your\sus_dist\path.nii" "\yout\fieldmap\path.nii" +analytical_cases -t "spherical" ``` +The figures generated would be + +![Figure_1](https://github.com/user-attachments/assets/a2f49a83-dc8f-4d94-a5ba-075e7b8675aa) + + +![Figure_2](https://github.com/user-attachments/assets/c4c2d34c-7153-43d8-ad21-8af6f49427de) + ## References diff --git a/fft_simulation/analytical_cases.py b/fft_simulation/analytical_cases.py index a2a1bb0..705301c 100644 --- a/fft_simulation/analytical_cases.py +++ b/fft_simulation/analytical_cases.py @@ -1,11 +1,132 @@ -from fft_simulation import compute_bz +from fft_simulation.fft_simulation import compute_bz import numpy as np from matplotlib import pyplot as plt from scipy.ndimage import rotate -import argparse import click -class Spherical: +class Visualization: + """ + A class that provides visualization methods for susceptibility distribution + and Bz field variation. + """ + + def plot_susceptibility_and_fieldmap(self, sus_dist, simulated_Bz, geometry_type): + """ + Plot the susceptibility distribution and Bz field variation for a given geometry type. + + Parameters: + sus_dist (ndarray): Array representing the susceptibility distribution. + simulated_Bz (ndarray): Array representing the Bz field variation. + geometry_type (str): Type of geometry. + + Returns: + None + """ + + dimensions_1 = np.array(sus_dist.shape) + dimensions_2 = np.array(simulated_Bz.shape) + + fig, axes = plt.subplots(2, 3, figsize=(10, 5), dpi=120) + fig.suptitle(f'Susceptibility distribution (top) and Bz field variation (bottom) for a {geometry_type} geometry') + + h = axes[0,0].imshow(sus_dist[dimensions_1[0] // 2, :, :], origin='lower') + axes[0,0].set_title('Y-Z plane') + axes[0,0].axis("off") + plt.colorbar(h, label='Susceptibility [ppm]') + + h = axes[0,1].imshow(sus_dist[:, dimensions_1[0] // 2, :], origin='lower') + axes[0,1].set_title('Z-X plane') + axes[0,1].axis("off") + plt.colorbar(h, label='Susceptibility [ppm]') + + h = axes[0,2].imshow(sus_dist[:, :, dimensions_1[0] // 2], origin='lower') + axes[0,2].set_title('X-Y plane') + axes[0,2].axis("off") + plt.colorbar(h, label='Susceptibility [ppm]') + + # plot section of the b0 field variation + + vmin = np.min(simulated_Bz)*1.1 + vmax = np.max(simulated_Bz)*1.1 + + h = axes[1,0].imshow(simulated_Bz[dimensions_2[0] // 2, :, :], vmin=vmin, vmax=vmax, origin='lower') + axes[1,0].set_title('Y-Z plane') + axes[1,0].axis("off") + plt.colorbar(h, label='Bz [T]') + + h = axes[1,1].imshow(simulated_Bz[:, dimensions_2[0] // 2, :], vmin=vmin, vmax=vmax, origin='lower') + axes[1,1].set_title('Z-X plane') + axes[1,1].axis("off") + plt.colorbar(h, label='Bz [T]') + + h = axes[1,2].imshow(simulated_Bz[:, :, dimensions_2[0] // 2], vmin=vmin, vmax=vmax, origin='lower') + axes[1,2].set_title('X-Y plane') + axes[1,2].axis("off") + plt.colorbar(h, label='Bz [T]') + + plt.show() + + def plot_comparaison_analytical(self, Bz_analytical, simulated_Bz, geometry_type): + """ + Plot the analytical solution and simulated results for the Bz field variation. + + Parameters: + Bz_analytical (ndarray): Array representing the analytical solution for the Bz field variation. + simulated_Bz (ndarray): Array representing the simulated Bz field variation. + geometry_type (str): Type of geometry. + + Returns: + None + """ + vmin = np.min(simulated_Bz)*1.1 + vmax = np.max(simulated_Bz)*1.1 + + dimensions = np.array(Bz_analytical.shape) + + fig, axes = plt.subplots(1, 3, figsize=(10, 3), dpi=120) + fig.suptitle(f'Analytical solution and simulated results for the Bz field variation for a {geometry_type} geometry') + + axes[0].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), Bz_analytical[:, dimensions[0]//2, dimensions[0]//2], label='Theory') + axes[0].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), simulated_Bz[:, dimensions[0]//2, dimensions[0]//2],'--', label='Simulated') + axes[0].set_xlabel('x position [mm]') + axes[0].set_ylabel('Field variation [ppm]') + axes[0].set_ylim(vmin, vmax) + axes[0].legend() + + axes[1].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), Bz_analytical[dimensions[0]//2, :, dimensions[0]//2], label='Theory') + axes[1].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), simulated_Bz[dimensions[0]//2, :, dimensions[0]//2],'--', label='Simulated') + axes[1].set_xlabel('y position [mm]') + axes[1].set_ylabel('Field variation [ppm]') + axes[1].set_ylim(vmin, vmax) + axes[1].legend() + + axes[2].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), Bz_analytical[dimensions[0]//2, dimensions[0]//2, :], label='Theory') + axes[2].plot(np.linspace(-dimensions[0]//2, dimensions[0]//2, dimensions[0]), simulated_Bz[dimensions[0]//2, dimensions[0]//2, :],'--', label='Simulated') + axes[2].set_xlabel('z position [mm]') + axes[2].set_ylabel('Field variation [ppm]') + axes[2].set_ylim(vmin, vmax) + axes[2].legend() + + plt.tight_layout() + plt.show() + +class Spherical(Visualization): + """ + Represents a spherical object in 3D space. + + Attributes: + - matrix (ndarray): The dimensions of the matrix representing the object (i.e. [128, 128, 128]). + - image_res (ndarray): The resolution of the image in mm (i.e. [1, 1, 1] mm). + - R (int): The radius of the sphere in mm. + - sus_diff (float): The susceptibility difference of the sphere + (sus_diff = susceptibility_in_the_sphere - susceptibility_outside). + + Methods: + - mask(): Generates a mask representing the spherical object. + - volume(): Create a volume of the sphere with the corresponding suceptibility value. + - analyticial_sol(): Calculates the analytical solution for the magnetic field inside and outside the sphere. + """ + def __init__(self, matrix, image_res, R, sus_diff): self.matrix = matrix self.image_res = image_res @@ -13,6 +134,12 @@ def __init__(self, matrix, image_res, R, sus_diff): self.sus_diff = sus_diff def mask(self): + """ + Generates a mask representing the spherical object. + + Returns: + - mask (ndarray): A boolean array representing the mask. + """ [x, y, z] = np.meshgrid(np.linspace(-(self.matrix[0]-1)/2, (self.matrix[0]-1)/2, self.matrix[0]), np.linspace(-(self.matrix[1]-1)/2, (self.matrix[1]-1)/2, self.matrix[1]), np.linspace(-(self.matrix[2]-1)/2, (self.matrix[2]-1)/2, self.matrix[2])) @@ -22,9 +149,21 @@ def mask(self): return r**2 < self.R**2 def volume(self): + """ + Create a volume of the sphere with the corresponding suceptibility value. + + Returns: + - volume (ndarray): A 3D array representing the distribution of suceptibility. + """ return np.where(self.mask() == True, self.sus_diff, 0) - def analyticial_sol(self): + def analytical_sol(self): + """ + Calculates the analytical solution for the magnetic field inside and outside the sphere. + + Returns: + - Bz_analytical (ndarray): A 3D array representing the analytical solution for the magnetic field. + """ mask = self.mask() [x, y, z] = np.meshgrid(np.linspace(-(self.matrix[0]-1)/2, (self.matrix[0]-1)/2, self.matrix[0]), @@ -34,19 +173,30 @@ def analyticial_sol(self): r = np.sqrt(x**2 + y**2 + z**2) - Bz_out = self.sus_diff/3 * (self.R/r)**3 * (3*z**2/r**2 - 1) - Bz_in = np.zeros(self.matrix) + Bz_analytical = self.sus_diff/3 * (self.R/r)**3 * (3*z**2/r**2 - 1) + Bz_analytical[mask] = 0 # set the field inside the sphere to zero + return Bz_analytical + - Bz_in = np.where(mask, Bz_in, 0) - Bz_out = np.where(~mask, Bz_out, 0) +class Cylindrical(Visualization): + """ + Represents a cylindrical object in 3D space. - Bz_analytical = Bz_out + Bz_in + Parameters: + - matrix (ndarray): The dimensions of the matrix representing the object (i.e. [128, 128, 128]). + - image_res (ndarray): The resolution of the image in mm (i.e. [1, 1, 1] mm). + - R (int): The radius of the cylinder in mm. + - sus_diff (float): The susceptibility difference of the cylinder + (sus_diff = susceptibility_in_the_cylinder - susceptibility_outside). + - theta (float, optional): The rotation angle about the y-axis. Default is pi/2. - return Bz_analytical - + Methods: + - mask(): Generates a mask representing the cylinder. + - volume(): Create a volume of the sphere with the corresponding suceptibility value. + - analytical_sol(): Calculates the analytical solution for the magnetic field inside and outside the cylinder. + """ -class Cylindrical: def __init__(self, matrix, image_res, R, sus_diff, theta=np.pi/2): self.matrix = matrix self.image_res = image_res @@ -55,6 +205,12 @@ def __init__(self, matrix, image_res, R, sus_diff, theta=np.pi/2): self.theta = theta # rotation angle about the y-axis def mask(self): + """ + Generates a mask representing the cylinder. + + Returns: + - mask (ndarray): The mask representing the cylinder. + """ [x, y, z] = np.meshgrid(np.linspace(-(self.matrix[0]-1)/2, (self.matrix[0]-1)/2, self.matrix[0]), np.linspace(-(self.matrix[1]-1)/2, (self.matrix[1]-1)/2, self.matrix[1]), np.linspace(-(self.matrix[2]-1)/2, (self.matrix[2]-1)/2, self.matrix[2])) @@ -67,10 +223,25 @@ def mask(self): return rotate(mask, self.theta*180/np.pi, axes=(0, 2), reshape=False, order=1) def volume(self): - return np.where(self.mask() == True, self.sus_diff, 0) + """ + Create a volume of the sphere with the corresponding suceptibility value. + + Returns: + - volume (ndarray): A 3D array representing the distribution of suceptibility. + """ + return np.where(self.mask(), self.sus_diff, 0) -# TODO: For now the analytical solution is only correct for theta = 90 def analytical_sol(self): + """ + Calculates the analytical solution for the magnetic field inside and outside the cylinder. + + Returns: + - Bz_analytical_x (ndarray): The analytical solution for the magnetic field along the x-axis. + - Bz_analytical_y (ndarray): The analytical solution for the magnetic field along the y-axis. + """ + + mask = self.mask() + phi_x = 0 phi_y = np.pi/2 @@ -82,176 +253,71 @@ def analytical_sol(self): # solution along the x-axis: phi = 0 Bz_out_x = self.sus_diff/2 * (self.R/r)**2 * np.sin(self.theta)**2 * np.cos(2*phi_x) - Bz_out_x = np.where(~self.mask(), Bz_out_x, 0) + Bz_out_x[mask] = 0 # solution along the x-axis: phi = 90 Bz_out_y = self.sus_diff/2 * (self.R/r)**2 * np.sin(self.theta)**2 * np.cos(2*phi_y) - Bz_out_y = np.where(~self.mask(), Bz_out_y, 0) + Bz_out_y[mask] = 0 Bz_in = np.zeros(self.matrix) + self.sus_diff/6 * (3*np.cos(self.theta) - 1) - Bz_in = np.where(self.mask(), Bz_in, 0) + Bz_in[~mask] = 0 Bz_analytical_x = Bz_out_x + Bz_in Bz_analytical_y = Bz_out_y + Bz_in - return Bz_analytical_x, Bz_analytical_y - - -def main(geometry_type, buffer): - matrix = np.array([128,128,128]) - image_res = np.array([1,1,1]) # mm - R = 15 # mm - sus_diff = 9 # ppm - - if geometry_type == 'spherical': - # create the susceptibility geometry - sphere = Spherical(matrix, image_res, R, sus_diff) - sus_dist = sphere.volume() - # compute Bz variation - calculated_Bz = compute_bz(sus_dist, image_res, buffer) - # analytical solution - Bz_analytical = sphere.analyticial_sol() - - # plot sections of the chi distribution - fig, axes = plt.subplots(2, 3, figsize=(10, 5), dpi=120) - - h = axes[0,0].imshow(sus_dist[matrix[0] // 2, :, :], origin='lower') - axes[0,0].set_title('Slice along y-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - h = axes[0,1].imshow(sus_dist[:, matrix[0] // 2, :], origin='lower') - axes[0,1].set_title('Slice along x-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - h = axes[0,2].imshow(sus_dist[:, :, matrix[0] // 2], origin='lower') - axes[0,2].set_title('Slice along y-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - # plot section of the b0 field variation + # Create a single 3D array for the analytical solution. Only the lines along the cartesian axes are filled + Bz_analytical = np.zeros(self.matrix) + Bz_analytical[:, self.matrix[0] //2, self.matrix[0] //2] = Bz_analytical_x[:, self.matrix[0] //2, self.matrix[0] //2] + Bz_analytical[self.matrix[0] //2, :, self.matrix[0] //2] = Bz_analytical_y[self.matrix[0] //2, :, self.matrix[0] //2] + Bz_analytical[self.matrix[0] //2, self.matrix[0] //2, :] = Bz_analytical_x[self.matrix[0] //2, self.matrix[0] //2, :] - vmin = np.min(calculated_Bz)*1.1 - vmax = np.max(calculated_Bz)*1.1 - - h = axes[1,0].imshow(calculated_Bz[63, :, :],vmin=vmin, vmax=vmax, origin='lower') - axes[0,0].set_title('Y-Z plane') - plt.colorbar(h, label='Field variation [ppm]') - - h = axes[1,1].imshow(calculated_Bz[:, 63, :],vmin=vmin, vmax=vmax, origin='lower') - axes[0,1].set_title('X-Z plane') - plt.colorbar(h, label='Field variation [ppm]') - - h = axes[1,2].imshow(calculated_Bz[:, :, 63],vmin=vmin, vmax=vmax, origin='lower') - axes[0,2].set_title('X-Y plane') - plt.colorbar(h, label='Field variation [ppm]') - plt.tight_layout() - plt.show() - - - fig, axes = plt.subplots(1, 3, figsize=(10, 3), dpi=120) - - axes[0].plot(np.linspace(-64,64, 128), Bz_analytical[:,63,63], label='Theory') - axes[0].plot(np.linspace(-64,64, 128), calculated_Bz[:,63,63],'--', label='Simulated') - axes[0].set_ylim(vmin, vmax) - axes[0].set_xlabel('x position [mm]') - axes[0].set_ylabel('Field variation [ppm]') - axes[0].legend() - - axes[1].plot(np.linspace(-64,64, 128), Bz_analytical[63,:,63], label='Theory') - axes[1].plot(np.linspace(-64,64, 128), calculated_Bz[63,:,63],'--', label='Simulated') - axes[1].set_ylim(vmin, vmax) - axes[1].set_xlabel('y position [mm]') - axes[1].set_ylabel('Field variation [ppm]') - axes[1].legend() - - axes[2].plot(np.linspace(-64,64, 128), Bz_analytical[63,63,:], label='Theory') - axes[2].plot(np.linspace(-64,64, 128), calculated_Bz[63,63,:],'--', label='Simulated') - axes[2].set_ylim(vmin, vmax) - axes[2].set_xlabel('z position [mm]') - axes[2].set_ylabel('Field variation [ppm]') - axes[2].legend() - - plt.tight_layout() - plt.show() - - else: # type is "cylindrical" - - # create the susceptibility geometry - cylinder = Cylindrical(matrix, image_res, R, sus_diff) - sus_dist = cylinder.volume() - - # compute Bz variation - calculated_Bz = compute_bz(sus_dist, image_res, buffer) - - Bz_analytical_x, Bz_analytical_y = cylinder.analytical_sol() - - # plot sections of the chi distribution - fig, axes = plt.subplots(2, 3, figsize=(10, 5), dpi=120) - - h = axes[0,0].imshow(sus_dist[matrix[0] // 2, :, :], origin='lower') - axes[0,0].set_title('Slice along y-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - h = axes[0,1].imshow(sus_dist[:, matrix[0] // 2, :], origin='lower') - axes[0,1].set_title('Slice along x-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - h = axes[0,2].imshow(sus_dist[:, :, matrix[0] // 2], origin='lower') - axes[0,2].set_title('Slice along y-z plane') - plt.colorbar(h, label='Susceptibility [ppm]') - - # plot section of the b0 field variation - - vmin = np.min(calculated_Bz)*1.1 - vmax = np.max(calculated_Bz)*1.1 - - h = axes[1,0].imshow(calculated_Bz[63, :, :],vmin=vmin, vmax=vmax, origin='lower') - axes[0,0].set_title('Y-Z plane') - plt.colorbar(h, label='Field variation [ppm]') + return Bz_analytical + +@click.command(help="Compare the analytical solution to the simulated solution for a spherical or cylindrical geometry.") +@click.option('-t', '--geometry-type',required=True, + type=click.Choice(['spherical', 'cylindrical']), + help='Type of geometry for the simulation') +@click.option('-b', '--buffer', default=2, + help='Buffer value for zero-padding.') +def compare_to_analytical(geometry_type, buffer): + """ + Main function to compare simulated fields to analytical solutions. - h = axes[1,1].imshow(calculated_Bz[:, 63, :],vmin=vmin, vmax=vmax, origin='lower') - axes[0,1].set_title('X-Z plane') - plt.colorbar(h, label='Field variation [ppm]') + Parameters: + - geometry_type (str): The type of geometry to simulate ('spherical' or 'cylindrical'). + - buffer (float): The buffer size for the simulation. - h = axes[1,2].imshow(calculated_Bz[:, :, 63],vmin=vmin, vmax=vmax, origin='lower') - axes[0,2].set_title('X-Y plane') - plt.colorbar(h, label='Field variation [ppm]') - plt.tight_layout() - plt.show() + Returns: + - None - fig, axes = plt.subplots(1, 3, figsize=(10, 3), dpi=120) + This function performs the following steps: + 1. Initializes the necessary variables and parameters. + 2. Creates the susceptibility geometry based on the specified geometry type. + 3. Computes the Bz variation using the computed susceptibility distribution and buffer size. + 4. Plots sections of the susceptibility distribution and Bz field variation. + 5. Plots the analytical solution and simulated results for the Bz field variation. - axes[0].plot(np.linspace(-64,64, 128), Bz_analytical_x[:,63,63], label='Theory') - axes[0].plot(np.linspace(-64,64, 128), calculated_Bz[:,63,63],'--', label='Simulated') - axes[0].set_ylim(vmin, vmax) - axes[0].set_xlabel('x position [mm]') - axes[0].set_ylabel('Field variation [ppm]') - axes[0].legend() + Note: The function uses a matrix size of [128, 128, 128], an image resolution of [1, 1, 1] mm, a radius of 15 mm + and a susceptibility difference of 9 ppm for the spherical and cylindrical geometries. + """ - axes[1].plot(np.linspace(-64,64, 128), Bz_analytical_y[63,:,63], label='Theory') - axes[1].plot(np.linspace(-64,64, 128), calculated_Bz[63,:,63],'--', label='Simulated') - axes[1].set_ylim(vmin, vmax) - axes[1].set_xlabel('y position [mm]') - axes[1].set_ylabel('Field variation [ppm]') - axes[1].legend() + matrix = np.array([128,128,128]) + image_res = np.array([1,1,1]) # mm + R = 15 # mm + sus_diff = 9 # ppm - axes[2].plot(np.linspace(-64,64, 128), Bz_analytical_x[63,63,:], label='Theory') - axes[2].plot(np.linspace(-64,64, 128), calculated_Bz[63,63,:],'--', label='Simulated') - axes[2].set_ylim(vmin, vmax) - axes[2].set_xlabel('z position [mm]') - axes[2].set_ylabel('Field variation [ppm]') - axes[2].legend() + dicto = {'spherical': Spherical(matrix, image_res, R, sus_diff), + 'cylindrical': Cylindrical(matrix, image_res, R, sus_diff)} - plt.tight_layout() - plt.show() + # create the susceptibility geometry + geometry = dicto[geometry_type] + sus_dist = geometry.volume() -@click.command(help="Compare the analytical solution to the simulated solution for a spherical or cylindrical geometry.") -@click.option('-t', '--geometry-type',required=True, - type=click.Choice(['spherical', 'cylindrical']), - help='Type of geometry for the simulation') -@click.option('-b', '--buffer', default=2, - help='Buffer value for zero-padding.') -def cli(geometry_type, buffer): - main(geometry_type, buffer) + # compute Bz variation + calculated_Bz = compute_bz(sus_dist, image_res, buffer) + # analytical solution + Bz_analytical = geometry.analytical_sol() -if __name__ == "__main__": - cli() \ No newline at end of file + # plot the results + geometry.plot_susceptibility_and_fieldmap(sus_dist, calculated_Bz, geometry_type) + geometry.plot_comparaison_analytical(Bz_analytical, calculated_Bz, geometry_type) diff --git a/fft_simulation/fft_simulation.py b/fft_simulation/fft_simulation.py index ab5617d..a452cf9 100644 --- a/fft_simulation/fft_simulation.py +++ b/fft_simulation/fft_simulation.py @@ -12,7 +12,7 @@ def is_nifti(filepath): Returns: bool: True if the file is a NIfTI file, False otherwise. """ - if filepath[-4:] == '.nii' or filepath[-7:] == '.nii.gz': + if filepath.endswith('.nii') or filepath.endswith('.nii.gz'): return True else: return False @@ -102,31 +102,21 @@ def save_to_nifti(data, image_resolution, output_path): -@click.command(help="Compute the magnetic field Bz in ppm from a susceptibility distribution in NIfTI format.") -@click.argument('input_file', required=True, type=click.Path(exists=True)) -@click.argument('output_file', required=True, type=click.Path()) -def main(input_file, output_file): - # parser = argparse.ArgumentParser() - # parser.add_argument("-i", - # dest="input_file", - # type=str, - # required=True, - # help="Path to the NIfTI file input.") - - # parser.add_argument("-o", - # dest="output_file", - # type=str, - # required=True, - # help="Path to the NIfTI file ouput.") - # args = parser.parse_args() - +@click.command(help="Compute the magnetic field variation in ppm from a susceptibility distribution in NIfTI format.") +@click.option('-i','--input','input_file', type=click.Path(exists=True), required=True, + help="Input susceptibility distribution, supported extensions: .nii, .nii.gz") +@click.option('-o', '--output', 'output_file', type=click.Path(), default='fieldmap.nii.gz', + help="Output fieldmap, supported extensions: .nii, .nii.gz") +def compute_fieldmap(input_file, output_file): """ - This script processes a NIfTI file by performing a simple operation on the voxel data - and saves the modified data to a new NIfTI file. + Main procedure for performing the simulation. - INPUT_FILE: Path to the input susceptibility distribution in NIfTI format. + Args: + input_file (str): Path to the susceptibility distribution in NIfTI format. + output_file (str): Path for the computed fieldmap in NIfTI format. - OUTPUT_FILE: Path to the output fieldmap where the data will be saved in NIfTI format. + Returns: + None """ if is_nifti(input_file): sus_dist, img_res = load_sus_dist(input_file) @@ -135,8 +125,4 @@ def main(input_file, output_file): else: print("The input file must be NIfTI.") - -if __name__ == "__main__": - main() - \ No newline at end of file diff --git a/setup.py b/setup.py index 8f3e113..5a14c9b 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,30 @@ from setuptools import find_packages, setup +from os import path + +# Get the directory where this current file is saved +here = path.abspath(path.dirname(__file__)) + +with open(path.join(here, "README.md"), encoding="utf-8") as f: + long_description = f.read() setup( - name="fft_simulation", - packages=find_packages(), + name="susceptibility-to-fieldmap-fft", + python_requires=">=3.8.8", + description="Code to compute the magnetic field variation from a given susceptibility distribution placed in a constant magnetic field.", + long_description=long_description, + url="https://github.com/shimming-toolbox/susceptibility-to-fieldmap-fft", + entry_points={ + 'console_scripts': [ + "compute_fieldmap=fft_simulation.fft_simulation:compute_fieldmap", + "analytical_cases=fft_simulation.analytical_cases:compare_to_analytical" + ] + }, + packages=find_packages(exclude=["docs"]), + install_requires=[ + "click", + "numpy>=1.24.4", + "nibabel>=5.2.1", + "matplotlib>=3.7.5", + "scipy>=1.10.1" + ], )