8. Simulated X-ray imaging with GVXR#
8.1. Introduction#
X-ray imaging is a common to method used to to perform detailed analyses of the internal structure of an object in non-destructive way. VirtualLab allows users to create realistic simulations of X-Ray images using the software package GVXR. GVXR is a C++ x-ray simulation library developed by Frank Vidal and Iwan Mitchel at Bangor University.
It uses ray-tracing in OpenGL to track the path and attenuation of X-ray beams through a polygon mesh as they travel from an X-Ray source to a detector.
This tutorial will not cover the specifics of how to use GVXR, however training material on this in the form of jupiter notebooks can be found here.
The goal here instead is to show how GVXR can be run as a method inside a container within the VirtualLab workflow. As such we will cover similar examples to the training material but not the detailed theory behind them.
8.2. Prerequisites#
The examples provided here are mostly self-contained. However, in order to understand this tutorial, at a minimum you will need to have completed the first tutorial to obtain a grounding in how VirtualLab is setup.
Also, although not strictly necessary, we also recommend completing the third tutorial because we will be using the Salome mesh generated from the HIVE analysis as part of one of the examples. All the previous tutorials (that is tutorials 2, 4 and 5) are useful but not required if your only interest is the X-Ray imaging features.
We also recommend you have at least some understanding of how to use GVXR as a standalone package and have looked through the GVXR training material as we will be working through very similar examples.
8.3. Example 1: First X-ray Simulations with GVXR#
In this first example we will demonstrate how to simulate a single X-Ray image. We will start with a simple monochromatic point source and an object made from a single element.
Action
The RunFile
RunTutorials.py
should be set up as follows to run this simulation:Simulation='Examples' Project='Dragon' Parameters_Master='TrainingParameters_GVXR-Draig' Parameters_Var=None #=============================================================================== # Environment #=============================================================================== VirtualLab=VLSetup( Simulation, Project ) VirtualLab.Settings( Mode='Interactive', Launcher='Process', NbJobs=1 ) VirtualLab.Parameters( Parameters_Master, Parameters_Var, RunCT_Scan=True, RunCT_Recon=False ) VirtualLab.CT_Scan()A copy of this run file can also be found in
RunFiles/Tutorials/X-ray_imaging/Task1_Run.py
.
The mesh we be using For this example is the Welsh Dragon
Model which was released by Bangor university, UK, for
Eurographics 2011. The mesh, which can be found here, can be downloaded and placed in the directory Output/Examples/Dragon/Meshes
by running the following command
VirtualLab -f RunFiles/Tutorials/X-ray_imaging/Task1_mesh.py
Action
Once you have the mesh downloaded you can launch VirtualLab using the following command:
VirtualLab -f RunFiles/RunTutorials.py
Because we have set Mode='Interactive'
in VirtualLab.Settings
you should see a 3D visualization
of the dragon model in the path of the X-Ray beam casting a shadow onto the X-Ray detector behind, see Fig. 8.1.
You can use the mouse to zoom and rotate the scene to get a better view. Once finished you can close
the window or type q
on the keyboard.
Tip
To prevent this visualization from appearing in future runs simply set Mode to 'Headless'
or 'Terminal'
.
The X-ray image itself can be found in Output/GVXR/Tutorials/GVXR_Images/Dragon/Dragon_1.tiff
, and should look like Fig. 8.2.
Looking though the RunFile The main thing to note is the call to
VirtualLab.CT_Scan()
. This is the function that initiates X-ray
imaging using the parameters defined in Parameters_Master and
Parameters_Var. Additionally, RunCT_Scan is explicitly set to
True
in VirtualLab.Parameters
.
This isn’t technically necessary because the inclusion of
VirtualLab.CT_Scan()
in the methods section means it
is True
by default, but explicitly stating this is good
practice.
The parameters file we used is Input/Examples/Dragon/TrainingParameters_GVXR-Draig.py
you will notice this file has a new Namespace GVXR
.
This contains the parameters used to setup and control the X-Ray Imaging.
The file is setup with some sensible default values.
The GVXR Namespace contains a number of options many of which we will cover in later examples. For the curious a full list of these can be found in the appendix.
For ease of discussion of this first example we will break the required parameters down into four sections:
X-ray Beam parameters
Detector Parameters
Sample Parameters
Misc. Parameters
8.3.1. Setting up the Beam:#
Our first group of parameters concern the properties of the X-Ray Beam (source) GVXR needs to know 3 basic properties to define a source.
The position of the source
The beam shape
The beam energy (spectrum)
To set the position we use GVXR.Beam_PosX
, GVXR.Beam_PosY
and GVXR.Beam_PosZ
the default units are mm. However, you can easily change this to essentially any metric
units by setting GVXR.Beam_Pos_units
to the appropriate string (“mm”,”cm”,”m” etc …)[1]_.
For the beam shape we use GVXR.Beam_Type
. GVXR allows for two choices:
Cone beam:
GVXR.Beam_Type = 'point'
Parallel beam (e.g. synchrotron):
GVXR.Beam_Type = 'parallel'
Finally we need to set the beam spectrum. Out of the box GVXR supports Monochromatic and PolyChromatic sources. You can also use the package xpecgen to generate more realistic/complex spectra, such as those from xray tubes. This will be covered in a later session. For now we will stick with a simple Monochromatic source.
This can be set with GVXR.Energy
, this should be floating point (decimal) number, default
units are MeV. The Intensity (taken as number of photons) is set with GVXR.Intensity
this should be an integer (whole number). You can also optionally use GVXR.energy_units
with a string to denote the energy units. This can be any of “eV”, “keV” or “MeV”
(take care with capitalization).
Tip
Setting up a simple monochromatic source can be easily done by passing in a list of numbers for
energy and intensity. For example GVXR.Energy = [50,100,150]
and GVXR.Intensity = [500,1000,200]
will specify an X-ray source with 500, 1000, and 200 photons of 50,100 and 150 Mev respectively.
Action
Try changing the Beam energy from its current value of 0.08 Mev to 200 keV and observe what happens to the resulting image. you may also wish to try changing the beam from a cone beam to a parallel one.
8.3.2. Setting up the Detector:#
Setting up the detector we need to specify its position, shape and physical size.
Similar to the beam to set the position we use GVXR.Detect_PosX
, GVXR.Detect_PosY
and
GVXR.Detect_PosZ
again the default units are mm. However, you can easily change this to
essentially any metric units by setting GVXR.Detect_Pos_units
to the appropriate string
(“mm”,”cm”,”m” etc …)[1]_.
For the number of pixels in each direction we use GVXR.Pix_X
and GVXR.Pix_Y
. Note:
somewhat confusingly, up for the detector (i.e. Y) is along the Z axis in GVXR.
For the detector size we define the spacing between pixes with GVXR.Spacing_X
and
GVXR.Spacing_Y
again the default units are mm but this can be changed with
GVXR.Spacing_units
.
8.3.3. Setting up the Sample:#
Next we need to set the properties of the Sample in this case our dragon model
For our sample we need specify four things:
A 3D model of the object
What the Sample is made from
It’s position
It’s size
It’s orientation
First we need to specify the name of mesh file used. This is done with GVXR.mesh
This can be any mesh format supported by the python package meshio. You
only need to specify the filename including file extension.
To set the position, much like the X-Ray beam we use GVXR.Model_PosX
, GVXR.Model_PosY
and GVXR.Model_PosZ
in this case these define the center of the cad mesh in 3D space.
However unlike the beam position these are optional and if they are not given the mesh we be centered on the scene at the origin (that is [0,0,0]).
For units you have two parameters:
GVXR.Model_Pos_units
for the positionGVXR.Model_Mesh_units
for the mesh itself
The default units are mm. However, once again you can easily change this to essentially any metric units by using the appropriate string (“mm”,”cm”,”m” etc …).
For scaling the mesh we have the optional values GVXR.Model_ScaleX
, GVXR.Model_ScaleY
and GVXR.Model_ScaleZ
. These allow you to set a decimal scale factor in each dimension
to reduce of increase the size of the model as needed. e.g. GVXR.Model_ScaleX=1.5
will scale the model size by 1.5 times in the X direction.
We can also optionally set the initial rotation with GVXR.rotation
.
This is set as a list of 3 floating point numbers to specify the rotation in degrees
about the X,Y and Z axes. The default is [0,0,0] (i.e. no rotation). This is useful
if the model is not correctly aligned initially.
A note about Rotation
If you have used GVXR previously you will know that rotation can be a pain to deal with because of how OpenGL defines rotations (heres a link to good article for those interested souls). Sufficed to say I personally find rotations very quickly become unintuitive especially when dealing with multiple rotations and translations in sequence.
As such in VirtualLab rotations (both initial rotation and for CT scans)
are defined in the simplest way I can think off. They are clockwise, centered on the mesh,
are fixed to the scene (world) axes and are performed in the order X then Y then Z.
(i.e. a GVXR.rotation=[26.0,0,-15.3]
will perform a sequence of 2 rotations first
26 degrees clockwise about the X axis, then 15.3 degrees anti-clockwise about the Z axis).
If that makes no sense to you don’t worry to much about it to much. If you are worried just leave it at the default [0,0,0] or play with the numbers until it looks right. Hopefully its intuitive enough.
Finally we need to set the material of the sample. For this we use three parameters:
GVXR.Material_list
a list of materials used.
GVXR.Amounts
a list of of relative amounts for each material, only used with mixtures.
GVXR.Density
a list of the densities in g/cm^3 for each material.
These are all lists of values to define the properties for each material used.
To actually define materials we use GVXR.Material_list
. Each item in the list defines the
material. In our case for the sake of simplicity we only have one mesh so we only need one value.
Using multiple materials
The current example uses a single mesh made from a single material. The step up to multiple materials however, is slightly more complicated. We will be covering a multi-material example in the example 3.
However, due to limited development time/resources. In the current version of VirtualLab the use of multiple materials is only supported by using mesh regions in salome .med mesh files. We do hope to add multi-materials for all mesh formats via the use of multiple meshes in the near future. However, for now this is a known limitation of the current version.
In GVXR materials are split into three types: elements, mixtures (alloys) and Compounds. To define
an element we supply the English name, symbol or atomic number. So for a single mesh made from Chromium
we can use any of GVXR.Material_list = ['Chromium']
, GVXR.Material_list = ['Cr']
, or
GVXR.Material_list = [24]
.
For a mixture we define a list of the elements in the mixture as atomic numbers
(Note: names/symbols are not yet supported). You will also need to define
the relative amounts of each using GVXR.Amounts
with decimal values between
0.0 and 1.0 representing percentages from 0 to 100%. So for example a mixture of
25% Titanium and 75% Aluminum would be defined as: GVXR.Material_list = [[22,13]]
and
GVXR.Amounts = [[0.25,0.75]]
Compounds are defined as strings that represent the chemical formulae e.g. water would be 'H2O'
whilst Aluminum Oxide would be 'Al2O3'
. So for example a sample made from Silicon carbide
would be defined as: GVXR.Material_list = ['SiC']
.
For both Compounds and Mixtures you also will need to define the density for each
material used, in g/cm^3. So for our previous example of Silicon carbide we can simply
look up the density as
3.16 g/cm^3 thus we can use GVXR.Density=[3.16]
The density for the mixture of Titanium and Aluminum is more complex as there is no standard value so we need to approximate it. According to the royal society of chemistry Ti has a density of 4.506 g/cm^3 whilst Al is 2.70 g/cm^3. Thus for for our mixture using Vegard’s law we get a approximate density of
Thus GVXR.Density=[3.152]
Task
The default material for this example is Aluminum. Try changing this to something much more dense like tungsten (hint the chemical symbol for tungsten is W whilst its atomic mass is 74) and observe what the effect is on the resulting image. You could also try changing the sample to Aluminum oxide (which for reference has a density of 3.987 g/cm^3).
8.4. Example 2: Defining scans using a Nikon .xect files.#
Many CT scanners use the Nikon .xect format to define scan parameters. These are just specially formatted text files ending in the .xect file extension. VirtualLab can read in parameters from these files.
To use these files you need to use GVXR.Nikon_file
which sets the
name of the nikon file you wish to use. This can either be in the Input
directory or the absolute path to the file.
You will also at a minimum need to define
GVXR.Name
GVXR.Mesh
GVXR.Materail_list
As well as possibly amounts and density depending on what materials you have specified. All other parameters are either optional or will be taken from the equivalent parameters in the nikon file.
Action
As an example we will perform the same simulation as the previous example only this time we will define the setup with a nikon file.
This will require changing Parameters_Master to ‘TrainingParameters_GVXR_Nikon’
The RunFile
RunTutorials.py
should be setup as follows to run this simulation:Simulation='Examples' Project='Dragon' Parameters_Master='TrainingParameters_GVXR_Nikon' Parameters_Var=None VirtualLab=VLSetup( Simulation, Project ) VirtualLab.Settings( Mode='Interactive', Launcher='Process', NbJobs=1 ) VirtualLab.Parameters( Parameters_Master, Parameters_Var, RunCT_Scan=True RunCT_Recon=False, ) VirtualLab.CT_Scan()Launch VirtualLab using the following command:
VirtualLab -f RunFiles/RunTutorials.py
The resulting xray image will be saved to Output/GVXR/Tutorials/GVXR_Images/Dragon_Nikon/Dragon_Nikon_1.tiff
and will be identical to Fig. 8.2.
The following is a table of parameters in the nikon file and there equivalent parameters in VirtualLab.
Nikon Parameter |
Notes |
Equivalent Parameter |
|
---|---|---|---|
Units |
Units for position of all objects |
GVXR.Beam_Pos_units, GVXR.Det_Pos_units, GVXR.Model_Pos_units |
|
Projections |
Number of projections |
GVXR.num_projections |
|
AngularStep |
Angular step between images in degrees. |
GVXR.angular_step |
|
DetectorPixelsX/Y |
number of pixels along X/Y axis |
GVXR.Pix_X/Pix_Y |
|
DetectorPixelSizeX/Y |
Size of pixels in X and Y |
GVXR.Spacing_X/Y |
|
SrcToObject |
Distance in z from X-ray source to object, Note this is y in GVXR co-ordinates thus our beam position is defined as: [0,-SrcToObject,0] |
GVXR.Beam_PosY |
|
SrcToDetector |
Distance in z from source to center of detector. Again this is equivalent to y in GVXR. Thus Detect_PosY is defined as: SrcToDetector-SrcToObject |
GVXR.Detect_PosY |
|
DetectorOffsetX/Y |
detector offset from origin in X/Y |
Detect_PosX/Z |
|
XraykV |
Tube voltage in kV |
GVXR.Tube_Voltage |
|
Filter_Material |
Material used for beam filter |
GVXR.Filter_Material |
|
Filter_ThicknessMM |
Thickness of beam filter in mm |
GVXR.Filter_ThicknessMM |
Please note however that a real nikon file will in general have a lot more parameters than these. As such any additional parameters defined in the file, along with comments in square brackets will simply be ignored.
Overriding values defined in a Nikon file.
You can define parameters in the input file that are also defined in the nikon file. If you do the parameters in the input file will override those in the nikon file.
8.5. Example 3: X-Ray CT-Scan with Multiple Materials#
In this example we will Simulate a X-ray CT scan using the AMAZE mesh that was previously used for the HIVE analysis in tutorial 3.
Note
If you haven’t completed tutorial 3 you will need to run the following command to generate the mesh
VirtualLab -f RunFiles/Tutorials/X-ray_imaging/Task3_mesh.py
Action
The RunFile
RunTutorials.py
should be setup as follows to run this simulation:Simulation='HIVE' Project='Tutorials' Parameters_Master='TrainingParameters_GVXR' Parameters_Var=None VirtualLab=VLSetup( Simulation, Project ) VirtualLab.Settings( Mode='Interactive', Launcher='Process', NbJobs=1 ) VirtualLab.Parameters( Parameters_Master, Parameters_Var, RunSim=False, RunCT_Scan=True, RunCT_Recon=False ) VirtualLab.CT_Scan()A copy of this run file can be found in
RunFiles/Tutorials/X-ray_imaging/Task3_Run.py
Looking at the file Input/HIVE/Tutorials/TrainingParameters_GVXR.py
you will notice the Namespace GVXR
has a few new options defined. Firstly, we are now using a more realistic beam spectrum instead of a monochromatic source. This is achieved by replacing GVXR.Energy
with
GVXR.Tube_Voltage
. This tell VirtualLab to generate a beam spectrum from a simulated X-Ray Tube using xspecgen, in this case running at 440 KV. This is a more realistic X-Ray source than a simple monochromatic beam.
VirtualLab also has three other optional parameters related to X-Ray Tube spectrums which we are not used in this example.
GVXR.Tube_Angle
common setting used by X-ray tubes default is 12.0GVXR.Filter_Material
material used for beam filter, used to remove certain frequenciesGVXR.Filter_ThicknessMM
Thickness of beam filter
The second change to note here is we are now using a mesh with multiple materials. As mentioned earlier this is only currently implemented for salome med meshes using mesh regions. In our case the mesh has 3 regions Pipe, Block, and Tile.
For GVXR we have to define the corresponding materials using GVXR.Material_list
in this case the pipe and block are both made from Copper. whilst the tile is
made from the much denser Tungsten.
Action
Launch VirtualLab using the following command:
VirtualLab -f RunFiles/RunTutorials.py
The x-ray image generated for this sample can be found in Output/HIVE/Tutorials/GVXR-Images/AMAZE_single
, and should look like Fig. 8.3.
8.6. Appendix#
Here is a complete list of all the available parameters that are used with GVXR alongside a brief explanation of there function. Note a default value of “-” indicates that this is a required parameter.
Parameter |
Notes |
Default Value |
---|---|---|
Name |
Name of the simulation |
– |
mesh |
Name of mesh file used |
– |
Beam_PosX |
Position of beam in X |
–2 |
Beam_PosY |
Position of beam in Y |
–2 |
Beam_PosZ |
Position of beam in Z |
–2 |
Beam_Pos_units |
units for Beam position 1 |
mm |
Beam_Type |
Type of Source used, can be either point or parallel |
point |
Energy |
Energy of Beam |
0.0 |
Intensity |
Number of Photons |
0 |
Tube_Angle |
Tube angle, if using spectrum calculation |
12.0 |
Tube_Voltage |
Tube Voltage, if using spectrum calculation |
0.0 |
Filter_material |
material for beam filter, optional parameter used in spectrum calculation. |
None |
Filter_ThicknessMM |
Beam filter thickness in mm, optional parameter used in spectrum calculation. |
None |
energy_units |
Units for Energy can be any of ‘eV’ ‘KeV’, ‘MeV’ |
Mev |
Model_PosX |
Position of center of the Cad Mesh in X |
0.0 2 |
Model_PosY |
Position of center of the Cad Mesh in Y |
0.0 2 |
Model_PosZ |
Position of center of the Cad Mesh in Z |
0.0 2 |
Model_ScaleX |
CAD Model scaling factor. Used to scale the model if needed. |
1.0 |
Model_ScaleY |
CAD Model scaling factor. Used to scale the model if needed. |
1.0 |
Model_ScaleZ |
CAD Model scaling factor. Used to scale the model if needed. |
1.0 |
rotation |
Initial rotation, in deg of Cad Model about X,Y and Z axis. Useful if the cad model is not aligned how you would like. |
[0.0,0.0,0.0] |
Model_Pos_units |
units for Cad Mesh position 1 |
mm |
Model_Mesh_units |
units for Mesh itself 1 |
mm |
Detect_PosX |
Position of X-Ray detector in X |
–2 |
Detect_PosY |
Position of X-Ray detector in Y |
–2 |
Detect_PosZ |
Position of X-Ray detector in Z |
–2 |
Detect_Pos_units |
units for X-Ray detector position 1 |
mm |
Pix_X |
Number of pixels for the X-Ray detector in X |
–2 |
Pix_Y |
Number of pixels for the X-Ray detector in Y |
–2 |
SpacingX |
distance between Pixels in X |
0.5 |
SpacingY |
distance between Pixels in Y |
0.5 |
Spacing_units |
units for Pixel spacing 1 |
mm |
Material_list |
list of materials used for each mesh or sub-mesh. See materials section for detailed usage. |
– |
Amounts |
relative amounts of each material used. Note values used here must add up to 1.0 |
None |
Density |
density of each material used in g/cm^3. |
None |
num_projections |
Number of projections generated for X-Ray CT Scan |
1 2 |
angular_step |
Angular step in deg to rotate mesh between each projection, Note: rotation is about the Y-axis in GVXR co-ordinates |
0 2 |
use_tetra |
Flag to tell GVXR you are using a volume mesh based on tetrahedrons. Not the default triangles. When this is set it tells GVXR to perform an extra step to extract just the mesh surface as triangle mesh. Note: whilst this is reasonably efficient it does add a small amount of overhead to the first run. However to mitigate this with multiple runs the new mesh is saved as ‘{filename}_triangles.{mesh_format}’ and is automatically re-used in future runs. |
False |
fill_percent |
This setting, along with fill_value is used for removing ring artifacts during CT reconstruction. It allows you to fill a given percentage of the pixels from the 4 edges of the image (Top, bottom, left and right) with a specific value fill_value. If fill_value is not specified then the value used is calculated automatically from the average of the image background. |
0.0 |
fill_value |
value used to fill pixels at the image edges, when using fill_percent. |
None |
Nikon_file |
Name of or path to a Nikon parameter .xtekct file to read parameters from, see section on Nikon file for more detailed explanation. |
None |
image_format |
This option allows you to select the image format for the final output.
If it is omitted (or set to |
Tiff |
bitrate |
bitrate used for output images. Can be ‘int8’/’int16’ for 8 and 16 bit greyscale or ‘float32’ for raw intensity values. |
float32 |
- 1(1,2,3,4,5)
Note for real space quantities units can be any off: “um”, “micrometre”, “micrometer”, “mm”, “millimetre”, “millimeter”, “cm”, “centimetre”, “centimeter”, “dm”, “decimetre”, “decimeter”, “m” “metre”, “meter”, “dam”, “decametre”, “decameter”, “hm”, “hectometre”, “hectometer”, “km”, “kilometre” “kilometer”
- 2(1,2,3,4,5,6,7,8,9,10,11,12,13)
These values are not required when using a Nikon .xect file as their corresponding values will be read in from that. If they are defined when using a nikon file they will override the corresponding value in the Nikon file. See section on Nikon files for more details.