Example for loading a 3D object from an image stack (binary) onto a grid and applying Sussman redistancing
In this example the image stack is read from a binary file. A jupyter notebook that converts tiff-images into -1/+1 binary files can be found here:
image2binary_dolphin.ipynb.
A 3D cartesian grid with same dimensions as image stack is constructed. The grid resolution can be either 1 grid node for each pixel in x and y) or the resolution can be higher/lower as the image stack. This can be achieved by setting the refinement factor to a value of choice in dimension of choice (e.g. to get a isotropic grid). The pixel value is stored in a property of the grid.
The initial, image-derived level set function phi (indicator function) is then converted into a signed distance function by using the Sussman redistancing (see RedistancingSussman.hpp).
When the grid contains phi as a signed distance function, particles can be placed on narrow band around the interface.
Output: print on promt : Iteration, Change, Residual (see: DistFromSol::change, DistFromSol::residual) writes vtk and hdf5 files of: 1.) 3D grid with geometrical object pre-redistancing and post-redistancing (Phi_0 and Phi_SDF, respectively) 2.) particles on narrow band around interface.
Include
These are the header files that we need to include:
Header file containing functions for loading pixel values from 2D image or 3D image stack (volume) st...
Class for getting the narrow band around the interface.
Header file containing functions for creating files and folders.
Class for reinitializing a level-set function into a signed distance function using Sussman redistanc...
Initialization, indices and output folder
Again we start with
- Initializing OpenFPM
- Setting the output path and creating an output folder This time, we also set the input path and name of the binary image stack that we want to load onto the grid. For this example, we provide 1 simple example image stack that has already been converted to a -1 / +1 binary (see jupyter notebook above). Optionally, we can define the grid dimensionality and some indices for better code readability later on.
x:
First dimension
y:
Second dimension
Phi_0_grid:
Index of property that stores the initial level-set-function
Phi_SDF_grid:
Index of property where the redistancing result should be written to
int main(int argc, char* argv[])
{
typedef double phi_type;
openfpm_init(&argc, &argv);
const std::string path_output = cwd + "/output_3D_sphere";
const std::string path_input ="input";
const std::string path_to_zstack = path_input + "/sphere.bin";
const std::string path_to_size = path_input + "/size_sphere.csv";
const unsigned int grid_dim = 3;
const size_t x = 0;
const size_t y = 1;
const size_t z = 2;
const size_t Phi_0_grid = 0;
const size_t Phi_SDF_grid = 1;
const size_t Phi_SDF_vd = 0;
const size_t Phi_grad_vd = 1;
const size_t Phi_magnOfGrad_vd = 2;
static void create_directory_if_not_exist(std::string path, bool silent=0)
Creates a directory if not already existent.
static std::string get_cwd()
Gets the current working directory and returns path as string.
Set refinement factor
If we want that the grid has a different resolution as the image stack, we can set a refinement factor. In the refinement array, we define by which factor the grid resolution should be changed w.r.t. the image stack resolution. This can be useful for example when you want to have an isotropic grid but the underlying image stack is anisotropic (as it often happens for the z-resolution of volumetric microscopy image data).
const phi_type refinement [] = {1.0, 1.0, 1.0};
Get size from image_size.csv
Here we read the stack size (number of pixel values per dimension) from a csv file. Alternatively, you can directly define the stack size as: std::vector<size_t>
stack_size
{pixels
in
x
, pixels
in
y}
. We use this stack size and the refinement factor to set the grid size sz
.
std::vector<int> stack_size =
get_size(path_to_size);
auto & v_cl = create_vcluster();
if (v_cl.rank() == 0)
{
for(std::vector<int>::size_type i = 0; i != stack_size.size(); i++)
{
std::cout << "#Pixel in dimension " << i << " = " << stack_size[i] << std::endl;
std::cout << "original refinement in dimension " << i << " = " << refinement[i] << std::endl;
}
}
const size_t sz[grid_dim] = {(size_t)std::round(stack_size[x] * refinement[x]), (size_t)std::round(stack_size[y] * refinement[y]), (size_t)std::round(stack_size[z] * refinement[z])};
std::vector< int > get_size(const std::string &path_to_file)
Read the number of pixels per dimension from a csv-file in order to create a grid with the same size.
Run Sussman redistancing and get narrow band
Once we have loaded the geometrical object from the 3D stack onto the grid, we can perform Sussman redistancing and get the narrow band the same way as it is explained in detail here: Disk 2D and here: Sphere 3D.
grid_in_type g_dist(sz, box, ghost);
g_dist.setPropNames({"Phi_0", "Phi_SDF"});
init_grid_and_ghost<Phi_0_grid>(g_dist, -1.0);
load_pixel_onto_grid<Phi_0_grid>(g_dist, path_to_zstack, stack_size);
g_dist.write(path_output + "/grid_from_images_initial", FORMAT_BINARY);
redist_options.min_iter = 1e3;
redist_options.max_iter = 1e4;
redist_options.convTolChange.value = 1e-7;
redist_options.convTolChange.check = true;
redist_options.convTolResidual.value = 1e-6;
redist_options.convTolResidual.check = false;
redist_options.interval_check_convergence = 1e3;
redist_options.width_NB_in_grid_points = 10;
redist_options.print_current_iterChangeResidual = true;
redist_options.print_steadyState_iter = true;
redist_options.save_temp_grid = true;
redist_obj.run_redistancing<Phi_0_grid, Phi_SDF_grid>();
g_dist.write(path_output + "/grid_images_post_redistancing", FORMAT_BINARY);
size_t bc[grid_dim] = {NON_PERIODIC, NON_PERIODIC, NON_PERIODIC};
vd_type vd_narrow_band(0, box, bc, ghost_vd);
vd_narrow_band.setPropNames({"Phi_SDF", "Phi_grad", "Phi_magnOfGrad"});
narrowBand.get_narrow_band<Phi_SDF_grid, Phi_SDF_vd, Phi_grad_vd, Phi_magnOfGrad_vd>(g_dist, vd_narrow_band);
vd_narrow_band.write(path_output + "/vd_narrow_band_images", FORMAT_BINARY);
openfpm_finalize();
return 0;
}
This class represent an N-dimensional box.
Class for getting the narrow band around the interface.
Class for reinitializing a level-set function into a signed distance function using Sussman redistanc...
This is a distributed grid.
Structure to bundle options for redistancing.
aggregate of properties, from a list of object if create a struct that follow the OPENFPM native stru...
Full code
int main(int argc, char* argv[])
{
typedef double phi_type;
openfpm_init(&argc, &argv);
const std::string path_output = cwd + "/output_3D_sphere";
const std::string path_input ="input";
const std::string path_to_zstack = path_input + "/sphere.bin";
const std::string path_to_size = path_input + "/size_sphere.csv";
const unsigned int grid_dim = 3;
const size_t x = 0;
const size_t y = 1;
const size_t z = 2;
const size_t Phi_0_grid = 0;
const size_t Phi_SDF_grid = 1;
const size_t Phi_SDF_vd = 0;
const size_t Phi_grad_vd = 1;
const size_t Phi_magnOfGrad_vd = 2;
const phi_type refinement [] = {1.0, 1.0, 1.0};
std::vector<int> stack_size =
get_size(path_to_size);
auto & v_cl = create_vcluster();
if (v_cl.rank() == 0)
{
for(std::vector<int>::size_type i = 0; i != stack_size.size(); i++)
{
std::cout << "#Pixel in dimension " << i << " = " << stack_size[i] << std::endl;
std::cout << "original refinement in dimension " << i << " = " << refinement[i] << std::endl;
}
}
const size_t sz[grid_dim] = {(size_t)std::round(stack_size[x] * refinement[x]), (size_t)std::round(stack_size[y] * refinement[y]), (size_t)std::round(stack_size[z] * refinement[z])};
grid_in_type g_dist(sz, box, ghost);
g_dist.setPropNames({"Phi_0", "Phi_SDF"});
init_grid_and_ghost<Phi_0_grid>(g_dist, -1.0);
load_pixel_onto_grid<Phi_0_grid>(g_dist, path_to_zstack, stack_size);
g_dist.write(path_output + "/grid_from_images_initial", FORMAT_BINARY);
redist_options.min_iter = 1e3;
redist_options.max_iter = 1e4;
redist_options.convTolChange.value = 1e-7;
redist_options.convTolChange.check = true;
redist_options.convTolResidual.value = 1e-6;
redist_options.convTolResidual.check = false;
redist_options.interval_check_convergence = 1e3;
redist_options.width_NB_in_grid_points = 10;
redist_options.print_current_iterChangeResidual = true;
redist_options.print_steadyState_iter = true;
redist_options.save_temp_grid = true;
redist_obj.run_redistancing<Phi_0_grid, Phi_SDF_grid>();
g_dist.write(path_output + "/grid_images_post_redistancing", FORMAT_BINARY);
size_t bc[grid_dim] = {NON_PERIODIC, NON_PERIODIC, NON_PERIODIC};
vd_type vd_narrow_band(0, box, bc, ghost_vd);
vd_narrow_band.setPropNames({"Phi_SDF", "Phi_grad", "Phi_magnOfGrad"});
narrowBand.get_narrow_band<Phi_SDF_grid, Phi_SDF_vd, Phi_grad_vd, Phi_magnOfGrad_vd>(g_dist, vd_narrow_band);
vd_narrow_band.write(path_output + "/vd_narrow_band_images", FORMAT_BINARY);
openfpm_finalize();
return 0;
}