StFms  0.0.0
FMS software in the STAR framework
StFmsEventClusterer.cxx
Go to the documentation of this file.
1 // $Id$
2 //
3 // $Log$
14 
15 #include <algorithm>
16 #include <functional>
17 #include <iterator>
18 #include <list>
19 #include <numeric>
20 
21 #include "TF2.h" // To use shower-shape function
22 #include "TMath.h"
23 #include "TRandom.h" // For ROOT global random generator, gRandom
24 
25 #include "StRoot/St_base/StMessMgr.h"
26 #include "StEvent/StFmsCluster.h"
27 #include "StEvent/StFmsHit.h"
28 
34 
35 using namespace std;
36 using namespace FMSCluster;
37 
38 namespace {
39 // We use the tower list defined in StFmsTowerCluster throughout this file.
40 // Define some typedefs for convenience.
41 typedef StFmsTowerCluster::Towers Towers;
42 typedef Towers::const_iterator TowerIter;
43 
44 /* Helper function to add numbers of photons using std::accumulate */
45 int accumulatePhotons(int nPhotons, const ClusterList::value_type& cluster) {
46  return nPhotons + cluster->cluster()->nPhotons();
47 }
48 
49 /* Unary predicate for selecting bad clusters. */
50 struct IsBadCluster
51  : public std::unary_function<const ClusterList::value_type&, bool> {
52  // Set minimum allowed cluster energy and maximum number of towers
53  IsBadCluster(double minEnergy, unsigned maxTowers)
54  : energy(minEnergy), towers(maxTowers) { }
55  bool operator()(const ClusterList::value_type& cluster) const {
56  return cluster->cluster()->energy() <= energy ||
57  cluster->towers().size() > towers;
58  }
59  double energy;
60  unsigned towers;
61 };
62 
63 /*
64  Returns a pointer to the lowest energy photon in a cluster
65 
66  Assumes the cluster is either 1- or 2-photon
67  Returns NULL if there is no photon in the cluster
68  */
69 const StFmsFittedPhoton* findLowestEnergyPhoton(
70  const StFmsTowerCluster* cluster) {
71  const StFmsFittedPhoton* photon(NULL);
72  switch (cluster->cluster()->nPhotons()) {
73  case 1:
74  photon = &(cluster->photons()[0]);
75  break;
76  case 2:
77  if (cluster->photons()[0].energy < cluster->photons()[1].energy) {
78  photon = &(cluster->photons()[0]);
79  } else {
80  photon = &(cluster->photons()[1]);
81  } // if
82  default:
83  break; // photon remains NULL
84  } // switch
85  return photon;
86 }
87 
88 /*
89  Search towers in a cluster for one matching a row and column number
90 
91  Return a pointer to the matching tower if one is found, NULL otherwise.
92  */
93 const StFmsTower* searchClusterTowers(int row, int column,
94  const StFmsTowerCluster& cluster) {
95  const StFmsTower* match(NULL);
96  const Towers& towers = cluster.towers();
97  for (TowerIter i = towers.begin(); i != towers.end(); ++i) {
98  const StFmsTower* tower = *i;
99  if (tower->row() == row && tower->column() == column) {
100  match = tower;
101  break;
102  } // if
103  } // for
104  return match;
105 }
106 } // unnamed namespace
107 
108 StFmsEventClusterer::StFmsEventClusterer(const StFmsGeometry* geometry,
109  Int_t detectorId) {
110  mGeometry = geometry;
111  mDetectorId = detectorId;
112 }
113 
114 StFmsEventClusterer::~StFmsEventClusterer() {
115  if (mFitter) {
116  delete mFitter;
117  } // if
118 }
119 
120 Bool_t StFmsEventClusterer::cluster(std::vector<StFmsTower>* towerList) {
121  mTowers = towerList;
122  mClusterFinder.setMomentEnergyCutoff(.5);
123  mTowerWidthXY = mGeometry->towerWidths(mDetectorId);
125  if (mTowers->size() > 578) {
126  LOG_ERROR << "Too many towers for Fit" << endm;
127  return false;
128  } // if
129  mFitter = new StFmsClusterFitter(mGeometry, mDetectorId);
130  return fitEvent(); // Return true for success
131 }
132 
133 Int_t StFmsEventClusterer::fitEvent() {
134  // Possible alternative clusters for 1-photon fit: for catagory 0
136  std::vector<StFmsTower>::iterator towerIter;
137  for (towerIter = mTowers->begin(); towerIter != mTowers->end(); ++towerIter) {
138  towerList.push_back(&(*towerIter));
139  } // for
140  mClusterFinder.findClusters(&towerList, &mClusters);
141  // Cluster energy should be at least 2 GeV (parameter "minRealClusterEne")
142  if (mDetectorId == 8 || mDetectorId == 9) {
143  mClusters.remove_if(IsBadCluster(0.75, 25));
144  } else { // Different cuts for small cell
145  mClusters.remove_if(IsBadCluster(2.0, 49));
146  } // if
147  // Must do moment analysis before catagorization
148  for (ClusterIter i = mClusters.begin(); i != mClusters.end(); ++i) {
149  (*i)->findClusterAxis(mClusterFinder.momentEnergyCutoff());
150  } // for
151  // Loop over clusters, catagorize, guess the photon locations for cat 0 or 2
152  // clusters then fit, compare, and choose the best fit
153  bool badEvent = false;
154  const double max2PhotonFitChi2 = 10.;
155  for (ClusterIter cluster = mClusters.begin(); cluster != mClusters.end();
156  ++cluster) {
157  Int_t clustCatag = mClusterFinder.categorise(cluster->get());
158  // point to the real TObjArray that contains the towers to be fitted
159  // it is the same tower array for the cluster or all alternative clusters
160  mFitter->setTowers(&(*cluster)->towers());
161  // Number of Degree of Freedom for the fit
162  if (clustCatag == k1PhotonCluster) {
163  // Do 1-photon fit
164  fitOnePhoton(cluster->get());
165  } else if (clustCatag == k2PhotonCluster) {
166  // Do 2-photon fit
167  fit2PhotonClust(cluster);
168  badEvent = (*cluster)->chiSquare() > max2PhotonFitChi2;
169  } else if (clustCatag == kAmbiguousCluster) {
170  // for catagory-0 cluster, first try 1-photon fit!
171  // If the fit is good enough, it is 1-photon. Else also
172  // try 2-photon fit, and find the best fit (including 1-photon fit).
173  Bool_t is2Photon = true;
174  double chiSq1 = fitOnePhoton(cluster->get());
175  const StFmsFittedPhoton photon = (*cluster)->photons()[0]; // Cache
176  double chiSq2(NAN); // Only set if do 2-photon fit
177  // Decide if this 1-photon fit is good enough
178  if (chiSq1 < 5.) {
179  is2Photon = false;
180  } else {
181  // The 1-photon fit isn't good enough, so try 2-photon fit
182  chiSq2 = fit2PhotonClust(cluster);
183  // Check that the 2-photon fit didn't result in a bogus 2nd photon
184  if (validate2ndPhoton(cluster)) {
185  // If the 2nd photon in the 2-photon cluster is real, select 1- or 2-
186  // photon based on fit chi2/ndf.
187  is2Photon = chiSq2 <= chiSq1;
188  } else {
189  // The 2nd photon was bogus, it must really be a 1-photon cluster,
190  // even though the chi2 is poor
191  is2Photon = false;
192  } // if (validate2ndPhoton...)
193  } // if (chiSq1...)
194  // Now fill in the fit result, either 1- or 2-photon
195  if (is2Photon) {
196  // 2-photon fit is better
197  (*cluster)->cluster()->setNPhotons(2);
198  (*cluster)->setChiSquare(chiSq2);
199  // Flag the event as bad if the fit chi2/ndf is too bad
200  badEvent = (*cluster)->chiSquare() > max2PhotonFitChi2;
201  } else {
202  // 1-photon fit is better
203  (*cluster)->cluster()->setNPhotons(1);
204  (*cluster)->setChiSquare(chiSq1);
205  (*cluster)->photons()[0] = photon;
206  } // if (is2Photon)
207  } else { // Invalid cluster category
208  // should not happen!
209  LOG_ERROR << "Your logic of catagory is wrong! Something impossible " <<
210  "happens! This a catagory-" << clustCatag <<
211  " clusters! Don't know how to fit it!" << endm;
212  } // if (clustCatag...)
213  } // Loop over all real clusters
214  Int_t nPh = std::accumulate(mClusters.begin(), mClusters.end(), 0,
215  accumulatePhotons);
216  if (nPh > StFmsClusterFitter::maxNFittedPhotons()) {
217  // myFitter can only do up to "maxNFittedPhotons()"-photon fit
218  LOG_WARN << "Can not fit " << nPh << " (more than " <<
219  StFmsClusterFitter::maxNFittedPhotons() << " photons!" << endm;
220  return nPh;
221  } // if
222  // For global fit, add all towers from all clusters
223  Towers allTow;
224  for (ClusterIter cluster = mClusters.begin(); cluster != mClusters.end();
225  ++cluster) {
226  allTow.insert(allTow.end(), (*cluster)->towers().begin(),
227  (*cluster)->towers().end());
228  } // for
229  mFitter->setTowers(&allTow);
230  // Only do global fit for 2 or more clusters (2-photon fit for one cluster
231  // already has global fit)
232  if (mClusters.size() > 1) {
233  globalFit(nPh, mClusters.size(), mClusters.begin());
234  // Check for errors in the global fit - the number of photons returned by
235  // the global fit should equal the sum of photons in the fitted clusters
236  Int_t iph = std::accumulate(mClusters.begin(), mClusters.end(), 0,
237  accumulatePhotons);
238  if (iph != nPh) {
239  LOG_ERROR << "total nPh=" << nPh << " iPh=" << iph << endm;
240  } // if
241  } // if (mClusters.size() > 1)
242  return !badEvent;
243 }
244 
245 Double_t StFmsEventClusterer::photonEnergyInCluster(
246  Double_t widthLG,
247  const StFmsTowerCluster *p_clust,
248  const StFmsFittedPhoton *p_photon) const {
249  Double_t eSS = 0;
250  // Sum depositions by the photon in all towers of this cluster
251  const Towers& towers = p_clust->towers();
252  for (TowerIter tower = towers.begin(); tower != towers.end(); ++tower) {
253  eSS += photonEnergyInTower(widthLG, *tower, p_photon);
254  } // for
255  return eSS;
256 }
257 
258 Double_t StFmsEventClusterer::photonEnergyInTower(
259  Double_t widthLG,
260  const StFmsTower *p_tower,
261  const StFmsFittedPhoton* p_photon) const {
262  Double_t xx = ((Double_t)p_tower->column() - 0.5) *
263  mTowerWidthXY[0] - p_photon->xPos;
264  Double_t yy = ((Double_t)p_tower->row() - 0.5) *
265  mTowerWidthXY[1] - p_photon->yPos;
266  Double_t eSS = p_photon->energy *
267  mFitter->showerShapeFunction()->Eval(xx, yy, 0);
268  return eSS;
269 }
270 
271 Float_t StFmsEventClusterer::fitOnePhoton(StFmsTowerCluster* p_clust) {
272  // 4 parameters are passed to the fitting routine: nPhotons, cluster x
273  // position, cluster y position and cluster energy. Set the starting points
274  // for the fitting routine, plus lower and upper bounds on allowed values.
275  // - set starting points for the fit parameters:
276  const Double_t start[4] = {
277  1.0, mTowerWidthXY[0] * p_clust->cluster()->x(),
278  mTowerWidthXY[1] * p_clust->cluster()->y(),
279  p_clust->cluster()->energy()};
280  // - maximum deviations from the start points during fit:
281  const Double_t delta[4] = {
282  0.5, 0.5 * mTowerWidthXY[0], 0.5 * mTowerWidthXY[1],
283  0.15 * p_clust->cluster()->energy()};
284  // - set lower and upper physical limits of fit parameters = start +/- delta
285  // The parameters will stay within these ranges during the fit
286  Double_t lowLim[4], upLim[4];
287  for (int i(0); i < 4; ++i) {
288  lowLim[i] = start[i] - delta[i];
289  upLim[i] = start[i] + delta[i];
290  } // for
291  PhotonList photons;
292  Double_t chiSq = mFitter->fit(start, NULL, lowLim, upLim, &photons);
293  if (photons.empty()) { // check return status in case of a bad fit
294  LOG_ERROR << "1-photon Minuit fit returns error!" << endm;
295  } // if
296  p_clust->photons()[0] = photons.back();
297  p_clust->cluster()->setNPhotons(photons.size());
298  int ndf = p_clust->towers().size() - 3;
299  if (ndf <= 0) {
300  ndf = 1;
301  } // if
302  p_clust->setChiSquare(chiSq / ndf);
303  return p_clust->chiSquare();
304 }
305 
306 Float_t StFmsEventClusterer::globalFit(const Int_t nPh, const Int_t nCl,
307  ClusterIter first) {
308  // By design, we can only fit up to "maxNFittedPhotons()" photons
309  if (nPh > StFmsClusterFitter::maxNFittedPhotons() || nPh < 2) {
310  LOG_ERROR << "Global fit! Can not fit " << nPh << " photons!" << endm;
311  return -9999;
312  } // if
313  // Check that there is at least one cluster
314  if (nCl < 1) {
315  LOG_ERROR << nCl << " clusters! Global fit will NOT work!" << endm;
316  return -9999;
317  } // if
318  // Fit has 3 parameters per photon (x, y, E), plus 1 for the number of photons
319  // Starting position, lower and upper limit of parameters
320  // Initialise with a single value, which we will later set to the number-of-
321  // photon parameters.
322  std::vector<double> start(1, 0.), lowLim(1, 0.), upLim(1, 0.);
323  // The positions (e.b. cluster->photons()[jp].xPos) are already in unit of cm
324  // Clusters have already had all their fields properly filled
325  // (for example cluster[].photons()[0] should NOT be NULL!)
326  Int_t totPh = 0;
327  // Loop over all clusters
332  ClusterIter end = first;
333  std::advance(end, nCl);
334  for (ClusterIter cluster = first; cluster != end; ++cluster) {
335  // Loop over all photons in cluster
336  for (Int_t jp = 0; jp < (*cluster)->cluster()->nPhotons(); jp++) {
337  if (totPh > StFmsClusterFitter::maxNFittedPhotons()) {
338  LOG_ERROR << "Total # of photons in " << nCl << " clusters is at least "
339  << totPh << "! I can NOT do fit!" << endm;
340  return -9999;
341  } // if
342  // Note positions are in centimetres, not tower units
343  // Set x position start, lower and upper values
344  start.push_back((*cluster)->photons()[jp].xPos);
345  lowLim.push_back(start.back() - 1.25);
346  upLim.push_back(start.back() + 1.25);
347  // Set y position start, lower and upper values
348  start.push_back((*cluster)->photons()[jp].yPos);
349  lowLim.push_back(start.back() - 1.25);
350  upLim.push_back(start.back() + 1.25);
351  // Set energy start, lower and upper values
352  start.push_back((*cluster)->photons()[jp].energy);
353  lowLim.push_back(start.back() * (1 - 0.3)); // Limit to +/- 30% energy
354  upLim.push_back(start.back() * (1 + 0.3));
355  totPh++;
356  } // for
357  } // for
358  // Set the number-of-photons fit parameter
359  start.front() = totPh;
360  lowLim.front() = 0.5;
361  upLim.front() = StFmsClusterFitter::maxNFittedPhotons() + 0.5;
362  if (totPh != nPh) {
363  LOG_WARN << "WARNING! Total # of photons in " << nCl <<
364  " clusters is at least " << totPh << "! Not the same as the nPh = "
365  << nPh << "! I will try " << totPh << " instead!" << endm;
366  } // if
367  PhotonList photons;
368  Double_t chiSq = mFitter->fit(&start.at(0), NULL,
369  &lowLim.at(0), &upLim.at(0), &photons);
370  if (photons.empty()) {
371  LOG_WARN << "Global Minuit fit returns error!" << endm;
372  } // if
373  // Put the fit result back in the clusters
374  // Loop over all clusters
375  PhotonList::const_iterator photonIter = photons.begin();
376  for (ClusterIter cluster = first; cluster != end; ++cluster) {
377  // Loop over all photons in cluster
378  for (Int_t jp = 0; jp < (*cluster)->cluster()->nPhotons();
379  jp++, ++photonIter) {
380  (*cluster)->photons()[jp] = *photonIter;
381  } // for loop over photons
382  } // for loop over clusters
383  // Evaluate the Chi-square function and return it
384  return chiSq;
385 }
386 
387 Float_t StFmsEventClusterer::fit2PhotonClust(ClusterIter p_clust) {
388  const Double_t step2[7] = {0, 0.02, 0.02, 0.01, 0.01, 0.01, 0.1};
389  Double_t ratioSigma = (*p_clust)->cluster()->sigmaMin() /
390  (*p_clust)->cluster()->sigmaMax();
391  Double_t maxTheta = ratioSigma / 2.8;
392  if (maxTheta > (TMath::Pi() / 2.0)) {
393  maxTheta = TMath::Pi() / 2.0;
394  } // if
395  // Use for restricting d_gg
396  Double_t EcSigmaMax = (*p_clust)->cluster()->energy() *
397  (*p_clust)->cluster()->sigmaMax();
398  // Starting position, lower and upper limit of parameters
399  Double_t start[7], lowLim[7], upLim[7];
400  // First parameter is the number of photons, which is constant = 2 photons
401  start[0] = 2;
402  lowLim[0] = 1.5;
403  upLim[0] = 2.5;
404  // Parameter starting points and limits are determined by looking at cluster
405  // information
406  // - xPi and yPi: rarely do they go beyond 0.3 unit of lgd
407  // - theta: have a narrow theta range (for r=sigmaMax/sigmaMax,
408  // |theta|<0.5*r/0.65 when r<0.65, and linear increase from
409  // 0.5 to Pi/2 for 0.65<r<1)
410  // - E_gg: given by Ec (+/- 20% or less)
411  // - z_gg: should just let it vary from -1 to 1.
412  // - d_gg: a lower bound is given by r=sqrt(sigmaX^2+sigmaY^2).
413  // d_gg > Max( 2.5*(r-0.6), 0.5 )
414  start[1] = mTowerWidthXY[0] * (*p_clust)->cluster()->x();
415  start[2] = mTowerWidthXY[1] * (*p_clust)->cluster()->y();
416  start[6] = (*p_clust)->cluster()->energy();
417  start[4] = (*p_clust)->thetaAxis();
418  const float dggPara[6] = {18.0, 2.2, 0.5, 60.0, 0.085, 3.5};
419  start[3] = dggPara[1] * mTowerWidthXY[0] * (*p_clust)->cluster()->sigmaMax();
420  // Randomize the starting point of Z_gg (from -0.1 to 0.1)
421  start[5] = 0.1 * (2 * gRandom->Rndm() - 1);
422  lowLim[1] = start[1] - 0.2 * mTowerWidthXY[0];
423  lowLim[2] = start[2] - 0.2 * mTowerWidthXY[1];
424  lowLim[6] = start[6] * (1. - 0.05);
425  upLim[1] = start[1] + 0.2 * mTowerWidthXY[0];
426  upLim[2] = start[2] + 0.2 * mTowerWidthXY[1];
427  upLim[6] = start[6] * (1. + 0.05);
428  lowLim[4] = start[4] - maxTheta;
429  lowLim[5] = - 1.0;
430  lowLim[3] = dggPara[0] / pow(EcSigmaMax, 0.8);
431  if (lowLim[3] < dggPara[2]) {
432  lowLim[3] = dggPara[2];
433  } // if
434  lowLim[3] *= mTowerWidthXY[0];
435  if (lowLim[3] >= start[3]) {
436  lowLim[3] = start[3] * 0.9;
437  } // if
438  upLim[3] = dggPara[4] * (dggPara[3] - EcSigmaMax);
439  if (upLim[3] > dggPara[5]) {
440  upLim[3] = dggPara[5];
441  } // if
442  upLim[3] *= mTowerWidthXY[0];
443  if (upLim[3] <= start[3]) {
444  upLim[3] = start[3] * 1.1;
445  } // if
446  upLim[4] = start[4] + maxTheta;
447  upLim[5] = 1.0;
448  // Call special 2-photon-cluster mFitter
449  PhotonList photons;
450  Double_t chiSq = mFitter->fit2PhotonCluster(start, step2, lowLim, upLim,
451  &photons);
452  if (photons.empty()) {
453  LOG_WARN << "Minuit fit returns error!" << endm;
454  } // if
455  // Do a global fit, using result of 1st fit as starting point
456  // Need to set "nPhoton" before calling "globalFit(..)"
457  (*p_clust)->photons()[0] = photons.front();
458  (*p_clust)->photons()[1] = photons.back();
459  (*p_clust)->cluster()->setNPhotons(photons.size());
460  chiSq = globalFit(2, 1, p_clust);
461  int ndf = (*p_clust)->towers().size() - 6;
462  if (ndf <= 0) {
463  ndf = 1;
464  } // if
465  (*p_clust)->setChiSquare(chiSq / ndf);
466  return (*p_clust)->chiSquare();
467 }
468 
469 /*
470  Further information:
471  If one photon peak lies on top of a low (compared to photon energy) or even
472  zero tower, this photon is definitely bogus. This could happen if a nearby
473  cluster took away towers that might make the fit have a large Chi-square.
474  So first check that the fitted photon of the lower-energy photon (we assume the
475  higher energy photon should be fine) is over one of the non-zero towers of the
476  cluster. First of all, this ensures that we don't have an "outside of cluster"
477  bogus photon, i.e. a bogus photon that could be the result of minimizing the
478  chi-square over towers that do not include the supposed peak tower.
479 */
480 bool StFmsEventClusterer::validate2ndPhoton(ClusterConstIter cluster) const {
481  // Select the lower-energy of the two photons
482  const StFmsFittedPhoton* photon = findLowestEnergyPhoton(cluster->get());
483  // Tower row and column where the fitted photon of lower energy should hit
484  int column = 1 + (Int_t)(photon->xPos / mTowerWidthXY[0]);
485  int row = 1 + (Int_t)(photon->yPos / mTowerWidthXY[1]);
486  // Now check whether this tower is one of the non-zero towers of the cluster
487  // The temporary StFmsTower only needs row and column set for the test
488  const StFmsTower* tower = searchClusterTowers(row, column, **cluster);
489  // If tower is non-NULL, the photon does hit in a tower in this cluster.
490  if (!tower) {
491  return false;
492  } // if
493  // Now test the photon and tower properties.
494  // Check if the fitted energy is too large compared to the energy of the tower
495  if (tower->hit()->energy() < 0.25 * photon->energy) {
496  return false;
497  } // if
498  // Check if the 2nd photon's "High-Tower" enery is too large compared to its
499  // fitted energy. If so, it is probably splitting one photon into two
500  Double_t eSS = photonEnergyInTower(mTowerWidthXY[0], tower, photon);
501  if (tower->hit()->energy() > 1.5 * eSS) {
502  return false;
503  } // if
504  // Check that the 2nd photon is not near the edge of another cluster
505  // Namely, we check what would be the energy deposited in other clusters by
506  // this photon vs. energy deposited in its own cluster
507  // If the ratio is too high, this fitted photon is probably a bogus one
508  Double_t energyInOwnCluster =
509  photonEnergyInCluster(mTowerWidthXY[0], cluster->get(), photon);
510  // Loop over all clusters except its own
511  for (ClusterConstIter i = mClusters.begin(); i != mClusters.end(); ++i) {
512  if (i != cluster) { // Skip the photon's own cluster
513  if (photonEnergyInCluster(mTowerWidthXY[0], i->get(), photon) >
514  (0.2 * energyInOwnCluster)) {
515  return false; // Stop as soon as we fail for one cluster
516  } // if
517  } // if
518  } // for
519  return true; // The photon passed all tests; it's real
520 }
A cluster created by 2 photons.
Definition: StFmsCluster.h:25
Float_t x() const
Definition: StFmsCluster.h:59
A cluster created by 1 photon.
Definition: StFmsCluster.h:24
Declaration of StFmsGeometry, an FMS database geometry interface.
Declaration of StFmsCluster, a group of adjacent FMS hits.
Float_t xPos
Fitted (relative) x-position.
Declaration of StFmsTower, a simple FMS tower wrapper.
Int_t column() const
Definition: StFmsTower.h:82
Declaration of StFmsClusterFitter, shower-shape fitting routine.
Float_t energy
Fitted energy.
Declaration of StFmsEventClusterer, manager class for clustering.
std::list< FMSCluster::StFmsTower * > TowerList
std::list< StFmsTower * > Towers
Shorthand for tower collection.
Declaration of StFmsTowerCluster, a cluster of FMS towers.
Bool_t setNPhotons(Int_t nPhoton)
StFmsFittedPhoton * photons()
Int_t nPhotons() const
Definition: StFmsCluster.h:55
Declaration of StFmsFittedPhoton, a photon fitted to an FMS cluster.
const StFmsHit * hit() const
Definition: StFmsTower.h:80
ClusterList::const_iterator ClusterConstIter
Float_t y() const
Definition: StFmsCluster.h:61
Float_t yPos
Fitted (relative) y-position.
std::vector< Float_t > towerWidths(Int_t detectorId) const
std::list< StFmsFittedPhoton > PhotonList
Int_t row() const
Definition: StFmsTower.h:84
Float_t energy() const
Definition: StFmsCluster.h:57
Could be 1- or 2-photon, needs to be fitted.
Definition: StFmsCluster.h:23