Division of Nuclear Medicine

SimSET
User Functions

[News] [Installation guide] [User guide] [Programmers' info] [Resources] [Contacts]

Contents

  1. Overview

  2. The user function modules
         2.1 General design and usage
         2.2 Collimator user module
         2.3 Detector user module
         2.4 Binning user module
         2.5 Randoms user module
         2.6 Converting previously existing user functions to the new format


  3. User function examples
         3.1 Example 1:  Sample user functions for addrandoms.c
         3.2 Example 2:  Alternative triples handling for randoms processing algorithm
         3.3
    Other ideas for user functions

 

Overview

User functions provide an easy way of modifying or adding to SimSET.  Furthermore, user functions can be ported to new versions of SimSET easily, unlike changes to the main modules - users only need to reset the pointers and import their user functions and makefile before compiling.

User functions can intercept a photon or event during its processing and modify it, thus altering how it will be processed further downstream.  In the default software, distributed in the src directory, these functions are not called:  the function pointers used to call them are set to 'NULL'.  However, the user can define functions and reset the pointers to these functions - then the user functions will be called during photon/event processing (see figure 1).

 

Figure 1.  General template for user function implementation.  No function is called if userFunctionFPtr is NULL when its value is checked, but if the user declares and defines a function, then sets userFunctionFPtr to the function, the function will be called by the SimSET code.

The collimator, detector, binning, and randoms processing modules contain user function calls like the one shown in Figure 1.  The modules check user function pointers as part of the module initialization and termination procedures.  There are also checks placed at convenient places during photon/event processing.  Thus users can, for instance, create their own output binning arrays in user functions during initialization, perform their own binning with user functions during event processing, and write the arrays to files in user functions during termination.

The pointers for the user function are assigned in the source code files ColUsr.c (for the collimator module), DetUsr.c (for the detector module), PhgUsrBin.c (for the binning module), and addrandUsr.c (for the randoms processing module).  This web page describes how to create user functions, where they are called in each module, and gives examples of how to use them.

[top of page] [collimator] [detector] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 

The User Function Modules

General design and usage

Each of the user function modules contains an initialization function pointer that gets checked at the beginning of a simulation, processing function pointers that get checked for each photon or event, and a termination function pointer that gets checked at the end of the simulation.  If the user has set a funtion pointer to point to a function, the function will be executed when the pointer is checked (as in Figure 1 above).

The user may create initialization and termination functions to allocate/deallocate ‘local global’ and global variables, initialize variables, read in user function parameters, error check input parameters, write output, report results, etc.  However, the meat of the user functions is in processing functions: it is here that the user can alter the flow of the simulation, and the processing functions tend to dictate what needs doing in the initialization and termination functions.  The functions pointed to by processing function pointers come during the photon/event processing and will be passed pointers to the current photon (or coincidence) with all its simulation history and the simulation parameters.  The function can reject the current photon (or coincidence) outright by returning false, or can modify some of the fields in the photon or alter processing parameters, changing how the photon is treated downstream

Below we list each of the user function modules, indicate where their function pointers are checked.  The sections repeat a lot of similar information:  if you are interested in a particular user module, go right to that section.

[top of page] [overview] [detector] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 

Collimator user module

The collimator user module, ColUsr.c, contains four function pointers:  ColUsrInitializeFPtr, ColUsrModPETPhotonsFPtr, ColUsrModSPECTPhotonsFPtr, and ColUsrTerminateFPtr.  Global #defines, variables and structures can be declared in ColUsr.h (if they are needed outside the collimator user module, for instance in one of the other user modules) or at the beginning of ColUsr.c (for 'local globals').

ColUsrInitializeFPtr is checked near the end of ColInitialize, the collimator initialization function in Collimator.c, after the collimator parameters have been read in.   This function pointer allows for allocation of memory and initialization of variables to be used by functions pointed to by ColUsrModPETPhotonsFPtr or ColUsrModSPECTPhotonsFPtrA user function referenced by this pointer will be passed the collimator parameters, found in the data structure *ColRunTimeParamsPtr (the data type is given in ColParams.h), though the user should be circumspect about altering its contents:  the contents would be better modified using the collimator parameter file.  The user could also use this pointer for a function to read the contents of an extra parameter file for these user functions.

Depending on whether the simulation is a PET or SPECT simulation, ColUsrModPETPhotonsFPtr or ColUsrModSPECTPhotonsFPtr is checked when a photon enters the collimator.  User functions referenced by these pointers will be passed pointers to the collimator parameters (ColRunTimeParamsPtr), the decay currently being processed (decayPtr) and the photon currently being tracked (photonPtr).  (The data types for the latter two are in Photon.h.)  The user can modify any of the decay or photon data, thus altering downstream processing, or can discontinue the tracking of the current photon by setting the function return value to false.

ColUsrTerminateFPtr is checked after all photon tracking and event processing as the first step of ColTerminate, the collimator termination function in Collimator.c.  The user should deallocate any memory allocated in the other ColUsr functions, and can report results or write output.

[top of page] [overview] [general design and usage] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 

Detector user module

The detector user module, DetUsr.c, contains four function pointers: DetUsrInitializeFPtr, DetUsrModPETPhotonsFPtr, DetUsrModSPECTPhotonsFPtr, and DetUsrTerminateFPtr.  Global #defines, variables and structures can be declared in DetUsr.h (if they are needed outside the detector user module, for instance in one of the other user modules) or at the beginning of DetUsr.c (for 'local globals').

DetUsrInitializeFPtr is checked near the end of DetInitialize, the detector initialization function in Detector.c, after the detector parameters have been read in.  This function pointer allows for allocation of memory and initialization of variables to be used by functions pointed to by DetUsrModSPECTPhotonsFPtr or DetUsrModPETPhotonsFPtrA user function referenced by this pointer will be passed the detector parameters, found in the data structure *DetRunTimeParamsPtr (the data type is given in DetParams.h), though the user should be circumspect about altering its contents: the contents would be better modified using the detector parameter file.  The user could also use this pointer for a function to read the contents of an extra parameter file for the user functions.

Depending on whether the simulation is a PET or SPECT simulation, DetUsrModSPECTPhotonsFPtr or DetUsrModPETPhotonsFPtr is checked when a photon enters the detector.  User functions referenced by these pointers will be passed pointers to the detector parameters (DetRunTimeParamsPtr), the decay currently being processed (decayPtr) and the photon currently being tracked (photonPtr). (The data types for the latter two are in Photon.h.)  The user can modify any of the decay or photon data, thus altering downstream processing, or can discontinue the tracking of the current photon by setting the function return value to false.

DetUsrTerminateFPtr is checked after all photon tracking and event processing as the first step of DetTerminate, the detector termination function in Detector.c.  The user should deallocate any memory allocated in the other DetUsr functions, and can report results or write output.

[top of page] [overview] [general design and usage] [collimator] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 

Binning user module

The binning user module, PhgUsrBin.c, contains six function pointers: BinUsrInitializeFPtr, BinUsrModPETPhotonsFPtr, BinUsrModPETPhotonsF2Ptr, BinUsrModSPECTPhotonsFPtr, BinUsrModSPECTPhotonsF2Ptr, and BinUsrTerminateFPtr.  Global #defines, variables and structures can be declared in PhgUsrBin.h (if they are needed outside the binning user module, for instance in one of the other user modules) or at the beginning of PhgUsrBin.c (for 'local globals').

BinUsrInitializeFPtr is checked near the end of BinUsrInitializeFPtr, the binning initialization function in PhgBin.c, after the binning parameters have been read in.  This function pointer allows for allocation of memory and initialization of variables to be used by functions pointed to by BinUsrModPETPhotonsFPtr, BinUsrModPETPhotonsF2Ptr, BinUsrModSPECTPhotonsFPtr and BinUsrModSPECTPhotonsF2PtrA user function referenced by this pointer will be passed all the binning parameters, which are found in the data structure *binParams (the data type is given in PhgParams.h), though the user should be circumspect about altering its contents:  the contents would be better modified using the binning parameter file.  The function will also be passed a pointer to the histogram data arrays, *binData (the data type is given in phg.h): in most cases these arrays will be initialized to zeroes at this point, but if the user has selected "add_to_existing_img = true" the renormalized histograms from previous runs will already be stored there.  Again, the user should be circumspect about altering the data.  The user could also use this pointer for a function to read the contents of an extra parameter file for the user functions.

PhgUsrBin.c has two function pointers for functions to process PET photons/events and two for SPECT photons/events.  Functions pointed to by BinUsrModPETPhotonsFPtr and BinUsrModSPECTPhotonsFPtr are called for each photon as it enters the binning function, before any steps are taken to accept/reject it or compute histogram indices for it.  The functions will be passed pointers to the binning parameters (*binParams), the histogram data arrays (*binData), and the decay currently being processed (*decay).  For PET, pointers to the current blue and pink photons will also be passed, *bluePhoton and *pinkPhoton;  for SPECT, a pointer to the current photon (*photon).  (The data types for the photons and decays are in Photon.h.)  The user can modify any of the decay or photon data, thus altering downstream processing, or can discontinue the tracking of the current photon(s) by setting the function return value to false.

The second set of function pointers for processing PET photons/events, BinUsrModPETPhotonsF2Ptr and BinUsrModSPECTPhotonsF2Ptr, are checked just before the output histograms are incremented for the current event.  Note that these pointers are not checked if the event is, for some reason (e.g., energy below the cutoff), rejected within the PhgBin.c binning function.  Functions pointed to by these pointers will be passed the same arguments as above, plus plus pointers to all the indices for binning (please see the PhgBin.c source code file! - there are a lot), and a pointer to the index into the histograms to be updated (*imageIndex).  Please note that *imageIndex must be changed if the histogram position for the event is to be changed:  changing one of the component indices will not change this position as *imageIndex is computed before this function call and is used for incrementing the array after the function call.  The functions will also be passed pointers to the values that will be used when incrementing the output weight and weight-squared histograms, *coincidenceWt and *coincidenceSqWt.

BinUsrTerminateFPtr is checked after all photon tracking and event processing as the first step of PhgBinTerminate, the binning termination function in PhgBin.c.  The user should deallocate any memory allocated in the other PhgUsrBin functions, and can report results or write output.

 

[top of page] [overview] [general design and usage] [collimator] [detector]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 

Randoms user module

The random coincidence user module, addrandUsr.c, contains four function pointers: addrandUsrInitializeFPtr, addrandUsrModDecays1FPtr, addrandUsrModDecays2FPtr, and addrandUsrTerminateFPtr.  Global #defines, variables and structures can be declared in addrandUsr.h (if they are needed outside the randoms user module, for instance in one of the other user modules) or at the beginning of addrandUsr.c (for 'local globals').

addrandUsrInitializeFPtr is checked near the end of adrandInitialize, the addrandoms.c initialization function, after the randoms processing parameters have been read in but before processing of the time-sorted history list starts.  This function pointer allows for allocation of memory and initialization of variables to be used by functions pointed to by addrandUsrModDecays1FPtr and addrandUsrModDecays2FPtrA user function referenced by this pointer will be passed the parameters for randoms processing, *detParams (the data type is given in DetParams.h).  Note that detParams may not be completely filled in, or may be filled in differently than it was for the detector module.  However, the parameters needed for randoms processing will be filled in.  The user should be circumspect about altering the structure's contents: the contents would be better modified using the addrandoms parameter file.  The user can also create an extra parameter file and use this function to read its contents.

addrandUsr.c has two function pointers for functions to process PET photons/events, addrandUsrModDecays1FPtr and addrandUsrModDecays2FPtraddrandUsrModDecays1FPtr is checked before the photons/decays are time-windowed.  A user function referenced by this pointer will be passed the timeWindowDetectionsTy structure (declared in addrandUsr.h).  This structure contains all the decays since the previous check of addrandUsrModDecays1FPtr, all of which will be within the length of the coincidence resolving time of each other (or actually longer: the window is extended each time another photon arrives until the time elapsed between succesive photons is greater than the coincidence resolving time).  The photons and decays may be altered/deleted as desired, e.g. to account for deadtime, different triples handling than SimSET's (currently all triples are deleted).  This function has no return value.

addrandUsrModDecays2FPtr  is checked for each coincidence after the photons/decays are time-windowed.  Only the decays that are actually going to be written out will reach this check - many of the decays/time windows passed to addrandUsrModDecays1 will not generate a call to this function.  A user function referenced by this pointer will be passed the timeWindowDetectionsTy structure, described above, but with only the decays that are to be written out.  The function returns a boolean that determines if the coincidence will be accepted. This gives the user a chance to change/reject the coincidences - whether random, scatter or true - that are getting written out.

addrandUsrTerminate is called after the entire time-sorted history file has been processed as the first step of the addrandoms termination function, adrandTerminate, in addrandoms.c. The user should deallocate any memory allocated in the other addrandUsr functions, and can report results or write output.

 

[top of page] [overview] [general design and usage] [collimator] [detector] [binning]

[example 1] [example 2: PET triples] [other ideas for user functions]


Converting previously existing user functions to the new format

Each of the user function modules has comments to help guide you through the process of implementing your own user function.  Example 1 also gives information about the structure and use of the new user functions.  We suggest you look at both of these to get a better idea of how they work.  However, if you just want to convert a new user function module to use an old user function, we give the steps you need to take below.

In earlier versions of SimSET, the names of the user functions were hard-coded in the calling function.  For instance, if a user wanted to modify PET photon or decay information at the beginning of the binning process, there was a function named PhgUsrBinPETPhotons called at the beginning of PhgBinPETPhotons:
            /* Let user modify and/or reject photons */
            if (PhgUsrBinPETPhotons(binParams, binData,
                    decay, &bluePhoton, &pinkPhoton)  == false) {
               
                /* They rejected it so go to next pink */
                continue;
            }
We distributed SimSET with an empty function called PhgUsrBinPETPhotons in PhgUsrBin.c.  The user modified this function to make their changes.

We have changed the above section of code to execute a user function only if a function pointer is set to something other than NULL, the default setting, so above code has been changed to:
            if (BinUsrModPETPhotonsFPtr &&
                    (*BinUsrModPETPhotonsFPtr)(binParams, binData,
                        decay, &bluePhoton, &pinkPhoton) == false) {
               
                /* They rejected it so go to next pink */
                continue;
            }

In PhgUsrBin.c we have set the above pointer to NULL:
            BinUsrPETTrackingFType        *BinUsrModPETPhotonsFPtr = NULL;
Thus no function is called.

To include an old user function one must make one's own copy of PhgUsrBin.c and

  1. replace the 'NULL' above with the user function name (let's call it myPETphotonMods):
        BinUsrPETTrackingFType        *BinUsrModPETPhotonsFPtr = myPETphotonMods;
  2. place a prototype for the function in the section called Local Prototypes - the prototype and function should have the same argument types as those used in the call to *BinUsrModPETPhotonsFPtr in PhgBinPETPhotons:
        Boolean   
    *myPETphotonMods(PHG_BinParamsTy *binParams, PHG_BinDataTy *binData,
                        PHG_Decay *decay,
                        PHG_TrackingPhoton *bluePhoton,
                        PHG_TrackingPhoton *pinkPhoton);
  3. place the function source code in your version of PhgUsrBin.c - in our examples we put the user functions in include files.

We encourage you to work through Example 1 below to develop familiarity with the new user function format.

[top of page] [overview] [general design and usage] [collimator] [detector] [binning] [randoms]

[example 1] [example 2: PET triples] [other ideas for user functions]


User Function Examples

Overview

The following two user function examples attempt to show how to use the function pointers and how user functions can make fundamental changes to SimSET.

The first example is an exceedingly simple one, intended to give a basic how-to for writing user functions.  For those who have never written a SimSET user function, it is the place to start.  It modifies the addrandoms.c user functions, found in addRandUsr.c, to increment and report a pair of counters.

The second example expands on the first to do something really useful with the addrandoms.c user functions.  SimSET's randoms module discards time windows with three or more photons, as many tomographs did so when it was designed.  In the interim, however, tomographs have started using all possible pairings of the three or more photons as potential coincidences.  This example changes addrandoms to create all possible pairings of the photons.

[top of page] [overview] [general design and usage] [collimator] [detector] [binning] [randoms]

[converting old user functions] [example 2: PET triples] [other ideas for user functions]


 

 Example 1: Sample user functions for addrandUsr.c

This example can be found in the SimSET subdirectory samples/userFuncExamples/example1.

Problem:

This example gives a very simple use of the user functions in addrandUsr.c (figure 2).  New functions are created to initialize, increment and report two counters.  All four of the function pointers in addrandUsr.c are reassigned from NULL (the value they are given in the version in the main src directory) to functions defined in the file addrandUsrSample.c.

Flowchart for addrandoms.c:  Figure addrandUsr_fig1.jpg didn't load.

Figure 2:  Flowchart for addrandoms.c.  In SimSET’s main source directory the function pointers declared in addrandUsr.c (addrandUsrInitializeFPtr, addrandUsrModDecays1FPtr, addrandUsrModDecays2FPtr, and addrandUsrTerminateFPtr, shown in red) point to NULL.  As a result, the conditional statements are evaluated as ‘false’.  In this example we reset these pointers to point to functions that we define – the conditional statements then take the ‘true’ branch.  The functions are executed at critical junctures in addrandoms.c.

Sample user functions description:

A1.    There are four functions in addrandUsrSample.c.  Each has a comment about where it will be called in the addrandoms.c execution stream. 
A2.    Together the four functions initialize, increment, and report the final value of two counters.
A3.    The function addrandUsrSampleInitialize initializes two counters, numModDecays1Calls and numModDecays2Calls, to zero.  It is called as part of the initialization in addrandoms.c.
A4.    The function addrandUsrSampleModDecays1 increments numModDecays1Calls.  addrandoms.c calls addrandUsrSampleModDecays1 with every time window containing two or more photons.
A5.    The function addrandUsrSampleModDecays2 increments numModDecays2Calls.  addrandoms.c calls addrandUsrSampleModDecays2 just before it writes each output coincidence to the history file.
A6.    The function addrandUsrSampleTerminate reports the values of the counters numModDecays1Calls and numModDecays2Calls.  It is called as part of the termination of addrandoms.c.

Modifications made to use addrandUsrSample.c:

(This section explains the modifications necessary implement A1-A6 above.  You can use these modifications as a template for your own user functions.)  To implement the above changes, we created one new file, samples/userFuncExamples/example1/src/addrandUsrSample.c, and modified two others: samples/userFuncExamples/example1/src/addrandUsr.c replaces src/addrandUsr.c and samples/userFuncExamples/example1/make.files/simset.make replaces make.files/simset.make.  We also included a copy of make_all.sh in the example1 directory, but this is identical to the one in the main SimSET directory (but it uses the simset.make in the example1 directory).

Comparing the version of addrandUsr.c in the samples/userFuncExamples/example1/src directory with the addrandUsr.c distributed in the main src directory, and the version of simset.make in samples/userFuncExamples/example1/make.files directory with the simset.make distributed in the main make.files directory, we see the following changes:

M1.    (Several comments in addrandUsr.c have been changed to highlight the changes made for the example version.)
M2.    The file addrandUsrSample.c has been #included in addrandUsr.c.
M3.    The four function pointers are reset from NULL to the functions in addrandUsrSample.c.
M4.    The user function templates addrandUsrInitialize, addrandUsrModDecays1, addrandUsrModDecays2, and addrandUsrTerminate are deleted.
M5.    The major additions are inside the include file addrandUsrSample.c: the new functions described in A1-6 above, along with the ‘local global’ counter variables, are defined.
M6.    In simset.make, following the comments explaining how to use user functions, SIMSET_PATH_USR has been changed from $SIMSET_PATH to the example1 directory, …/samples/userFuncExamples/example1.  (Note that the user must change SIMSET_PATH_USR to the directory path for example1 for their installation.  SIMSET_PATH should also be set to the path for the main SimSET build.)
M7.    Near the end of simset.make, two references to ${PHG_SRC}/addrandUsr.c have been changed to references to ${PHG_SRC_USR}/addrandUsr.c.  Again, this follows the instructions for using user functions in the file.

There are comments in addrandUsr.c and simset.make to help you make the changes necessary to implement your own user functions.

Usage:

To use the new user module, the SimSET program must be recompiled/linked.  To do this, SIMSET_PATH_USR and SIMSET_PATH in .../example1/make.files/simset.make must be set to match the user's installation, as noted above in M6:

(from the simset.make in the example1/make.files directory...)
# Setup paths (change SIMSET_PATH to the directory where you installed SimSET)
SIMSET_PATH = /Users/roberth/dev                       << change to the path for your main SimSET directory

# If you are building a separated user function version of SimSET,
#       change SIMSET_PATH_USR to the top directory of your separate version.
# You will also need to change the user function file instructions at the end of this file.
# The default is to have everything in the same directory path.
SIMSET_PATH_USR = /Users/roberth/dev/samples/userFuncExamples/example1         << change to the path for your example1 directory

(The change noted above in M7 has already been made to the simset.make in the example 1 directory.)

The program is then compiled and linked by running the copy of make_all.sh in .../example1:

./make_all.sh

(NOTE:  if you are using an old version of gcc, make_all.sh may fail when compiling addrandUsr.c, complaining that it cannot find many of SimSET's header files.  To fix this, go to the line in simset.make where CFLAGS is defined.  Comment this line out with a #, and try one of the two options for CFLAGS that follow by deleting it's #.  If the first option doesn't work, try the second.)

The example1 directory includes parameter files for a test run.  These parameter files and the commands to run them are the same as those for the normal SimSET software – indeed, the user functions supplied in addrandUsrSample.c will not change the results of the simulation at all, except to add a few lines reporting the new counting variables at the end of the addrandom screen output.  We have provided a script to run the example:

./run_addrandUsrSample1.sh. 

Results:

The simulation runs exactly as it would using the distributed version of SimSET except that the following lines appear at the end of the screen output from addrandoms (found in PETblockrand.arout):
    SAMPLE ADDRAND USER MODULE REPORT
    Number of calls to addrandUsrSampleModDecays1 is 22950
    Number of calls to addrandUsrSampleModDecays2 is 3978

The last two lines report the values of counters incremented each time the specified function is called.  (The numbers reported may vary with your installation.)

[top of page] [overview] [general design and usage] [collimator] [detector] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 Example 2: Alternative triples handling for randoms processing algorithm

This example can be found in the SimSET subdirectory samples/userFuncExamples/example2.

Problem:

SimSET’s current randoms processing algorithm rejects all photons in any coincidence timing window with more than three photons in it (so called triple coincidences or triples – see figure 3 for examples).  However, many tomographs will create both true and random coincidences with these photons.  The exact algorithm used for pairing up photons in a coincidence timing window is tomograph-specific and complicated, but generally is closer to accepting all possible pairings of photons in a coincidence timing window rather than rejecting all triples as SimSET does.  This example gives a user function that accepts all photon pairs detected within a the coincidence resolving time of each other (figure 4).  Note that coincidences may still be discarded later in the processing stream, e.g., when an energy window is applied in the binning module. 

Unable to load figure 3.

Figure 3:  A time line showing detected photons and the coincidence resolving time opened by each.  If another photon is detected within this coincidence resolving time, it is grouped in the previous photon's coincidence timing window.  (a) The first photon opens a coincidence timing window, but no other photon arrives within the coincidence resolving time.  Unmodified, SimSET will discard the photon.  (b) The second photon opens a coincidence timing window.  The third photon arrives within the coincidence resolving time and extends the coincidence timing window by the coincidence resolving time, within which a fourth photon arrives, extending the window by the coincidence resolving time again, within which time a fifth photon arrives once again extending the window.  No further photons arrive in the window.  Unmodified, SimSET will group the second through fifth photons in one time window and reject all of them (all photons in a time window with three or more photons are rejected). (c) The sixth and seventh photons arrive within the coincidence resolving time of each other, and no further photons arrive within the extended window after the seventh photon.  Unmodified, SimSET will accept this pair as a coincidence (a normal coincidence if the photons are from the same decay; a random coincidence if they are from different decays).  (d) Similar to (b) – the eighth through tenth photons are discarded as there are three photons in the coincidence timing window.  The photons detected in the time windows (b) and (d) are referred to as triples.


Figure 4 can not be loaded.

Figure 4:  The photons from time windows (b) and (d) in figure 2 would all be discarded by SimSET’s normal triples processing.  The triples processing in the addrandUsr functions given for this example will, instead, accept any coincidence between photons within the coincidence resolving time of each other.  Thus, in time window (b) from figure 3, the new triples processing will create coincidences between the first two photons and between every pairing of the 2nd through 4th photons (2-3, 2-4, 3-4).  The 1st photon will not be paired with the 3rd or 4th photon as they are separated by more than the coincidence resolving time .  Similarly, in time window (d) coincidences will be formed from the 1st and 2nd photons and the 2nd and 3rd photons, but not from the 1st and 3rd photons, which are separated by more than the coincidence resolving time.  Note that a single photon may be involved in more than one coincidence, e.g. the 2nd photon from window (b) is involved in three coincidences.

User function triples algorithm description:

A1.    The function is passed a time window structure containing all the decays with their detected photons that occurred within the time window (i.e., the structure will contain the decay and photon information for a collection of photons like those shown grouped together in figure 3 as group a, b, c, or d).
A2.    The function makes no changes to the processing of time windows containing 2 or less photons (i.e., in figure 3 windows a and c would be returned unchanged to the calling function).  
A3.    For other windows it accepts every pair of photons within the structure that were detected with a time difference less than the coincidence resolving time, writing them to the addrandoms output history file (i.e., for windows b and d in figure 3, each pair of photons identified as coincidences in figure 4 will be written out as a decay).  The time window structure itself is returned empty as all the acceptable decays are already written out to the history file.  
A4.    Accepted photon coincidences are labeled as true events if they are from the same decay, random events otherwise. 
A5.    The counters that keep track of the number of decays written out, number of decays written out unchanged, the number of randoms created, and the number of decays lost to correct time windowing are updated as the user function writes out events.
A6.    Two new counters are initialized, incremented and reported:  the number of true events created from triples and the number of random events created from triples.

Modifications made to realize the new triples algorithm:

(This section explains the modifications necessary implement A1-A6 above.  You can use these modifications as a template for your own user functions.)  To implement the above changes, we created one new file, samples/userFuncExamples/example2/src/addrandUsr_procTriples.c, and modified two others: samples/userFuncExamples/example2/src/addrandUsr.c replaces src/addrandUsr.c and samples/userFuncExamples/example2/make.files/simset.make replaces make.files/simset.make.  We also included a copy of make_all.sh in the example2 directory, but this is identical to the one in the main SimSET directory (but it uses the simset.make in the example2 directory).

Comparing the version of addrandUsr.c in the samples/userFuncExamples/example2/src directory with the addrandUsr.c distributed in the main src directory, and the version of simset.make in samples/userFuncExamples/example2/make.files directory with the simset.make distributed in the main make.files directory, we see the following changes:

M1.    addrandUsr.c includes addrandUsr_procTriples.c as a #include file.
M2.    Three of the function pointers in addrandUsr.c have been reassigned to functions found in addrandUsr_procTriples.c:  *addrandUsrInitializeFPtr to procTriplesInitialize, *addrandUsrModDecays1FPtr to procTriplesProcess and *addrandUsrTerminateFPtr to procTriplesTerminate. *addrandUsrModDecays2FPtr still points to NULL.
M3.    In addrandUsr_procTriples.c we declare a procTriplesVars structure containing a field for the user supplied coincidence resolving time and the new counters described in (A7) above.
M4.    The function procTriplesInitialize in addrandUsr_procTriples.c initializes the fields in procTriplesVars.
M5.    The function procTriplesProcess in addrandUsr_procTriples.c fulfills the requirements given by (A1-6) above, i.e., to examine all pairing of photons in any time window containing 3 or more photons, write any pair that occur within the coincidence resolving time of each other to the addrandoms history file, and update the appropriate counters.  To keep addrandoms.c from reporting that decays have been discarded because they were triples, any time window that contained 3 or more photons has its number of decays changed to 0 at the end of triples processing.
M6  The function procTriplesTerminate in addrandUsr_procTriples.c reports the counters from procTriplesVars. 
M7.    In simset.make, following the comments explaining how to use user functions, SIMSET_PATH_USR has been changed from $SIMSET_PATH to the example2 directory, …/samples/userFuncExamples/example2.  (Note that the user must chnage SIMSET_PATH_USR to the directory path for example2 for their installation.  SIMSET_PATH should also be set to the path for the main SimSET build.)
M8.    Near the end of simset.make, two references to ${PHG_SRC}/addrandUsr.c have been changed to references to ${PHG_SRC_USR}/addrandUsr.c.  Again, this follows the instructions for using user functions in the file.

There are comments in addrandUsr.c and simset.make to help you make the changes necessary to implement your own user functions.

Usage:

(For more step-by-step instructions, see the usage section for example 1.  The instructions are identical - aside from the directory name.)  To use the new user module, the SimSET program must be recompiled/linked.  To do this, SIMSET_PATH_USR and SIMSET_PATH in .../example2/make.files/simset.make must be set to match the user's installation, as noted in M7 above.  The program is then compiled and linked by running the copy of make_all.sh in .../example2.  The software is run using the same parameter files and commands as the normal version – SimSET will run 'normally', though the results it produces will be different due to the new triples processing algorithm. 

There are sample parameter files for a run in this directory as well.  Run the sample using runPETblockrand.sh.

Results:

The simulation runs exactly as it would using the distributed version of SimSET except that some photons that are normally discarded because they are part of triple coincidences instead get passed on as photons in coincidences.  The numbers of such coincidences are reported at the end of the output from addrandoms, found in the file PETblockrand.arout (the numbers in your run may be slightly different):
    TRIPLES PROCESSING USER MODULE REPORT
    Number of single decay events created from triples is 4002
    Number of random events created from triples is 9764

The single decay events are two photons from the same decay, so either true or scatter coincidences.  The random events are coincidences created by pairing photons from two different decays.  These extra coincidences are added to addrandom's output history file and passed on to the binning module.  Looking at the total detected coincidences after the binning file (in PETblockrand.binout), we see:
    Total accepted coincidences in this simulation = 23937

Doing a regular SimSET run using the same parameter files, we see there are less accepted coincidences:
    Total accepted coincidences in this simulation = 18360

The difference between the total accepted coincidences is due to the events created from triples.  Note that not every event created from triples becomes an accepted coincidence:  the events can still be rejected, e.g. if one of the photons does not fall inside the energy window set in the binning parameters file.


[top of page] [overview] [general design and usage] [collimator] [detector] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


 Other ideas for other user functions

Below we offer possibilities for other user functions; we would also like to solicit examples from users to add to this page.

User functions are most useful in cases where a few changes to the attributes of a photon or decay can improve the realism of the simulation or add a new functionality. They can even be used to supplant the entire functionality of their module with a new model, for instance a new collimator. Below we suggest a few possible uses for each of the user function modules. Note that the ColUsr.c and DetUsr.c photon tracking functions are called only as the photon enters the collimator or detector, respectively; thus, to modify a photon as the photon exits the collimator or detector, one must use the user functions in the next module, e.g. DetUsr.c, addranUsr.c, or PhgUsrBin.c.

Collimator user functions: ColUsr.c

- Unsupported collimators. If the collimator to be simulated is different from those SimSET supports, the user could implement their own collimator here.

- Modifications to existing collimators. For instance, for a collimator like one of those currently offered, but which doesn't cover the full angular range, the user could reject photons that would fail to hit the collimator in ColUsrPETPhotons or ColUsrSPECTPhotons.

Detector user functions: DetUsr.c

- Pinhole collimator. One might be able to simulate a pinhole collimator by using a PET collimator simulation, creating a collimator that used the axial profile of the pinhole to define the axial composition of the PET septa. The transaxial and off-axis components of the pinhole would then be approximated in the DetUsr.c function DetUsrSPECTPhotons. Photons are passed to this function before they enter the detector. Projecting the photon position/direction back to the collimator ring, one can determine where the 'pinhole' would have to have been for the photon to pass through; this value can be stored in the photonPtr field detectorAngle. The user binning module, PhgUsrBin, would then use this value, along with the detected position of the photon in a planar detector, to determine the correct position to bin the photon. This pinhole collimator approximation would not accurately reflect collimator penetration or collimator scatter, though some tabular approximations of these effects could be used to improve the modeling. Note, as mentioned above, that this modification of the collimator is implemented in the DetUsr and PhgUsrBin files, rather than in the ColUsr files.

Binning user functions: PhgUsrBin.c

- Create a new binning variable. Users can either do their own binning, using an initialization function pointed to by BinUsrInitializeFPtr to set up their output array and binning parameters, or alter the photon(s) to trick SimSET into binning the new variable into an otherwise unused variable. For example, a user not binning by photon energy can, nevertheless, set SimSET up to bin by energy, but then use PhgUsrBinPETPhotons or PhgUsrBinSPECTPhotons to change the photon energy to a value corresponding to the desired bin for the new binning variable.

- Change detected position. Most of the SimSET detectors position photons at the energy-weighted centroid of the interactions in the crystal. This is not an accurate representation of how some detectors work. In PhgUsrBinPETPhotons or PhgUsrBinSPECTPhotons the user has access to all the interaction information (position and energy deposited for each interaction) and can alter the detected position before the photon (or coincidence) is binned.

- A depth-of-interaction capability to the detectors.  We plan to release another example that does this in the near future.

- Weight events based on their history or bin. For example, when imaging 140 keV photons, one might want to give more credence (a higher weight) to a photon detected at 145 keV than 135 keV (a scatter photon is less likely to register the former than a true photon). This would be done in PhgUsrBinPETPhotons2 or PhgUsrBinSPECTPhotons2.

- Approximating detector gaps. This is a possibility that our group used quite successfully to approximate block detectors using the cylindrical detector model before the block detector software was available. We determined in PhgUsrBinPETPhotons (or PhgUsrBinSPECTPhotons) if a photon had interacted in parts of the cylindrical detector that would have been in the gaps between the block detectors we were trying to simulate and altered the detected position and energy of the photon accordingly. While this particular approximation is no longer necessary, similar approximations might be useful for structures in the detector that are not well represented by right-rectangular boxes. Note, as mentioned above that this modification of the detector is implemented in the PhgUsrBin files, rather than in the DetUsr files.

Randoms module user functions: addrandUsr.c

- The first two examples given above show user functions for addrandoms.

- Detector deadtime and pulse pileup could be implemented in set of user function similar to the triples processing shown in example 2.

[top of page] [overview] [general design and usage] [collimator] [detector] [binning] [randoms]

[converting old user functions] [example 1] [example 2: PET triples] [other ideas for user functions]


Last revised by: Robert Harrison
Revision date: 15 January 2014