Problem with layers_step_axl not being set in castor-GATEMacToGeom v3.2.1

Hi all,

I’ve just started using CASToR and encountered an issue when converting a GATE PET macro to a castor geometry file. The conversion is successful with v3.1.1 but fails with v3.2.1, so the issue appears to be related to the changes to improve multi-layer geometry management.

Here’s what I’ve found from debugging src/management/gDataConversionUtilities.cc, and I’d appreciate your input on whether my interpretation and solution are reasonable.

From the GDB debugger:

Program received signal SIGSEGV, Segmentation fault.
CreateGeomWithCylindrical (a_pathMac="../mac/geometry_layer.mac", a_pathGeom="/opt/castor/castor_v3.2.1/castor_v3.2/config/scanner/geometry_layer.geom") at /opt/castor/castor_v3.2.1/castor_v3.2/src/management/gDataConversionUtilities.cc:4394
4394	        if (layers_step_axl[l] - layers_size_axl[l] >= 0)
(gdb) print layers_step_axl[l]
Cannot access memory at address 0x0
(gdb) print layers_size_axl[l]
$1 = 5.2999999999999998

Just before this crash, CASToR checks if(layers_step_trs.size()>0 || layers_step_axl.size()>0), and since the former is > 0, the code continues. In v3.1.1, layers_step_axl.size() is 1 and the conversion succeeds, but in v3.2.1, the size is 0 but the vectory is actually empty, so layers_step_axl[l] results in a segfault.

Looking further back at the calls to layers_step_axl.push_back(step_axl), one of the key differencs lies within the if/else statements in the blocks of code below. In v3.2.1, the push back lies within an else statement that isn’t executed, and in v3.1.1 there are no if/else statements to prevent the push back.
For v3.2.1, lines 4041-4055 are:

          if(step_trs > 0) // linear repeater for trs
          {
            layers_step_trs.push_back(step_trs);
          }
          else if (step_axl > 0) // linear repeater for axl
          {
            layers_step_axl.push_back(step_axl);
          }
          else if (step_dph > 0)
            layers_step_dph.push_back(step_dph);
          else // something wrong
          {
            Cerr("***** dataConversionUtilities::CreateGeomWithCylindrical()-> There seems to be a problem with layer linear repeater. Please report the error to the castor mailing-list! At line " << line<< endl);
            return 1;
          }

Note that when this if/else block is executed, the values of step_trs, step_axl, and step_dph are 3.983300e+00, 5.300000e+00, and 0.000000e+00, respectively.

For v3.1.1, the corresponding lines (3947-3948) are:

          layers_step_trs.push_back(step_trs);
          layers_step_axl.push_back(step_axl);

I don’t fully understand the changes that were made and why, or all of the possible use cases. However, if I replace the if/else statements in v3.2.1 with the push_back lines immediately below, then conversion completes successfully with the same result as v3.1.1 (apart from the “rsector gap transaxial” which is no longer included in the .geom file).

          layers_step_trs.push_back(step_trs);
          layers_step_axl.push_back(step_axl);
          layers_step_dph.push_back(step_dph);

Are these if/else statements in v3.2.1 necessary or intentional? If so, is there an alternate approach to convert single-layer scanners to ensure all relevant vectors are filled?

Best,
Matthew

I’m also encountering a similar issue with castor-GATERootToCastor where the function ReadMacCylindrical tries to access nLayersRptAxial[l] when the vector is empty.

After more testing of the RootToCastor conversion, I found CASToR has a slight dependence on the order of commands in the GATE macro which might be causing lines 1906-1907 to be executed too early:

      if(nLayersRptTransaxial.size()>0 || nLayersRptAxial.size()>0    )
         nb_crystals_layer *= nLayersRptTransaxial[l] * nLayersRptAxial[l];

For example, I receive a segfault when configuring my macro as

/gate/crystal/cubicArray/setRepeatNumberX       4
/gate/crystal/cubicArray/setRepeatNumberY       1
/gate/crystal/cubicArray/setRepeatNumberZ       3

However, the conversion is successful when my macro is configured with setRepeatNumberZ appearing before Y:

/gate/crystal/cubicArray/setRepeatNumberZ       3
/gate/crystal/cubicArray/setRepeatNumberX       4
/gate/crystal/cubicArray/setRepeatNumberY       1

I don’t fully understand what’s going wrong with the parsing, but it appears that something is being executed as soon as setRepeatNumberY is pushed back to nLayersRptTransaxial. This is problematic if Z hasn’t been parsed first.

I’m not sure if this is directly related to the above problem.

Best,
Matthew

Greetings,

Would you mind sharing your mac file or a MWE? Those I have do not seem to generate an error with CASToR v3.2.1 and I have a hypothesis I would like to test.

Bests,
Maxime Toussaint

Hi Maxime,

Thank you for your reply! The discourse says new users cannot upload attachments, so I’ll try sending the macro and root files in this email.

The commands I’m using are:

  1. castor-GATEMacToGeom -m geometry_layer.mac -o geometry_layer
  2. castor-GATERootToCastor -i output.root -s geometry_layer -m geometry_layer.mac -o test
  3. castor-recon -df test_df.Cdh -fout test_recon -it 4:28 -dim 256,256,89 -fov 300,300,247.42 -opti MLEM

The recon runs to completion, so there are no apparent issues there thankfully.

Best,
Matthew

(Attachment output.root is missing)

(attachments)

geometry_layer.mac (5.01 KB)

Second attempt, renaming output.root to output.txt to bypass restricted file types.

Cheers,
Matthew

(Attachment output.txt is missing)

Final attempt, renaming output.root to output.txt with file size < 4 MB.

M

(attachments)

output.txt (2.86 MB)

Hi Matthew,

First, sorry for the late reply. I am having a really hard time compiling CASToR with ROOT in my current workstations. I was able to sidestep this for the Mac convertor but I can’t use the ROOT convertor currently…

Nevertheless, I think I understand what is happening. The code seems to assume that layer would be used to describe a change in geometry along the “thickness” of the cylinder, not along its “surface”. In order word, for a scanner with DOI-dependent geometries.

Is there a particular need to use “layer0” over merging block&optical as a “submodule” and promote crystal to “crystal”? Block and optical are repeated in two different axes, so it seems to be mergeable, at first glance.

Note that it is my understanding of the code so it might be wrong. However, it seems that GATE documentation mentions something in that sense for the layer structure:

  • “1.4.3.1 (…) layer (depth=5) is a (or several, in the case of a phoswich system) radially arranged box(es) inside crystal. A repeater should not be used for layers, but the should be build them one by one in the macro”

Thus, I would recommend changing the mac file.

I hope it helps!

Hi Matthew (and Maxime),

Thank you for your very detailed posts.

The changes in v3.2 were due to accommodate different ways to define layers in Gate macros, using repeaters instead of the usual methods as mentioned by Maxime. Unfortunately it brought other issues as you noticed.

There is a quick temporary fix in the Gitlab (hotfixv.3.2.2) which separates the processing for each axis and seems to work with your files, at least for the geometry conversion. However it doesn’t address the issue you mentioned regarding the order of the commands in the macro files.

If this due to the c++ standard ? The code still assumes c++11 by default, which is not compatible with lastest ROOT versions and must currently be manually changed when compiling with ROOT.

Best,
Thibaut

About ROOT:

Yes and no… Some versions of ROOT require c++14 or c++17. If you take the latest versions that works on c++11, you can be unlucky and have a ROOT bug that was only repaired in a ROOT that require c++14 [1].

At the end, taking a ROOT v5.x.y worked. Well, it compiled at least!

[1] Build error from XROOTD - ROOT - ROOT Forum

Just to clarify, old CASToR versions (up to v2.x I think) unnecessarily forced compilation with c++11, but it can also be compiled with c++14/17 if required by ROOT or any other library.

Anyway, you already found a work-around!

Ohhhhh! Thanks for the clarification, I indeed thought you meant the other way around! Thanks for the tip!

Hi Maxime and Thibaut,

Thanks for sharing the hotfix. I’m not sure if the link is broken or if I don’t have permission to access it, but I’m able to workaround my issue without it. I appreciate the speedy assistance in any case! For clarification, I successfully compiled CASToR v3.2.1 and v3.1.1 on Ubuntu 22.04 with ROOT v6.32.06 (precompiled binary distribution) after changing castor’s CMakeLists.txt to use c++17.

I inherited the macros (and castor commands) as a fully validated system. Thus, I cannot immediately answer why the block & optical geometries were separately implemented instead of a unified submodule. I took your recommendations and successfully merged them into a submodule. I also redefined the system to use Y repetitions instead of X and removed the repeater on layer0. The resulting system appears to produce the same simulation output (aside from submodule, crystal, and layer IDs in the root file), and the macro is castor-compatible without having to reorder any commands, so my hope is that the complete system is still valid.

There are some subtle differences in the .geom files (pasted below) after redefining the macro. In the mandatory fields, the number of crystals and voxels are different (although I specify the voxel info with dim and fov when calling castor-recon). I have not reconstructed a distribution of activity yet, but I presume that the number of crystals axial should be consistent with whichever macro was used to generate the data in order to not affect the recon. In the optional fields, the submodule gaps are of slight concern as the axial direction changed from 0 to 0.7, and the transaxial direction changed from 0.3 to 0.3001. Since these are listed as optional, I’m not yet sure if the affect reconstruction.

I admit that I still need to read through the CASToR documentation in detail. In the meantime, could you please let me know if the optional fields as written are used during reconstruction? Also, could you possibly share your insights on the .geom files and whether I should be concerned about the changes in the fields? Again, I hope that the system is still valid if I move forward with these changes.

Thanks in advance!

Positively,
Matthew

Original geometry:

# MANDATORY FIELDS
modality : PET
scanner name : geometry_layer
number of elements              : 20160
number of layers : 1

voxels number transaxial        : 37
voxels number axial                : 30
field of view transaxial        : 432.4
field of view axial                : 250.4

description        : PET system extracted from GATE macro: ../mac/geometry_layer.mac

scanner radius : 311.8
number of rsectors              : 28
number of crystals transaxial    : 4
number of crystals axial            : 9

crystals size depth                : 25
crystals size transaxial          : 3.95
crystals size axial                 : 5.3


# OPTIONAL FIELDS
rsectors first angle              : 0
number of rsectors axial            : 1
rsector gap axial                        : 0
number of modules transaxial    : 1
number of modules axial            : 5
module gap transaxial                : 0
module gap axial                        : 2.8
number of submodules transaxial    : 4
number of submodules axial            : 1
submodule gap transaxial                : 0.3
submodule gap axial                        : 0
crystal gap transaxial                : 0.0333
crystal gap axial                        : 0
mean depth of interaction       :  -1
rotation direction       : CCW 

Redefined geometry:

# MANDATORY FIELDS
modality : PET
scanner name : geometry_layer_redefined
number of elements              : 20160
number of layers : 1

voxels number transaxial        : 149
voxels number axial                : 90
field of view transaxial        : 432.4
field of view axial                : 250.4

description        : PET system extracted from GATE macro: ../mac/geometry_layer_redefined.mac

scanner radius : 311.8
number of rsectors              : 28
number of crystals transaxial    : 4
number of crystals axial            : 3

crystals size depth                : 25
crystals size transaxial          : 3.95
crystals size axial                 : 5.3


# OPTIONAL FIELDS
rsectors first angle              : -90
number of rsectors axial            : 1
rsector gap axial                        : 0
number of modules transaxial    : 1
number of modules axial            : 5
module gap transaxial                : 0
module gap axial                        : 2.8
number of submodules transaxial    : 4
number of submodules axial            : 3
submodule gap transaxial                : 0.3001
submodule gap axial                        : 0.07
crystal gap transaxial                : 0.0333
crystal gap axial                        : 0
mean depth of interaction       :  -1
rotation direction       : CCW

In the documentation, there is the following information:

  • number of crystals transaxial: for each layer, number of transverse crystals within a
    submodule.
  • number of crystals axial: for each layer, number of axial crystals within a submodule.

Thus, I think that “3” corresponds to the expected value following your changes.

Also, since you merged block and optical as a submodule, it can make sense that the gap between the submodules is non-zero in both directions. As for the values, you can try to validate it by hand from your mac file.

As for the difference between the two geom, if the former was produced from the code you modified so that CASToR would not crash, it is possible that the output is slightly wrong.

Finally, I think that OPTIONAL FIELDS here means that it is not required to provide them IF their default values correspond to your geometry. So they affect the reconstruction.

Positively is a nice closing word. I think I shall add it to my list XD

1 Like

Hi Matthew

Yes a registration to the gitlab is required. I just sent you an invitation link, if you still want to have a look.

  • The new numbers of axial crystal/submodules reflect your changes as Maxime stated.
  • I believe the submodule gap comes from a difference between a setRepeatVector of a volume and its size.
  • The voxels number xxx variables are indeed default value used for castor-recon image matrix in case none is provided by the user, so they don’t really matter as long as you set them when launching a reconstruction.
  • Some fields are marked optional simply as the program will not send an error if they are not provided in the geom file, contrary to the mandatory fields. But they will affect the geometry.

Just for information one can display/print the location of each elements as computed by CASToR using the castor-scannerLUTExplorer tool with the path of the created geom file. This could help to check everything is correctly located.

Cheers,
Thibaut