Recent posts

#11
Thanks for the detailed observation — this is indeed a point where the GGEMS v1.3 implementation behaves differently from what the documentation suggests.

In the voxelized geometry module of v1.3, the voxel grid is internally indexed from a corner, but when the phantom is constructed and positioned in the world, the engine applies a transformation that implicitly recentres the volume around its geometric centre.
As a result, calling:
phantom.set_position(0.0, 0.0, 0.0, "mm");

places the centre of the phantom at the world origin, not the corner of the voxel grid.This behaviour is consistent with what you are seeing, and it is expected for the current v1.3 implementation. This centring step was historically introduced to simplify rotations and transformations, but it does create an inconsistency between the documented "corner origin" and the effective placement of the object in world coordinates.
We will address this properly in GGEMS v2, where the coordinate conventions will be made fully explicit and consistent (local origin, centre, positioning, and rotations). The goal is to remove this ambiguity entirely and adopt a single, predictable convention across the whole geometry system.
#12
I find that I get substantially different simulation results depending on the order in which navigators are defined. In the attached file MeshVox.py, I define meshed navigators first  (for a CBCT bowtie and collimator) and a voxelized navigator second (the phantom). This leads to the simulated projection image shown in the attached MeshVox.png, which is how the result should look. However, if I interchange the order of the meshed and voxelized navigators (see VoxMesh.py) then the result exhibits a strong artifact (see VoxMesh.png).

Is this expected behavior? If so, what are the precise rules for how the ordering determines navigation geometry?
#13
In the documentation for voxelized navigators in v1.3, the diagram shows the local coordinate system (x',y',z') of the voxel volume to have its origin at one corner of the voxel grid (I'm not sure if that's supposed to be the center of the first voxel or the voxel's  outermost corner...)

However, all of the behavior I'm seeing suggests that the origin is instead at the center of the voxel grid. In particular, when I set up a phantom with the following
phantom = GGEMSVoxelizedPhantom('anthropomorphic')
phantom.set_phantom('data/anthropomorphic.mhd', 'data/range_anthropomorphic.txt')
phantom.set_rotation(0.0, 0.0, 0.0, 'deg')
phantom.set_position(0.0, 0.0, 0.0, 'mm')
the simulation results I'm getting are consistent with a phantom centered at the world origin.
                             
If the (x',y',z') origin is at the corner of the voxel grid, shouldn't
set_position(0.0, 0.0, 0.0, 'mm') put the corner of the voxel grid at the world origin, rather than the phantom's center?
#14
Output Data / Improving histogramming effici...
Last post by mwj12 - Dec 06, 2025, 01:48 AM
This section of kernel code in TrackThroughGGEMSSolidBox.cl looks like it could be made more efficient (this is v1.3). Specifically, if we are storing scatter separately, it doesn't make sense to increment both histogram arrays (&histogram and &scatter_histogram) when a scattered detection occurs.

       
 atomic_add(&histogram[voxel_id.x + voxel_id.y * virtual_element_number.x], 1);

  // Storing scatter
  if (scatter_histogram) {
     if (primary_particle->scatter_[global_id] == TRUE) atomic_add(&scatter_histogram[voxel_id.x + voxel_id.y * virtual_element_number.x], 1);
  }

Instead,one can increment just one of the histogram counters, e.g.,
const unsigned int idx =
    (unsigned int)voxel_id.x +
    (unsigned int)voxel_id.y * (unsigned int)virtual_element_number.x;

if (scatter_histogram && primary_particle->scatter_[global_id]) {
    atomic_add(&scatter_histogram[idx], 1u);
} else {
    atomic_add(&histogram[idx], 1u);
}

This would eliminate an atomic add. One might still want the final &histogram to be a combined tally of both primary and scattered events, but that can be done trivially by adding the two histograms together at the end of the simulation.
#15
My Python script generates a multitude of navigators, detectors, etc... in list form using a loop, something like,
   
meshed = []
for Md in as_list(cfg.get("meshedNavigators")):
        meshed.append(build_meshed_navigator(Md, matlabBase))

Here, the details of build_meshed_navigator() are not really important. It is my own function which constructs a meshed navigator with GGEMSMeshedPhantom and populates its members to certain specifications. The result is a list of navigators, held in the list 'meshed'.

But in all the documentation examples, the components are all created as explicit top-level variables, and  GGEMS v1.3 doesn't seem to be able to recognize GGEMS objects when they are encapsulated in lists or other containers. My simulation output thus contains all zeros, as if no sources or other components are present. Is there a way to get GGEMS v1.3 to recognize arrays of navigators and other components, built using loops?
#16
Output Data / CBCT simulation output except...
Last post by zhenguo - Dec 01, 2025, 01:18 PM
Hello, I am a beginner with GGEMS. When using example 7_Mesh.py, I modified the original STL phantom code as follows:
Original code for STEP 5: Phantoms and systems
# Loading phantom in GGEMS
mesh_phantom = GGEMSMeshedPhantom('phantom_mesh')
mesh_phantom.set_phantom('data/Stanford_Bunny.stl')
mesh_phantom.set_rotation(90.0, 90.0, 0.0, 'deg')
mesh_phantom.set_position(0.0, 0.0, 0.0, 'mm')
mesh_phantom.set_mesh_octree_depth(4)
mesh_phantom.set_visible(True)
mesh_phantom.set_material('Water')
mesh_phantom.set_material_color('Water', color_name='white') # Uncomment for automatic color

Modified code:
# Loading phantom in GGEMS
mesh_phantom = GGEMSMeshedPhantom('Centrifuge_tube')
mesh_phantom.set_phantom('data/Centrifuge_tube.STL')
mesh_phantom.set_rotation(0.0, 0.0, 0.0, 'deg')
mesh_phantom.set_position(-11.25, -50.0, -11.25, 'mm')
mesh_phantom.set_mesh_octree_depth(4)
mesh_phantom.set_visible(True)
mesh_phantom.set_material('Bakelite')
mesh_phantom.set_material_color('Bakelite', color_name='white') # Uncomment for automatic color

mesh_phantom1 = GGEMSMeshedPhantom('Cylindrical_soil')
mesh_phantom1.set_phantom('data/Cylindrical_soil.STL')
mesh_phantom1.set_rotation(0.0, 0.0, 0.0, 'deg')
mesh_phantom1.set_position(-6, -10.0, -6, 'mm')
mesh_phantom1.set_mesh_octree_depth(4)
mesh_phantom1.set_visible(True)
mesh_phantom1.set_material('Lead')
mesh_phantom1.set_material_color('Lead', color_name='blue') # Uncomment for automatic color

The STL file of the centrifuge tube phantom is a cylindrical ring with a height of 100 mm, an outer radius of 11.25 mm, and an inner radius of 10.5 mm; the cylindrical soil phantom is a cylinder with a radius of 6 mm and a height of 20 mm. The geometric centers of both phantoms are at the coordinate origin, and there is no overlap between them at all—the cylindrical ring encloses the cylinder.
After running the code, I found that the generated projection.raw image only shows the projection of the centrifuge tube sample, with no information about the cylindrical soil reflected. However, if I change mesh_phantom1.set_position(-6, -10.0, -6, 'mm') to mesh_phantom1.set_position(-100, -10.0, -6, 'mm') (at which point the cylindrical ring and the cylinder no longer have an enclosing or overlapping relationship), the information of the soil cylinder will appear in the projection.raw file generated after running the code.
What causes this issue and how can it be resolved? I would be extremely grateful for your answer. Best regards!
#17
Thanks!
#18
Great, thanks!
#19
In version 1.3:

GGEMSXRaySource = independent source, with its own position/orientation.

GGEMSCTSystem = CT system geometry description (isocentre → source → detector distances).

The CT system assumes a specific scanning geometry (as in a real CT gantry), and these distances are internal parameters used for projections and detector placement.

This is why you can create several GGEMSXRaySource and several GGEMSCTSystem objects independently:
the CT system does not track or reference a specific GGEMSXRaySource.

If several sources exist, the CT system geometry stays the same and is not "linked" to one of them.
It simply defines its own source position relative to its isocentre internally.
#20
In GGEMS 1.3, the three angles (rx, ry, rz) are applied as extrinsic rotations about the global X, Y and Z axes, in this order (X → Y → Z).
Internally the code composes the rotation matrix as Rz * Ry * Rx, which, with the convention used in GGEMS (column vectors, active rotations), corresponds to a rotation first about X, then about Y, then about Z.
In GGEMS v2, the whole transformation system (rotations and translations) will be redesigned and much more thoroughly documented, with explicit conventions on Euler angles, rotation order, and possibly quaternions.