UCL/STIR

Expansion to Buckets on Cylindrical geometries

robbietuk opened this issue · 1 comments

Primarily an extension of issue #1374 and PR #1375. The motivation for this was detailed here with an initial plan laid out. This issue serves as an extension of that comment.

STIR does not support multiple axial buckets of blocks of crystals on a ring. #1375 disabled the ability to use multiple axial buckets due to a bug in an assumptive implementation.

I require the use of gaps_between blocks != gaps_between_buckets therefore I am planning the implementation of BucketsOnCylindrical using the BlocksOnCylindrical current frame work. I do not plan to change the name, just the implementation.

Below are some of the main points worth discussion.


What do are a set of parallel buckets called?

I will refer to them as modules in this issue but the name can change via a refactor.

Modules are rotated by angle alpha around the z axis of the scanner. It is important a suitable name is given to the collection of buckets

Some other ideas:

  • In GATE these are called rsectors for CylindricalPET (see GATE example .mac file)
    An OpenGATE scanner geometry is modular. For CylindricalPET, \textit{STIR} uses the conversion
    scheme detailed in Table \ref{tab:STIR-OpenGATE_terminology}.
    \begin{table}[h]
    \centering
    \begin{tabular}{ |c | c| }
    \hline
    \textbf{GATE} & \textbf{STIR} \\
    \hline
    Crystal & Crystal \\
    Submodule & Block \\
    Module & Bucket \\
    Rsector & Buckets around the ring \\
    \hline
    \end{tabular}
    \caption{A guide on the terminology equivalents between STIR and OpenGATE for CylindricalPET
    scanner geometries.}
    \label{tab:STIR-OpenGATE_terminology}
    \end{table}

    STIR could adopt rsectors, sectors or sections
  • Alternatively, these can be refereed to as a modules. This would might make the translation between STIR and GATE even more annoying but it is a good fit.
  • @KrisThielemans I don't know exactly what other manufacturers refer to them as.

Suggestions welcome


Transaxial Buckets are maybe not supported?

Looking at the current definition,the alpha definition is in terms of buckets. So it will put all blocks in a bucket next/parallel to eachother. That looks good to me. So, I think that currently BlocksOnCylindrical does support multiple blocks per bucket in transaxial direction. However, clearly in axial direction the bucket location is completely ignored.

#1375 (comment). I am not sure this is the case upon further review.

float alpha = scanner.get_intrinsic_azimuthal_tilt() + trans_bucket_num * (2 * _PI) / num_transaxial_buckets
+ csi_minus_csiGaps;

It seems trans_bucket_num is only used in the computation of alpha and in the tangential_coord (similar to axial_bucket_num in calculation of axial_coord). The alpha is used to determine the rotation_matrix, i.e. alpha is an angle around the z axis.

I understand this as no two buckets can be parallel.

In this case, additional variables is needed throughout: num_axial_buckets_per_module and num_transaxial_buckets_per_module.

  • These variables inevitably need to be everywhere but will default to 1.
  • GeometryBlocksOnCylindrical::build_crystal_maps loops will need to be extended.
  • More methods added to get_num_modules(), get_num_axial_crystals_per_module() and get_num_transaxial_crystals_per_module()

Spacing between buckets (Axial & Transaxial)

Furthermore, for most scanners it would make more sense to have a larger axial gap between buckets than between blocks. We currently don't have the variables for that.

#1375 (comment) . Not too much to add. The value should probably default to num_axial_blocks_per_bucket*get_axial_block_spacing(), otherwise a user defined variable > 0.


More tests

#1375 adds a test requiring the axial crystals position to increase monotonically with the axial crystal coordinate.

Additional tests should be added for the transaxial. As an idea, the angle sequential coordinate vectors in the detector map should never be greater than 90 degrees, i.e. cos(A -> B, B->C) >= 0. Except maybe in the instance of only two modules.


Virtual Crystals

#776 by @NikEfth tries to add virtual crystals. I am not 100% sure on the status of this and neither do I want to delve into it but I assume any issues encountered with adding virtual crystals to BucketsOnCylindrical would also be present in the BlocksOnCylindrical, so no significant change there.


Data Correction

Will there be any other issues with data correction methods etc. that we have not taken into account? e.g. scatter correction and normalization? I expect the same issues, maybe further complicated by this proposal.

@KrisThielemans @markus-jehl @danieldeidda

Notes for myself.

It is currently assumed that a scanner is defined from two parts. Consider Scanner::set_params() method. A "module", as dubbed above, is defined from the following transaxial variables:

int num_axial_blocks_per_bucket_v,
int num_transaxial_blocks_per_bucket_v,
int num_axial_crystals_per_block_v,
int num_transaxial_crystals_per_block_v,

The number of these "modules", incrementally rotated by angle alpha about the z axis, is derived from this transaxial bucket information and the num_detectors_per_ring_v parameter. I am not sure there is a check on this...

The axial information is derived from the above axial variables in the code quote and the num_rings_v.

To expand to BucketsOnCylindrical, "modules" need to be derived from the bucket per module details too. This will require an expansion in the scanner definition to add num_axial_buckets_per_module_v and num_transaxial_buckets_per_module_v. Checks should be run on all of these.


I should also mention that in #1375 (comment) I suggested replacing num_rings (and by extension num_detectors_per_ring):

Transition away from the use of num_rings to define the geometry and implement entirely from num_crystals_per_block, num_blocks_per_bucket and num_buckets_per_(module?) (in each dimension)

  • I personally perfer this method as it is the simplest for the user as they no longer need to precalculate the num_rings, STIR does it for them.

This is method is similar to how GATE handles PET geometries and it doesn't require the user to pre-calculate num_rings and num_detectors_per_ring in the scanner definition. It would also remove the potential issues with getting the value wrong... However, that depreciates functionality and is a rather large change. I propose using the current tooling and then make the switch later if desired, maybe STIR v7?