Project

General

Profile

Writing Detector Code » History » Version 6

Ole Hansen, 03/05/2018 02:57 PM

1 1 Ole Hansen
h1. How to write a detector class for the C++ analyzer
2
3
{{TOC}}
4
5
h2. Getting started
6
7
Before you start programming your new detector class, you will need to make a few decisions:
8
9 3 Ole Hansen
* Which apparatus(es) will your detector belong to? If it is an existing apparatus, for example "@THaHRS@":http://hallaweb.jlab.org/podd/html/THaHRS.html, then your detector class needs to be included in that apparatus's list of detectors. Including it there is very easy, just use the "@AddDetector()@":http://hallaweb.jlab.org/podd/html/THaApparatus.html#THaApparatus:AddDetector or write a derived apparatus class that creates the detector in its constructor (see [[Adding Detectors|adding and removing detectors]]). If it is a new apparatus (_e.g._ a new spectrometer), you will need to write a class for it as well (see [[Adding a new apparatus|creating an Apparatus]]).
10
 
11
* Does you detector resemble an existing detector? For example, a new scintillator should normally resemble the existing scintillators ("@THaScintillator@":http://hallaweb.jlab.org/podd/html/THaScintillator.html). If so, you may be able to use the existing class. You will need to think of a unique name for the detector and compile a new database entry for it, in particular geometry information (e.g. number of paddles).
12
 
13 1 Ole Hansen
If an existing detector class is not suitable for your detector, _e.g._ because the behavior of certain functions need to be different, then you should let your new class to inherit from the existing class, overriding only those functions.
14 3 Ole Hansen
 
15
To emphasize: It is *not* always necessary to write a new class for a new detector! If you have several essentially identical detectors (_e.g._ scintillators), you should try to write a *single* class that can handle all of them. Each object of this class type will then represent one of these detectors. The individual detectors are distinguished by their unique name (see [[Naming Conventions]]).
16
 
17 1 Ole Hansen
* If your detector does not resemble any existing one, then your detector class should inherit directly from one of the generic detector classes:
18 3 Ole Hansen
** "@THaDetector@":http://hallaweb.jlab.org/podd/html/THaDetector.html is the simplest base class. It can be used for beamline instrumentation (_e.g._ @THaBPM@) and similar miscellaneous equipment.
19
** "@THaSpectrometerDetector@":http://hallaweb.jlab.org/podd/html/THaSpectrometerDetector.html is a @THaDetector@ to be included in a "@THaSpectrometer@":http://hallaweb.jlab.org/podd/html/THaSpectrometer.html. Actual detector classes should not inherit directly from this class, but rather from one of the following three, unless you know exactly what you are doing.
20
** "@THaTrackingDetector@":http://hallaweb.jlab.org/podd/html/THaTrackingDetector.html can be used as a base class for implementing tracking detectors such as VDCs. Here, a "tracking detector" represents equipment that is used to find the "physics tracks" in the spectrometer. In general, "physics tracks" are tracks that can be used for target/vertex reconstruction.
21
Note: Some detectors, such as an FPP, do reconstruct tracks, but those tracks are not the "physics tracks". Rather, they are "instrumentation tracks" that are used within the detector only. Thus, an FPP should not be a @THaTrackingDetector@, but rather a @THaNonTrackingDetector@.
22
** "@THaNonTrackingDetector@":http://hallaweb.jlab.org/podd/html/THaNonTrackingDetector.html is the base class for any spectrometer detectors that are not tracking detectors, _e.g._ scintillators.
23
** "@THaPidDetector@":http://hallaweb.jlab.org/podd/html/THaPidDetector.html is a specialized @THaNonTrackingDetector@ that provides support for PID. Use it as the base class for all true PID detectors, _e.g._ Cherenkovs and shower counters.
24
Note: For performance reasons, do not use this class if your detector currently does not do any PID calculations, even though it might in principle be used for PID (_e.g._ scintillators). If you add PID calculations later, all you have to do is change the class inheritance to add PID support.
25 1 Ole Hansen
26 3 Ole Hansen
p(((. If appropriate, create your own detector subclass structure, e.g. @THaPidDetector@ <- @THaCherenkov@ <- @THaGasCherenkov@. This might result in a cleaner design and might help avoid code duplication. However, be careful not to overcomplicate things. The above scheme for Cherenkovs could be useful if there is at least one common method for all Cherenkovs (_e.g._ PID processing). 
27 1 Ole Hansen
28
Once you have decided where your new detector fits into the class hierarchy, take one of the existing classes as a template and start rewriting it. There are a few requirements to keep in mind. Your class must
29
30 3 Ole Hansen
* inherit from "@THaDetector@":http://hallaweb.jlab.org/podd/html/THaDetector.html or one of its subclasses;
31
* define a [[Writing Detector Code#Decoding|Decode()]] method;
32
* if it is a "@THaNonTrackingDetector@":http://hallaweb.jlab.org/podd/html/THaNonTrackingDetector.html, define a [[Writing Detector Code#Coarse-processing|CoarseProcess()]] and [[Writing Detector Code#Fine-processing|FineProcess()]] method; these can be dummy routines if no special processing is needed or if you don't know yet what best to do here;
33
* if it is a "@THaTrackingDetector@":http://hallaweb.jlab.org/podd/html/THaTrackingDetector.html, define a [[Writing Detector Code#Fine-tracking|CoarseTrack()]] and [[Writing Detector Code#Fine-tracking|FineTrack()]] method; again, these can be dummy routines if appropriate. 
34 1 Ole Hansen
35 3 Ole Hansen
Also, your class should define appropriate [[Writing Detector Code#Initialization|initialization]] routines, _i.e._ an @Init()@ method and/or a @ReadDatabase()@ and/or @DefineVariables()@ method. These methods should obtain all detector-specific parameters (geometry, mapping, calibrations, etc.) from a database and should register any symbolic variables to be accessible for dynamic tests and histograms in the global variable list @gHaVars@.
36 1 Ole Hansen
37 3 Ole Hansen
A simple example for a detector class is the generic scintillator class "@THaScintillator@":http://hallaweb.jlab.org/podd/html/THaScintillator.html.
38 1 Ole Hansen
39
h2. Initialization
40
41
Every detector must have a public Init() method of type
42
43
<pre>
44
THaAnalysisObject::EStatus Init( const TDatime& date )
45
</pre>
46
47
This method should obtain all detector-specific parameters (geometry, mapping, calibrations, etc.) from the database. It should not be called from the constructor(s). @Init()@ is called by the standard analyzer at the beginning of the analysis of a run. The @TDatime@ object that is passed contains the time stamp of the prestart event of the run. It should be used to do time-dependent initializations, such as retrieving appropriate calibration parameters from the database. While you can write your own Init() function, it is recommended that you use the default @Init()@ provided by the @THaAnalysisObject@ base class. This default function will establish the connection to the database for you. In analyzer version 1.0, the database is simply a collection of plain text files. There is support time-dependence with a 1-day granularity and less (see database). If you do use the default @Init()@ method, you should (but do not have to) implement one or both of the following methods in your detector class. (These methods may be protected to prevent direct calls.)
48
49
<pre>
50
virtual Int_t ReadDatabase( const TDatime& date )
51
52
virtual Int_t DefineVariables( Emode mode )
53
</pre>
54
55
The first action in @ReadDatabase()@ should be to open the plaintext database file corresponding to the detector (as determined by the detector's and containing apparatus's names) as well as the time stamp of the current run. A convenience function @OpenFile()@ is available for this purpose. You can use the @FILE*@ handle returned by @OpenFile()@ with standard C file I/O commands (@fscanf@, @fgets@ etc.) to read the file. The file must be closed before leaving @ReadDatabase()@.
56
57
@DefineVariables()@ is called after @ReadDatabase()@ and should be used for setting up global symbolic variables. If your detector does not need a database file, you can put your initializations either in @Init()@ or in @DefineVariables()@, or write a @ReadDatabase()@ method that does not actually open any files. If you do not need any initialization, you do not need to implement any functions.
58
59
h2. Global Variables
60
61
Any data of your detector that you want to be accessible within the global analyzer context should be registered as global symbolic variables in the global list @gHaVars@. Any variables that you want to have available in tests/cuts or output to a ROOT file must be registered in this way. You can register variables either with a series of calls to
62
63
<pre>
64
gHaVars->Define( ... )
65
</pre>
66
67
or with a single call to
68
69
<pre>
70
DefineVariables( const VarDef* vars )
71
</pre>
72
73
The second form is recommended. Both forms can be mixed since @DefineVariables()@ simply make the appropriate calls to @Define()@. You must call @Define()@ explicitly in order to define multidimensional arrays.
74
75
@VarDef@ is an array whose entries have the structure (see @VarDef.h@)
76 4 Ole Hansen
<pre><code class="cplusplus">
77 1 Ole Hansen
struct VarDef {
78
  const char*      name;     // Variable name
79
  const char*      desc;     // Variable description
80
  Int_t            type;     // Variable data type (see VarType.h)
81
  Int_t            size;     // Size of array (0/1 = scalar)
82
  const void*      loc;      // Location of data
83
  const Int_t*     count;    // Optional: Actual size of variable size array
84
};
85 4 Ole Hansen
</code></pre>
86 1 Ole Hansen
A typical sequence for defining global variables would be
87 4 Ole Hansen
<pre><code class="cplusplus">
88 1 Ole Hansen
#include "VarDef.h"
89
90
VarDef vars[] = {
91
  { "nhit", "Number of hits",  kInt,   0,      &fNhit },
92
  { "adc",  "ADC values",      kFloat, fNelem, fADC },
93
  { "csum", "Cluster sums",    kFloat, MAXCLU, fCsum, &fNclusters },
94
  { NULL }
95
};
96
DefineVariables( vars );
97 4 Ole Hansen
</code></pre>
98 1 Ole Hansen
99
This will register three global variables called "@nhit@", "@adc@", and "@csum@", prefixed with the apparatus name and detector name, _e.g._ "@L.s1.nhit@". The data types (@kInt@, @kFloat@, see @VarType.h@) *must* correspond exactly to the types of the variables (5th field). "@nhit@" is a scaler (@size = 0@), "@adc@", a fixed-size array of size @fNelem@, and "@csum@" is a variable-size array of maximum size @MAXCLU@ and actual size contained in the variable fNclusters. The actual size is allowed to change from event to event. The 5^th^ field (@&fNhit@, @fADC@, @fCsum@) must be the address of the variable or the first array element. (You could also write @&fADC[0]@ instead of @fADC@ if you like to type a lot.) The 6^th^ field (@&fNclusters@), if not @NULL@, *must* be the address of a variable of type @Int_t@. (If the 6^th^ field is not specified, it is automatically initialized to @NULL@.) The definitions *must always* end with a final @{ NULL }@ entry or your program will crash.
100
101
h2. Decoding
102
103
Every detector must have a @Decode()@ method. It must be of type
104
105
<pre>
106
Int_t Decode( const THaEvData& evdata )
107
</pre>
108
109
Within the standard analyzer, Decode() will be called for every physics event. (You can override this behavior if you use your own apparatus class.) The event's raw data can be accessed via the methods of the @THaEvData@ object that is passed as the argument of Decode(). At the minimum, your Decode() function should find the detector's information in the event buffer and move it to data members of your class. Of course you may apply further processing if appropriate.
110
111
Within @Decode()@ you must only do processing that does not require information from any other detectors. If you need information from other detectors, put the corresponding code either in the @CoarseProcess()@/@CoarseTrack()@ or @FineProcess()@/@FineTrack()@ method of the detector class or in the @Reconstruct()@ method of the apparatus class to which your detector belongs.
112
113
h2. Coarse processing
114
115
The @CoarseProcess()@ method is relevant only for non-tracking detectors. Its type is
116
117
<pre>
118
Int_t CoarseProcess( TClonesArray& tracks )
119
</pre>
120
121
In the standard analyzer, @CoarseProcess()@ is called for every non-tracking detector after @CoarseTrack()@ has been called for all tracking detectors. Information on the tracks found for the current event, if any, is passed in the @TClonesArray@. This array contains zero or more @THaTrack@ objects. The track information is "Coarse", i.e. only preliminary reconstruction has been performed.
122
123
Here is a simple example how to retrieve the information from all tracks passed in the array:
124 5 Ole Hansen
<pre><code class="cplusplus">
125 1 Ole Hansen
#include < THaTrack.h >
126
127
int ntrack = tracks.GetLast()+1; 
128
129
for( int i = 0; i < ntrack; i++ ) {
130
  THaTrack* theTrack = static_cast < THaTrack* > ( tracks.At(i) );
131
  if( !theTrack ) continue;
132
  Double_t fpx    = theTrack->GetX();     // x-position in fp
133
  Double_t fpy    = theTrack->GetY();     // y-position in fp
134
  Double_t fpth   = theTrack->GetTheta(); // theta wrt normal to fp
135
  Double_t fpph   = theTrack->GetPhi();   // phi wrt fp x-axis
136
  //... do computations ...
137
}
138 5 Ole Hansen
</code></pre>
139 1 Ole Hansen
Within @CoarseProcess()@, you cannot generally use information from any other detectors except tracking detectors. There is no guarantee that the non-tracking detectors are processed in any particular order. Therefore, this method should operate only on the tracks passed in the array.
140
141
h2. Fine processing
142
143
Like @CoarseProcess()@, the @FineProcess()@ method is relevant only for non-tracking detectors. Its type is
144
145
<pre>
146
Int_t FineProcess( TClonesArray& tracks )
147
</pre>
148
149
@FineProcess()@ is called for every non-tracking detector after all focal-plane track reconstruction has been done by the tracking detectors. Thus, this method should be used for any detector-specific processing that requires precise track information. Track information can be obtained from the @TClonesArray@ of @THaTracks@ in the same manner as described above for @CoarseProcess()@.
150
151
Within @FineProcess()@, you can use all "coarse processing" information from all other detectors as well as all information from all tracking detectors. To access information from another detector, either retrieve a global physics variable defined by that detector or use the public interface of that detector. For example:
152 6 Ole Hansen
<pre><code class="cplusplus">
153 1 Ole Hansen
// Retrieve global physics variable called "r.s.x"
154
#include "THaVarList.h"
155
#include "THaVar.h"
156
157
Double_t x = kBig;
158
THaVar* pvar = gHaVars->Find("r.s.x");
159
if( pvar )
160
  x = pvar->GetValue();
161 6 Ole Hansen
</code></pre>
162 1 Ole Hansen
or
163 6 Ole Hansen
<pre><code class="cplusplus">
164 1 Ole Hansen
// Call method GetX() of detector "mydet"
165
#include "THaApparatus.h"
166
#include "THaMyDet.h"       //as appropriate
167
168
Double_t x = kBig;
169
THaMyDet* mydet = static_cast< THaMyDet* >( fApparatus->GetDetector("mydet"));
170
if( mydet )
171
  x = mydet->GetX();
172 6 Ole Hansen
</code></pre>
173 1 Ole Hansen
174
h2. Coarse tracking
175
176
This method is only relevant for tracking detectors. It has the type
177
178
<pre>
179
Int_t CoarseTrack( TClonesArray& tracks )
180
</pre>
181
182
@CoarseTrack()@ is called immediately after @Decode()@ has been called for all detectors. The @TClonesArray@ of tracks is empty when the function is called and is intended to be an output variable. Tracking detectors are not guaranteed to be processed in a particular order, so the only information that can be assumed to be valid in this function is the decoded data for all detectors.
183
184
@CoarseTrack()@ is expected to reconstruct tracks from the decoded data in the fastest possible way, create @THaTrack@ objects, and put them in the output array. For convenience, the @THaTrackingDetector@ base class defines a method @AddTrack@ that can be used to add tracks to the array. This method should be be used for this purpose unless there is a good reason not to.
185
186
h2. Fine tracking
187
188
Like @CoarseTrack()@, this method is only relevant for tracking detectors. It has the type
189
190
<pre>
191
Int_t FineTrack( TClonesArray& tracks )
192
</pre>
193
194
@FineTrack()@ is called after @Decode()@, @CoarseTrack()@, and @CoarseProcess()@ have been executed for all detectors. Upon entry, the @TClonesArray@ of tracks contains the tracks found by @CoarseTrack()@. Tracking detectors are not guaranteed to be processed in a particular order, so the only information that can be assumed to be valid in this function is the decoded and coarse-processed data for all detectors.
195
196
@FineTrack()@ is expected to reconstruct tracks in the most precise way possible, using the decoded data and the coarse tracks as input. The function should modify the tracks already present in the input array rather than add new tracks. @THaTrack@ provides several @Set()@ methods that allow altering track data.