|
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]
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 ColUsrModSPECTPhotonsFPtr. A 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 DetUsrModPETPhotonsFPtr. A 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 BinUsrModSPECTPhotonsF2Ptr. A 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 addrandUsrModDecays2FPtr. A 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 addrandUsrModDecays2FPtr. addrandUsrModDecays1FPtr 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
- replace the 'NULL' above with the user function name (let's call it myPETphotonMods):
BinUsrPETTrackingFType *BinUsrModPETPhotonsFPtr = myPETphotonMods;- 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);
- 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]
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.
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:
(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:
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]
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.
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:
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