Table of Contents
- Description
- Getting Started
- Dependencies
- Installation (CMake)
- Installation (Docker)
- Documentation
- Usage
Description
MIDSX is a code system for simulating the propagation of X-rays through a medium. Using EPDL and NIST datasets, it samples photon free paths and interactions to propagate photons through a computational domain of specified dimensions and geometries. Geometries/bodies are defined using the NIfTI-1 Data Format, which are specified in JSON files. To extract results from a simulation, both Volume and Surface tallies with specifiable measurable quantities and geometries are available, along with derived quantities, such as air kerma for HVL measurements.
Getting Started
Dependencies
MIDSX requires the following dependencies to be manually installed:
- CMake 3.10.0 or higher: If you don't have CMake installed, or require a newer version, follow this guide.
- SQLite3 Library: On Linux, the library can be installed using your distribution's package manager. Using apt:
sudo apt install sqlite3 libsqlite3-dev
.
- Python 3.8.x or higher: If not already installed, go here.
- nibabel: To load NifTI files, MIDSX uses the python package nibabel. It can be easily installed with pip:
pip install nibabel
.
- Boost: On Linux, the library can be installed using your distribution's package manager. Using apt:
sudo apt install libboost-all-dev
.
MIDSX additionally uses the following libraries via Git submodules; these do not need to be installed manually:
- Eigen: For data storage and linear algebra.
- pybind11: For use of nibabel in C++ code. Will be used later for MIDSX python bindings.
Installation (CMake)
MIDSX has been shown to work on Ubuntu with GCC. It will likely work on other Linux distributions with GCC or Clang. Support for MacOS is close to being added, but issues with AppleClang's OpenMP implementation and a known bug with Xcode's linker with GCC (see here) are preventing it from being finished. For those who want to build on MacOS, see the Docker section below. \ To install with the command line:
- Clone the repo and enter the directory:
git clone https://github.com/jmeneghini/MIDSX.git
cd MIDSX
- Create and enter the build directory:
- Generate cmake files and install:
cmake ..
sudo make install
- To build all the cpp_simulations (not required) assuming you are still in the build directory:
./../utility_scripts/build_cpp_sims.sh
Installation (Docker)
MIDSX can be built and run in a Docker container. This is currently the only way to build on MacOS. \ \ If you don't have Docker installed, follow the instructions for Docker Desktop (GUI) or for Docker Engine (CLI). Docker has excellent documentation, so if you have any issues, refer to their website. \ \ To pull the Docker image from Docker Hub:
docker pull jmeneghini/midsx:latest
Then, to run the container with an interactive shell:
docker run -it jmeneghini/midsx:latest
The container will have already installed/compiled MIDSX, its dependencies, and the cpp_simulations, so you can start using it right away.
Documentation
The documentation for MIDSX is generated via Doxygen and is hosted with Github Pages. \ \ The preprint for the validation of MIDSX can be found on arXiv.
Usage
To use the library, configure a project with the following CMakeLists.txt structure:
cmake_minimum_required(VERSION 3.10)
project(project_name)
# Links libraries to executable (function is useful for multiple executables)
function(create_executable EXE_NAME SRC_FILE)
add_executable(${EXE_NAME} ${SRC_FILE})
target_link_libraries(${EXE_NAME} PRIVATE ${COMMON_LIBS})
endfunction()
# Finds MIDSX package
find_package(MIDSX REQUIRED)
# Sets common libs that are linked to executable. You can add your own libraries you want to link here.
set(COMMON_LIBS MIDSX::MIDSX)
create_executable(project main.cpp)
A typical MIDSX simulation has the following structure:
- A .json file containing scene information:
{
"dim_space": [
4,
4,
100
],
"background_material_name": "Air, Dry (near sea level)",
"voxel_grids": [
{
"file_path": "path/to/nifti/file.nii",
"origin": [0, 0, 10]
}
]
}
- Note that the
file_path
is relative to the location of the .json file, not the executable.
- Using the .json file, the
ComputationalDomain
object can be initialized: ++
Class which represents the computational domain.
- The
InteractionData
object can either be manually initialized via a list of material names or by calling comp_domain.getInteractionData()
, which generates an InteractionData
object using the materials contained in the ComputationalDomain
object.
- A list of materials available for simulation can be found in the documentation
++
std::vector<std::string> material_names = {"Air, Dry (near sea level)", "Water, Liquid"};
Class which provides access to various simulation significant data.
- Using these two objects, the
PhysicsEngine
can now be initialized:
- To make measurements, tallies must be specified. Various
SurfaceTally
and VolumeTally
objects are available, and these objects must be supplied with a SurfaceQuantityContainer
or VolumeQuanitityContainer
which holds SurfaceQuantity
's and VolumeQuantity
's, respectively, to be measured by the tallies. These containers can be created manually, or predefined containers can be used via SurfaceQuantityContainerFactory
or VolumeQuantityContainerFactory
. Since these tallies must be constructed in an OpenMP parallel region, you must create seperate functions which return a vector of unique pointers to these tallies.
++
std::vector<std::unique_ptr<SurfaceTally>> initializeSurfaceTallies() {
std::vector<std::unique_ptr<SurfaceTally>> tallies = {};
surface_tallies.emplace_back(std::make_unique<DiscSurfaceTally>(
Eigen::Vector3d(2, 2, 100),
Eigen::Vector3d(0, 0, 1),
1.0,
SurfaceQuantityContainerFactory::AllQuantities()));
return tallies;
}
std::vector<std::unique_ptr<VolumeTally>> initializeVolumeTallies() {
std::vector<std::unique_ptr<VolumeTally>> tallies = {};
volume_container.addVectorQuantity(
VectorVolumeQuantity(VectorVolumeQuantityType::EnergyDeposition));
volume_tallies.emplace_back(std::make_unique<AACuboidVolumeTally>(
Eigen::Vector3d(0, 0, 155),
Eigen::Vector3d(39.0, 39.0, 175),
volume_container));
return tallies;
}
Class which represents a vector quantity for a volume tally.
Class which represents a container for volume quantities.
++
std::unique_ptr<EnergySpectrum> spectrum = std::make_unique<MonoenergeticSpectrum>(mono_spectrum);
std::unique_ptr<Directionality> directionality = std::make_unique<BeamDirectionality>(
std::unique_ptr<SourceGeometry> geometry = std::make_unique<PointGeometry>(
PointGeometry(Eigen::Vector3d(0.0, dim_space.y()/2.0, dim_space.z()/2.0)));
PhotonSource source(std::move(spectrum), std::move(directionality), std::move(geometry));
Class which represents a beam directionality.
Class which represents a monoenergetic energy spectrum.
Class which represents a photon source.
Class which represents a point geometry.
- Now, with
PhotonSource
, PhysicsEngine
, and the tally initialization functions, you can use runSimulation
to run the simulation:
++
const int NUM_OF_PHOTONS = 1000000;
initializeVolumeTallies, NUM_OF_PHOTONS);
void runSimulation(PhotonSource &source, PhysicsEngine &physics_engine, std::function< std::vector< std::unique_ptr< SurfaceTally > >()> surface_tally_init, std::function< std::vector< std::unique_ptr< VolumeTally > >()> volume_tally_init, int N_photons, double &run_time= *(new double))
Helper function which runs the particle transport simulation.
- Data can be retrieved from the simulation via
physics_engine.getSurfaceQuantityContainers()
and physics_engine.getVolumeQuantityContainers()
.
- For further info, look at the several examples in the
cpp_simulations
folder. When building these examples, note that the paths in the .cpp
files located in each simulation folder were written with the assumption that the executables will be run from the directory containing the .cpp
file. If you want to run the executable from a different directory, you will need to change the paths in the .cpp
files.