Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-12-18 09:15:15

0001 #include "StreakEventsIdentifier.h"
0002 
0003 
0004 #include <fun4all/Fun4AllReturnCodes.h>
0005 
0006 
0007 // Fun4All
0008 #include <ffaobjects/EventHeader.h>
0009 #include <fun4all/Fun4AllHistoManager.h>
0010 #include <fun4all/Fun4AllReturnCodes.h>
0011 #include <fun4all/Fun4AllServer.h>
0012 #include <phool/PHCompositeNode.h>
0013 #include <phool/getClass.h>
0014 #include <phool/phool.h>
0015 
0016 
0017 // ROOT stuff
0018 #include <TFile.h>
0019 #include <TH1F.h>
0020 #include <TH2F.h>
0021 #include <TH3F.h>
0022 #include <TLorentzVector.h>
0023 #include <TTree.h>
0024 
0025 
0026 
0027 #include <fun4all/SubsysReco.h>
0028 
0029 
0030 
0031 // Event / Run
0032 #include <ffaobjects/EventHeader.h>
0033 #include <ffaobjects/RunHeader.h>
0034 
0035 // Calo / towers / clusters / jets
0036 #include <calobase/RawCluster.h>
0037 #include <calobase/RawClusterContainer.h>
0038 #include <calobase/RawClusterUtility.h>
0039 #include <calobase/RawTowerGeom.h>
0040 #include <calobase/RawTowerGeomContainer.h>
0041 #include <calobase/TowerInfo.h>
0042 #include <calobase/TowerInfoContainer.h>
0043 #include <calobase/TowerInfoDefs.h>
0044 #include <jetbase/Jet.h>
0045 #include <jetbase/JetContainer.h>
0046 
0047 // For clusters and geometry
0048 
0049 #include <calobase/RawTower.h>
0050 
0051 // ROOT
0052 #include <TFile.h>
0053 #include <TH1F.h>
0054 #include <TH2F.h>
0055 #include <TCanvas.h>
0056 #include <TStyle.h>
0057 
0058 #include <calotrigger/TriggerRunInfo.h>
0059 
0060 
0061 // GL1 Information
0062 #include <ffarawobjects/Gl1Packet.h>
0063 
0064 // for cluster vertex correction
0065 #include <CLHEP/Geometry/Point3D.h>
0066 
0067 // for the vertex
0068 #include <globalvertex/GlobalVertex.h>
0069 #include <globalvertex/GlobalVertexMap.h>
0070 #include <globalvertex/MbdVertex.h>
0071 #include <globalvertex/MbdVertexMap.h>
0072 
0073 #include <g4main/PHG4TruthInfoContainer.h>
0074 #include <g4main/PHG4Particle.h>
0075 #include <g4main/PHG4VtxPoint.h>
0076 #include <g4main/PHG4Shower.h>
0077 
0078 
0079 #include <mbd/MbdGeom.h>
0080 #include <mbd/MbdPmtContainer.h>
0081 #include <mbd/MbdPmtHit.h>
0082 
0083 #include <phhepmc/PHHepMCGenEvent.h>
0084 #include <phhepmc/PHHepMCGenEventMap.h>
0085 #include <ffaobjects/RunHeader.h>
0086 
0087 #include <calotrigger/TriggerRunInfo.h>
0088 
0089 #include <jetbase/Jetv1.h>
0090 #include <jetbase/Jetv2.h>
0091 #include <jetbase/JetContainer.h>
0092 
0093 #include <set>                          // for std::set in fillClusterWindows
0094 #include <CLHEP/Vector/ThreeVector.h>   // for CLHEP::Hep3Vector
0095 #include <calobase/RawTowerDefs.h>      // for RawTowerDefs::decode_index*
0096 #include <phhepmc/PHGenIntegral.h>
0097 
0098 
0099 #include <TSystem.h>
0100 #include <TMath.h>
0101 #include <iomanip>
0102 #include <fstream>
0103 
0104 #include <ios>
0105 
0106 #include <TCanvas.h>
0107 #include <TLatex.h>
0108 
0109 #include <TVector2.h>
0110 
0111 
0112 
0113 // -----------------------------------------------------------
0114 StreakEventsIdentifier::StreakEventsIdentifier(
0115   const std::string& name,
0116   const std::string& outprefix)
0117 : SubsysReco(name), m_outprefix(outprefix) {}
0118 
0119 StreakEventsIdentifier::~StreakEventsIdentifier() = default;
0120 
0121 // -----------------------------------------------------------
0122 int StreakEventsIdentifier::Init(PHCompositeNode* topNode) {
0123 
0124   std::cout.setf(std::ios::unitbuf); 
0125   std::cout << "[Init] Setting ROOT style and booking histos...\n";
0126   gStyle->SetOptStat(0);
0127   bookHistos();
0128   
0129   if (Verbosity()>0) {
0130     std::cout << "[Init] Booking histos. Output prefix='" << m_outprefix << "'\n"
0131               << "       Cuts: Et_min=" << m_et_min
0132               << " GeV, weta_min=" << m_weta_min
0133               << ", |t|<" << m_abs_time_cut_ns << " ns, tRMS>=" << m_time_spread_min << " ns\n"
0134               << "       Time weight Ethresh=" << m_time_weight_Ethresh
0135               << " GeV, tower E for shapes=" << m_min_tower_E_for_shapes << " GeV" << std::endl;
0136   }
0137   const std::string ofn = m_outprefix + "_results.root";
0138   std::cout << "[Init] Opening output ROOT file: " << ofn << "\n";
0139   m_out.reset(TFile::Open(ofn.c_str(), "RECREATE"));
0140   return 0;
0141 }
0142 
0143 // -----------------------------------------------------------
0144 int StreakEventsIdentifier::InitRun(PHCompositeNode* topNode) {
0145   if (auto* rh = findNode::getClass<RunHeader>(topNode, "RunHeader")) {
0146     m_runnumber = rh->get_RunNumber();
0147     std::cout << "[InitRun] RunHeader found. Run = " << m_runnumber << "\n";
0148   } else {
0149     std::cout << "[InitRun][WARN] RunHeader node not found.\n";
0150   }
0151   return 0;
0152 }
0153 
0154 
0155 // -----------------------------------------------------------
0156 /////GEtting the nodes needed towers and clusters
0157 // -----------------------------------------------------------
0158 RawClusterContainer* StreakEventsIdentifier::getCemcClusterContainer(PHCompositeNode* topNode) const {
0159   const char* candidates[] = {
0160     "CLUSTERINFO_CEMC",
0161     nullptr
0162   };
0163   for (const char** p = candidates; *p; ++p) {
0164     if (auto* c = findNode::getClass<RawClusterContainer>(topNode, *p)) {
0165       std::cout << "[getCemcClusterContainer] Using node: " << *p << "\n";
0166       return c;
0167     }
0168   }
0169   std::cout << "[getCemcClusterContainer][WARN] No EMCal cluster container found.\n";
0170   return nullptr;
0171 }
0172 
0173 
0174 //Raw towers
0175 TowerInfoContainer* StreakEventsIdentifier::getCemcRawTowers(PHCompositeNode* topNode) const {
0176   auto* t = findNode::getClass<TowerInfoContainer>(topNode, "TOWERS_CEMC");
0177   std::cout << (t ? "[getCemcRawTowers] Found TOWERS_CEMC.\n"
0178                   : "[getCemcRawTowers][INFO] No RAW tower container (continuing).\n");
0179   return t;
0180 }
0181   
0182 //Calibrated towers
0183 TowerInfoContainer* StreakEventsIdentifier::getCemcCalibTowers(PHCompositeNode* topNode) const {
0184   const char* names[] = {
0185     "TOWERINFO_CALIB_CEMC_RETOWER",    
0186     "TOWERINFO_CALIB_CEMC_RETOWER_SUB1",       
0187     nullptr
0188   };
0189   for (const char** n = names; *n; ++n) {
0190     if (auto* t = findNode::getClass<TowerInfoContainer>(topNode, *n)) {
0191       std::cout << "[getCemcCalibTowers] Found " << *n << "\n";
0192       return t;
0193     }
0194   }
0195   std::cout << "[getCemcCalibTowers][WARN] No calibrated CEMC TowerInfo container found.\n";
0196   return nullptr;
0197 }
0198 
0199 RawTowerGeomContainer* StreakEventsIdentifier::getCemcGeom(PHCompositeNode* topNode) const {
0200   auto* g = findNode::getClass<RawTowerGeomContainer>(topNode, "TOWERGEOM_CEMC");
0201   std::cout << (g ? "[getCemcGeom] Found TOWERGEOM_CEMC.\n"
0202                   : "[getCemcGeom][WARN] Missing TOWERGEOM_CEMC.\n");
0203   return g;
0204 }
0205 
0206 
0207 // --- IHCal ---
0208 TowerInfoContainer* StreakEventsIdentifier::getIhcalCalibTowers(PHCompositeNode* topNode) const {
0209   auto* t = findNode::getClass<TowerInfoContainer>(topNode, "TOWERINFO_CALIB_HCALIN");
0210   std::cout << (t ? "[getIhcalCalibTowers] Found TOWERINFO_CALIB_HCALIN.\n"
0211                   : "[getIhcalCalibTowers][WARN] Missing TOWERINFO_CALIB_HCALIN.\n");
0212   return t;
0213 }
0214 TowerInfoContainer* StreakEventsIdentifier::getIhcalRawTowers(PHCompositeNode* topNode) const {
0215   auto* t = findNode::getClass<TowerInfoContainer>(topNode, "TOWERS_HCALIN");
0216   std::cout << (t ? "[getIhcalRawTowers] Found TOWERS_HCALIN.\n"
0217                   : "[getIhcalRawTowers][INFO] No RAW IHCal tower container (continuing).\n");
0218   return t;
0219 }
0220 RawTowerGeomContainer* StreakEventsIdentifier::getIhcalGeom(PHCompositeNode* topNode) const {
0221   auto* g = findNode::getClass<RawTowerGeomContainer>(topNode, "TOWERGEOM_HCALIN");
0222   std::cout << (g ? "[getIhcalGeom] Found TOWERGEOM_HCALIN.\n"
0223                   : "[getIhcalGeom][WARN] Missing TOWERGEOM_HCALIN.\n");
0224   return g;
0225 }
0226 
0227 // --- OHCal ---
0228 TowerInfoContainer* StreakEventsIdentifier::getOhcalCalibTowers(PHCompositeNode* topNode) const {
0229   auto* t = findNode::getClass<TowerInfoContainer>(topNode, "TOWERINFO_CALIB_HCALOUT");
0230   std::cout << (t ? "[getOhcalCalibTowers] Found TOWERINFO_CALIB_HCALOUT.\n"
0231                   : "[getOhcalCalibTowers][WARN] Missing TOWERINFO_CALIB_HCALOUT.\n");
0232   return t;
0233 }
0234 TowerInfoContainer* StreakEventsIdentifier::getOhcalRawTowers(PHCompositeNode* topNode) const {
0235   auto* t = findNode::getClass<TowerInfoContainer>(topNode, "TOWERS_HCALOUT");
0236   std::cout << (t ? "[getOhcalRawTowers] Found TOWERS_HCALOUT.\n"
0237                   : "[getOhcalRawTowers][INFO] No RAW OHCal tower container (continuing).\n");
0238   return t;
0239 }
0240 RawTowerGeomContainer* StreakEventsIdentifier::getOhcalGeom(PHCompositeNode* topNode) const {
0241   auto* g = findNode::getClass<RawTowerGeomContainer>(topNode, "TOWERGEOM_HCALOUT");
0242   std::cout << (g ? "[getOhcalGeom] Found TOWERGEOM_HCALOUT.\n"
0243                   : "[getOhcalGeom][WARN] Missing TOWERGEOM_HCALOUT.\n");
0244   return g;
0245 }
0246 
0247 
0248 JetContainer* StreakEventsIdentifier::getJetContainer(PHCompositeNode* topNode) const {
0249   if (!m_jet_container_hint.empty()) {
0250     if (auto* jc = findNode::getClass<JetContainer>(topNode, m_jet_container_hint)) {
0251       std::cout << "[getJetContainer] Using hinted jet container: " << m_jet_container_hint << "\n";
0252       return jc;
0253     } else {
0254       std::cout << "[getJetContainer][INFO] Hint '" << m_jet_container_hint << "' not found.\n";
0255     }
0256   }
0257   const char* cands[] = { "AntiKt_Tower_r04", "AntiKt_Tower_r04_Sub1", "AntiKt_Tower_r04_Combined", nullptr };
0258   for (const char** p = cands; *p; ++p) {
0259     if (auto* jc = findNode::getClass<JetContainer>(topNode, *p)) {
0260       std::cout << "[getJetContainer] Using jet container: " << *p << "\n";
0261       return jc;
0262     }
0263   }
0264   std::cout << "[getJetContainer][INFO] No jet container found.\n";
0265   return nullptr;
0266 }
0267 
0268 float StreakEventsIdentifier::getVertexZ(PHCompositeNode* topNode) const {
0269   if (auto* mbd = findNode::getClass<MbdVertexMap>(topNode, "MbdVertexMap")) {
0270     if (!mbd->empty()) {
0271       const float vz = mbd->begin()->second->get_z();
0272       std::cout << "[getVertexZ] Using MBD z = " << vz << "\n";
0273       return vz;
0274     }
0275   }
0276   std::cout << "[getVertexZ][INFO] No MBD vertex. Using z=0.\n";
0277   return 0.f;
0278 }
0279 
0280 // -----------------------------------------------------------
0281 /////Shower shape windows from CaloAna24
0282 // -----------------------------------------------------------
0283 
0284   bool StreakEventsIdentifier::fillClusterWindows(
0285     const RawCluster* cl,
0286     TowerInfoContainer* emcCalib,
0287     TowerInfoContainer* emcRaw,
0288     ClusterWindows& win,
0289     int& maxieta, int& maxiphi,
0290     float& cog_eta, float& cog_phi) const
0291 {
0292   if (!cl) return false;
0293 
0294   // tower source (96×256)
0295   auto is_full = [](TowerInfoContainer* c){ return c && c->size() >= 96*256; };
0296   TowerInfoContainer* src = nullptr;
0297   if (is_full(emcCalib)) src = emcCalib;
0298   else if (is_full(emcRaw)) src = emcRaw;
0299   else {
0300     std::cout << " No full 96 *256 tower container (calib="
0301               << (emcCalib?emcCalib->size():-1) << ", raw=" << (emcRaw?emcRaw->size():-1) << ")\n";
0302     return false;
0303   }
0304 
0305   //  max-energy tower from the cluster towermap
0306   float emax = -1.f; int seed_i=-1, seed_j=-1;
0307   for (const auto& kv : cl->get_towermap()) {
0308     const int i = RawTowerDefs::decode_index1(kv.first);
0309     const int j = RawTowerDefs::decode_index2(kv.first);
0310     const unsigned int key = TowerInfoDefs::encode_emcal(i,j);
0311     if (auto* tw = src->get_tower_at_key(key)) {
0312       const float e = tw->get_energy();
0313       if (e > emax) { emax = e; seed_i = i; seed_j = j; }
0314     }
0315   }
0316   if (seed_i < 0) {
0317     std::cout << "  [fillClusterWindows][WARN] Towermap had no usable towers for this cluster.\n";
0318     return false;
0319   }
0320   maxieta = seed_i;  // seed center
0321   maxiphi = seed_j;
0322 
0323   //  use shapes for a fractional CoG  
0324   float frac_eta = 0.f, frac_phi = 0.f;
0325   {
0326     std::vector<float> ss = cl->get_shower_shapes(0.070f);
0327     if (ss.size() >= 6) {
0328 
0329       // Here we only keep the fractional part in [-0.5, +0.5] as a sub-tower
0330       float eta_like = ss[4]; 
0331       float phi_like = ss[5]; 
0332       frac_eta = (eta_like - std::floor(eta_like)) - 0.5f;  
0333       frac_phi = (phi_like - std::floor(phi_like)) - 0.5f;
0334       std::cout << "  [fillClusterWindows] shapes present: frac_eta=" << frac_eta
0335                 << " frac_phi=" << frac_phi << "\n";
0336     } else {
0337       std::cout << "  [fillClusterWindows] no usable shapes; using energy-only CoG.\n";
0338     }
0339   }
0340 
0341   // Building 7×7 window 
0342   auto wrap_phi = [](int& iphi){ iphi = (iphi%256 + 256)%256; };
0343   auto in_eta = [](int i){ return (0 <= i && i < 96); };
0344 
0345   float sumE=0.f, sumEi=0.f, sumEj=0.f;
0346   std::set<unsigned int> owned;
0347   for (const auto& it : cl->get_towermap()) {
0348     owned.insert( TowerInfoDefs::encode_emcal(RawTowerDefs::decode_index1(it.first),
0349                                              RawTowerDefs::decode_index2(it.first)) );
0350   }
0351 
0352   for (int di=-3; di<=+3; ++di) {
0353     for (int dj=-3; dj<=+3; ++dj) {
0354       int i = maxieta + di;
0355       int j = maxiphi + dj;
0356       if (!in_eta(i)) continue;        // truncate at eta edges
0357       wrap_phi(j);                      // wrap phi
0358 
0359       const int I = di + 3;
0360       const int J = dj + 3;
0361 
0362       const unsigned int key = TowerInfoDefs::encode_emcal(i,j);
0363       TowerInfo* tw = src->get_tower_at_key(key);
0364       
0365       if (!tw || !tw->get_isGood()) continue;
0366 
0367       const float e = tw ? tw->get_energy() : 0.f;
0368       const float t = tw ? tw->get_time() : 0.f;
0369 
0370       if (e < 0.f) continue; // skip negative energy towers
0371       if (!t) continue; // skip no-time towers
0372       //if (t < -999.f) continue; // skip invalid time towers
0373 
0374 
0375       win.E77[I][J]   = (e > m_min_tower_E_for_shapes) ? e : 0.f;
0376       win.Own77[I][J] = owned.count(key) ? 1 : 0;
0377       win.T77[I][J]   = tw ? tw->get_time() : 0.f;
0378 
0379       if (win.E77[I][J] > 0.f) {
0380         sumE  += win.E77[I][J];
0381         sumEi += win.E77[I][J] * I;
0382         sumEj += win.E77[I][J] * J;
0383       }
0384     }
0385   }
0386 
0387   if (sumE <= 0.f) {
0388     std::cout << "  [fillClusterWindows][WARN] No energy in 7 X 7 window.\n";
0389     return false;
0390   }
0391 
0392   // CoG: energy-weighted from the window + fractional from shapes
0393   cog_eta = (sumEi / sumE) + frac_eta; 
0394   cog_phi = (sumEj / sumE) + frac_phi;
0395 
0396   return true;
0397 }
0398 
0399 
0400 void StreakEventsIdentifier::computeWidths(
0401     const ClusterWindows& win, float cog_eta, float cog_phi,
0402     float& weta, float& weta_cogx, float& wphi, float& wphi_cogx) const  
0403 {
0404   const bool owned_only = true;
0405 
0406   float sumE = 0.f, sumE_eta2 = 0.f, sumE_phi2 = 0.f;
0407   float sumE_excl = 0.f, sumE_eta2_excl = 0.f, sumE_phi2_excl = 0.f;
0408 
0409   for (int i = 0; i < 7; ++i) {
0410     for (int j = 0; j < 7; ++j) {
0411       const float E = win.E77[i][j];
0412       if (E <= 0.f) continue;
0413       if (owned_only && !win.Own77[i][j]) continue;
0414 
0415       const float di = float(i) - cog_eta;
0416       const float dj = float(j) - cog_phi;
0417       
0418       sumE       += E;
0419       sumE_eta2  += E * di * di;
0420       sumE_phi2  += E * dj * dj;  
0421 
0422       if (!(i == 3 && j == 3)) {
0423         sumE_excl       += E;
0424         sumE_eta2_excl  += E * di * di;
0425         sumE_phi2_excl  += E * dj * dj;  
0426       }
0427     }
0428   }
0429 
0430   weta       = (sumE      > 0.f) ? (sumE_eta2      / sumE)      : 0.f;
0431   weta_cogx  = (sumE_excl > 0.f) ? (sumE_eta2_excl / sumE_excl) : 0.f;
0432   wphi       = (sumE      > 0.f) ? (sumE_phi2      / sumE)      : 0.f;
0433   wphi_cogx  = (sumE_excl > 0.f) ? (sumE_phi2_excl / sumE_excl) : 0.f; 
0434 }
0435 
0436 // -----------------------------------------------------------
0437 
0438 void StreakEventsIdentifier::computeTimeMoments(
0439     const ClusterWindows& w, float& t_avg, float& t_rms) const {
0440   float sumE = 0.f, sumEt = 0.f, sumEt2 = 0.f;
0441   for (int i=0;i<7;++i) for (int j=0;j<7;++j) {
0442     const float E = w.E77[i][j];
0443     const float t = w.T77[i][j];
0444 
0445     if (E < m_time_weight_Ethresh) continue; // weight threshold
0446     sumE   += E;
0447     sumEt  += E * t;
0448     sumEt2 += E * t * t;
0449   }
0450   if (sumE>0) {
0451     t_avg = sumEt / sumE;
0452     const float var = std::max(0.f, (sumEt2/sumE) - t_avg*t_avg);
0453     t_rms = std::sqrt(var);
0454   } else {
0455     t_avg = -999.f; t_rms = -999.f;
0456   }
0457   std::cout << "    [computeTimeMoments] t_avg=" << t_avg << " t_rms=" << t_rms
0458             << " (sumE=" << sumE << ")\n";
0459 }
0460 
0461 
0462 // extractCaloTiming ---Extract weighted average timing from calorimeter
0463 std::pair<float, float> StreakEventsIdentifier::extractCaloTiming(
0464     TowerInfoContainer* towers, float energy_threshold) const {
0465     
0466   if (!towers) {
0467     std::cout << "    [extractCaloTiming] No tower container provided\n";
0468     return {-999.f, -999.f};
0469   }
0470   
0471   float sum_time = 0.f;
0472   float sum_weight = 0.f;
0473   std::vector<float> times;
0474   
0475   // Loop over all towers in the calorimeter
0476   const int ntowers = towers->size();
0477   for (int i = 0; i < ntowers; ++i) {
0478     TowerInfo* tw = towers->get_tower_at_channel(i);
0479     if (!tw || !tw->get_isGood()) continue;
0480     
0481     float energy = tw->get_energy();
0482     if (energy < energy_threshold) continue;
0483     
0484     float time = tw->get_time();
0485     if (time < -900.f) continue;  // Skip invalid times
0486     
0487     sum_time += time * energy;
0488     sum_weight += energy;
0489     times.push_back(time);
0490   }
0491   
0492   if (sum_weight > 0.f && !times.empty()) {
0493     float mean = sum_time / sum_weight;
0494     
0495     // Calculate RMS
0496     float sum_sq = 0.f;
0497     for (float t : times) {
0498       sum_sq += (t - mean) * (t - mean);
0499     }
0500     float rms = std::sqrt(sum_sq / times.size());
0501     
0502     std::cout << "    [extractCaloTiming] mean=" << mean << " ns, rms=" << rms 
0503               << " ns (from " << times.size() << " towers, sum_weight=" 
0504               << sum_weight << " GeV)\n";
0505     
0506     return {mean, rms};
0507   }
0508   
0509   std::cout << "    [extractCaloTiming] No valid timing data found\n";
0510   return {-999.f, -999.f};
0511 }
0512 
0513 // -----------------------------------------------------------
0514 //STARting the event processing
0515 // -----------------------------------------------------------
0516 
0517 int StreakEventsIdentifier::process_event(PHCompositeNode* topNode) {
0518   ++m_evt_processed;
0519 
0520   if (Verbosity()>2 && (m_evt_processed<=15 || m_evt_processed%500==1))
0521   std::cout << "\n[Event] idx=" << m_evt_processed << std::endl;
0522 
0523   if (m_evt_processed % 100 == 1) {
0524     std::cout << "\n=== [Event " << m_evt_processed << "] Starting processing ===" << std::endl;
0525   }
0526 
0527   // event header
0528   int eventnumber = 0;
0529   if (auto* eh = findNode::getClass<EventHeader>(topNode, "EventHeader")) {
0530     eventnumber = eh->get_EvtSequence();
0531     std::cout << "[process_event] Run " << eh->get_RunNumber()
0532               << "  Event " << eventnumber << "\n";
0533   } else {
0534     std::cout << "[process_event][WARN] No EventHeader node.\n";
0535   }
0536 
0537    // ===== Triggers Selection =====
0538 
0539    Gl1Packet* gl1PacketInfo = findNode::getClass<Gl1Packet>(topNode, "GL1Packet");
0540   
0541   if (!gl1PacketInfo) {
0542     std::cout << "[process_event][WARN] GL1Packet node is missing. "
0543               << "Skipping trigger selection for event " << m_eventnumber << "\n";
0544   }
0545 
0546     if (gl1PacketInfo) {
0547       // getting the trigger vectors
0548       uint64_t triggervec = gl1PacketInfo->getScaledVector();
0549       uint64_t triggervecraw = gl1PacketInfo->getLiveVector();
0550       
0551       // Processing   all 64 trigger bits
0552       for (int i = 0; i < 64; i++) {
0553         bool trig_decision = ((triggervec & 0x1U) == 0x1U);
0554         bool trig_decision_raw = ((triggervecraw & 0x1U) == 0x1U);
0555 
0556         if (i < 64) {
0557           // Reset and set trigger flags
0558           m_scaledtrigger[i] = false;
0559           m_scaledtrigger[i] = trig_decision;
0560           
0561           m_livetrigger[i] = false;
0562           m_livetrigger[i] = trig_decision_raw;
0563           
0564           // Count triggers
0565           if (trig_decision) m_nscaledtrigger[i]++;
0566           if (trig_decision_raw) m_nlivetrigger[i]++;
0567           
0568           // Initialize scalers on first event
0569           if (!m_initilized) {
0570             for (int j = 0; j < 3; j++) {
0571               m_initscaler[i][j] = gl1PacketInfo->lValue(i, j);
0572             }
0573           }
0574           
0575           // Store current scalers
0576           for (int j = 0; j < 3; j++) {
0577             m_currentscaler[i][j] = gl1PacketInfo->lValue(i, j);
0578             if (j == 0) m_currentscaler_raw[i] = m_currentscaler[i][j];
0579             if (j == 1) m_currentscaler_live[i] = m_currentscaler[i][j];
0580             if (j == 2) m_currentscaler_scaled[i] = m_currentscaler[i][j];
0581           }
0582         }
0583 
0584         // Shift trigger vectors for next bit
0585         triggervec = (triggervec >> 1U);
0586         triggervecraw = (triggervecraw >> 1U);
0587       }
0588       m_initilized = true;
0589       
0590       // Get trigger prescales
0591       TriggerRunInfo* trigRunInfo = findNode::getClass<TriggerRunInfo>(topNode, "TriggerRunInfo");
0592       if (trigRunInfo) {
0593         for (int i = 0; i < 32; i++) {
0594           m_trigger_prescale[i] = trigRunInfo->getPrescaleByBit(i);
0595         }
0596       }
0597       
0598       // Apply trigger selection
0599       if (!m_using_trigger_bits.empty()) {
0600         bool trigger_passed = false;
0601         
0602         for (int bit : m_using_trigger_bits) {
0603           if (bit >= 0 && bit < 64 && m_scaledtrigger[bit]) {
0604             trigger_passed = true;
0605             std::cout << "  [Trigger] Event " << m_eventnumber 
0606                       << " PASSED trigger bit " << bit << "\n";
0607             break;  
0608           }
0609         }
0610         
0611         if (!trigger_passed) {
0612           std::cout << "  [Trigger] Event " << m_eventnumber 
0613                     << " REJECTED - does not pass required trigger bits (";
0614           for (size_t i = 0; i < m_using_trigger_bits.size(); i++) {
0615             std::cout << m_using_trigger_bits[i];
0616             if (i < m_using_trigger_bits.size() - 1) std::cout << ", ";
0617           }
0618           std::cout << ")\n";
0619           return Fun4AllReturnCodes::ABORTEVENT;
0620         }
0621       } else {
0622         std::cout << "  [Trigger] No trigger bits specified - accepting all events\n";
0623       }
0624     }
0625    
0626   // ===== End of trigger selection =====
0627 
0628 
0629 
0630   // Get tower containers
0631   auto* emcTowerContainer = getCemcCalibTowers(topNode);
0632   auto* ihcalTowerContainer = getIhcalCalibTowers(topNode);
0633   auto* ohcalTowerContainer = getOhcalCalibTowers(topNode);
0634   
0635   // Calculate total calo energies
0636   m_totalEMCal_energy = 0.f;
0637   m_totalIHCal_energy = 0.f;
0638   m_totalOHCal_energy = 0.f;
0639   
0640   // EMCal towers Loop
0641   if (emcTowerContainer) {
0642     const int emcsize = emcTowerContainer->size();
0643     for (int i = 0; i < emcsize; i++) {
0644       TowerInfo* towerinfo = emcTowerContainer->get_tower_at_channel(i);
0645       if (towerinfo && towerinfo->get_isGood()) {
0646         m_totalEMCal_energy += towerinfo->get_energy();
0647       }
0648     }
0649     std::cout << "  [EMCal] Total energy: " << m_totalEMCal_energy << " GeV\n";
0650   }
0651   
0652   // IHCal tower loop
0653   if (ihcalTowerContainer) {
0654     const int ihsize = ihcalTowerContainer->size();
0655     for (int i = 0; i < ihsize; i++) {
0656       TowerInfo* towerinfo = ihcalTowerContainer->get_tower_at_channel(i);
0657       if (towerinfo && towerinfo->get_isGood()) {
0658         m_totalIHCal_energy += towerinfo->get_energy();
0659       }
0660     }
0661     std::cout << "  [IHCal] Total energy: " << m_totalIHCal_energy << " GeV\n";
0662   }
0663   
0664   // Loop  OHCal towers
0665   if (ohcalTowerContainer) {
0666     const int ohsize = ohcalTowerContainer->size();
0667     for (int i = 0; i < ohsize; i++) {
0668       TowerInfo* towerinfo = ohcalTowerContainer->get_tower_at_channel(i);
0669       if (towerinfo && towerinfo->get_isGood()) {
0670         m_totalOHCal_energy += towerinfo->get_energy();
0671       }
0672     }
0673     std::cout << "  [OHCal] Total energy: " << m_totalOHCal_energy << " GeV\n";
0674   }
0675 
0676   // ============================================================================
0677 
0678   // Get event header
0679   auto* eventheader = findNode::getClass<EventHeader>(topNode, "EventHeader");
0680   m_eventnumber = eventheader ? eventheader->get_EvtSequence() : 0;
0681 
0682   // run header
0683   if (auto* rh = findNode::getClass<RunHeader>(topNode, "RunHeader")) {
0684     m_runnumber = rh->get_RunNumber();
0685   }
0686   m_run_total[m_runnumber]++;
0687 
0688   // Nodes
0689   auto* clusters = getCemcClusterContainer(topNode);
0690   auto* emcCalib = getCemcCalibTowers(topNode);
0691   auto* emcRaw   = getCemcRawTowers(topNode);
0692   auto* jets     = getJetContainer(topNode);
0693 
0694   
0695   if (!clusters || (!emcCalib && !emcRaw)) {
0696   std::cout << "[process_event][SKIP] Missing clusters or any CEMC tower container.\n";
0697   return Fun4AllReturnCodes::EVENT_OK;
0698   }
0699 
0700   if (Verbosity()>1) {
0701   std::cout << "  nodes:"
0702             << " clusters=" << (clusters? "OK":"NULL")
0703             << " calibTowers=" << (emcCalib? "OK":"NULL")
0704             << " rawTowers="   << (emcRaw? "OK":"NULL")
0705             << " jets="        << (jets? "OK":"NULL") << std::endl;
0706   }
0707 
0708   if (!clusters || !emcCalib) {
0709     std::cout << "[process_event][SKIP] Missing clusters or calib towers. clusters="
0710               << (clusters?"yes":"no") << " calib="
0711               << (emcCalib?"yes":"no") << "\n";
0712     return Fun4AllReturnCodes::EVENT_OK;
0713   }
0714 
0715   // Z vertex from MBDvertexMap
0716 
0717   float vertexz = -9999.0f;
0718   auto* vertexmap = findNode::getClass<MbdVertexMap>(topNode, "MbdVertexMap");
0719   
0720   if (!vertexmap || vertexmap->empty()) {
0721     if (Verbosity() > 1) {
0722       std::cout << "[process_event][SKIP] No MBD vertex map" << std::endl;
0723     }
0724     return Fun4AllReturnCodes::EVENT_OK;
0725   }
0726   
0727   MbdVertex* mbd_vtx = vertexmap->begin()->second;
0728   if (!mbd_vtx) {
0729     if (Verbosity() > 1) {
0730       std::cout << "[process_event][SKIP] Null vertex pointer" << std::endl;
0731     }
0732     return Fun4AllReturnCodes::EVENT_OK;
0733   }
0734   
0735   vertexz = mbd_vtx->get_z();
0736   
0737   // Check for NaN vertex
0738   if (vertexz != vertexz) {  // NaN check
0739     if (Verbosity() > 1) {
0740       std::cout << "[process_event][SKIP] NaN vertex" << std::endl;
0741     }
0742     return Fun4AllReturnCodes::EVENT_OK;
0743   }
0744   
0745   // Apply vertex cut
0746   if (std::fabs(vertexz) > m_vertex_cut) {
0747     if (Verbosity() > 1) {
0748       std::cout << "[process_event][SKIP] Vertex |z| = " << std::fabs(vertexz) 
0749                 << " cm exceeds cut of " << m_vertex_cut << " cm" << std::endl;
0750     }
0751     return Fun4AllReturnCodes::EVENT_OK;
0752   }
0753   
0754   if (Verbosity() > 2) {
0755     std::cout << "[process_event] Using vertex z = " << vertexz << " cm" << std::endl;
0756   }
0757   // ============================================================================
0758 
0759   const CLHEP::Hep3Vector vertex(0,0,vertexz);
0760 
0761     // Gettin MBD timing
0762   float mbd_time = -999.f;
0763   bool mbd_time_valid = false;
0764   if (auto* mbdpmts = findNode::getClass<MbdPmtContainer>(topNode, "MbdPmtContainer")) {
0765     // Calculating average MBD timing from both sides
0766     float sum_time = 0.f;
0767     int n_valid = 0;
0768     for (int i = 0; i < mbdpmts->get_npmt(); ++i) {
0769       auto* pmt = mbdpmts->get_pmt(i);
0770       if (pmt && pmt->get_q() > 0.4) {  // charge threshold
0771         sum_time += pmt->get_time();
0772         ++n_valid;
0773       }
0774     }
0775     if (n_valid > 0) {
0776       mbd_time = sum_time / n_valid;
0777       mbd_time_valid = true;
0778       std::cout << "  [MBD] Average time = " << mbd_time << " ns (from " << n_valid << " PMTs)\n";
0779     }
0780   } else {
0781     std::cout << "  [MBD][INFO] No MbdPmtContainer found.\n";
0782   }
0783 
0784 
0785   int njet_seen = 0;
0786   if (jets && Verbosity()>2) {
0787   for (const auto* j : *jets) { if (j) ++njet_seen; }
0788   std::cout << "  jets: n=" << njet_seen << std::endl;
0789   }
0790 
0791   // --- Collect jets (for veto and QA) ---
0792   bool has_back_to_back = false;
0793   std::vector<float> jet_pt, jet_phi, jet_E;
0794   if (jets) {
0795     for (const auto* j : *jets) {
0796       if (!j) continue;
0797       jet_pt .push_back(j->get_pt());
0798       jet_phi.push_back(j->get_phi());
0799       jet_E  .push_back(j->get_e());
0800       if (h_jet_pt_vs_phi) h_jet_pt_vs_phi->Fill(j->get_phi(), j->get_pt());
0801     }
0802     std::cout << "[process_event] Njets=" << jet_pt.size() << "\n";
0803 
0804     const int njet = static_cast<int>(jet_pt.size());
0805     // dijet veto -- both jets pT>10, dphi > pi/2
0806     for (int a = 0; a < njet; ++a) {
0807       if (jet_pt[a] <= 10.f) continue;
0808       for (int b = a + 1; b < njet; ++b) {
0809         if (jet_pt[b] <= 10.f) continue;
0810         const float dphi = std::fabs(std::acos(std::cos(jet_phi[a] - jet_phi[b])));
0811         if (h_jet_dphi) h_jet_dphi->Fill(dphi);
0812         if (jet_pt[b] > 0.f && h_dphi_vs_pt_ratio) {
0813           h_dphi_vs_pt_ratio->Fill(dphi, jet_pt[a] / jet_pt[b]);
0814         }
0815         if (dphi > TMath::Pi() / 2.0) {
0816           has_back_to_back = true;
0817           std::cout << "  [Jets] Back-to-back found: a=" << a << " b=" << b
0818                     << " pt(a)=" << jet_pt[a] << " pt(b)=" << jet_pt[b]
0819                     << " dphi=" << dphi << " (>pi/2) -> veto\n";
0820           break;
0821         }
0822       }
0823       if (has_back_to_back) break;
0824     }
0825 
0826     // Jet hemisphere asymmetry
0827     float jet_e_plus=0.f, jet_e_minus=0.f;
0828     for (int i = 0; i < njet; ++i) {
0829       ((std::cos(jet_phi[i])>0) ? jet_e_plus : jet_e_minus) += jet_E[i];
0830     }
0831     const float jet_asym = safe_div(jet_e_plus-jet_e_minus, jet_e_plus+jet_e_minus);
0832     if (h_jet_asym) h_jet_asym->Fill(jet_asym);
0833     std::cout << "  [Jets] has_back_to_back=" << has_back_to_back
0834               << " jet_asym=" << jet_asym << "\n";
0835   }
0836 
0837   // --- Loop clusters ---
0838   bool cluster_condition_met = false;
0839   float e_plus=0.f, e_minus=0.f; // cluster hemisphere 
0840   int ncl = 0;
0841 
0842   RawClusterContainer::ConstRange range = clusters->getClusters();
0843   for (auto it = range.first; it != range.second; ++it) {
0844     const RawCluster* cl = it->second;
0845     if (!cl) continue;
0846     ++ncl;
0847 
0848     const float E  = cl->get_energy();
0849     const float eta = RawClusterUtility::GetPseudorapidity(*cl, vertex);
0850     const float phi = RawClusterUtility::GetAzimuthAngle(*cl, vertex);
0851     const float Et = E / std::cosh(eta);
0852     if (Et < 0.5f) continue; 
0853 
0854     ClusterWindows w; int maxieta=0, maxiphi=0; float cog_eta=0, cog_phi=0;
0855     const bool ok = fillClusterWindows(cl, emcCalib, emcRaw, w, maxieta, maxiphi, cog_eta, cog_phi);
0856     if (!ok) {
0857       std::cout << "  [Cluster] skip: could not build 7x7. Et=" << Et
0858                 << " eta=" << eta << " phi=" << phi << "\n";
0859       continue;
0860     }
0861 
0862     float weta=0.f, weta_cog=0.f, weta_cogx=0.f, wphi_cog=0.f, wphi_cogx=0.f;
0863     computeWidths(w, cog_eta, cog_phi, weta_cog, weta_cogx, wphi_cog, wphi_cogx); 
0864 
0865     float tavg=0, trms=0;
0866     computeTimeMoments(w, tavg, trms);
0867 
0868     bool this_cluster_has_timing = (tavg > -900.f);
0869 
0870     // QA fills (all events)
0871     h_eta_phi_all->Fill(phi, eta);
0872     h_phi_Et_all->Fill(phi, Et);
0873     h_weta_all->Fill(weta);
0874     h_weta_all_x->Fill(weta_cogx);
0875     h_wphi_all->Fill(wphi_cogx);
0876     h_cluster_Et_all->Fill(Et);
0877     if (tavg>-998.f) h_cluster_time_all->Fill(tavg);
0878     if (trms>-998.f) h_time_spread_all->Fill(trms);
0879 
0880     // Hemisphere energy (clusters)
0881     (std::cos(phi)>0 ? e_plus : e_minus) += Et;
0882 
0883 
0884   ////////////////////////////STREAK Events SELECTION////////////////////////////
0885     // Now using: (Weta + Et + timing)
0886    bool passes_weta = (weta_cogx > m_weta_min);
0887     bool passes_et = (Et >= m_et_min);
0888     bool passes_timing = !m_require_valid_timing || this_cluster_has_timing;
0889     
0890     if (passes_weta && passes_et && passes_timing)
0891       cluster_condition_met = true;
0892 
0893     std::cout << "  [Cluster] Et=" << Et
0894               << " eta=" << eta << " phi=" << phi
0895               << " weta(CoG)=" << weta_cog
0896               << " weta_cogx=" << weta_cogx
0897               << " tavg=" << tavg << " trms=" << trms
0898               << " -> pass_timing=" << passes_timing  << "\n";
0899   }
0900   std::cout << "[process_event] Nclusters(looped)=" << ncl << "\n";
0901 
0902 
0903   const float e_asym = safe_div(e_plus - e_minus, e_plus + e_minus);
0904   h_asymmetry->Fill(e_asym);
0905   std::cout << "  [Clusters] e_asym=" << e_asym
0906             << " cluster_condition_met=" << cluster_condition_met << "\n";
0907 
0908   // Event-level streak tag
0909   if (cluster_condition_met && !has_back_to_back) {
0910 
0911   auto [emcal_mean, emcal_rms] = extractCaloTiming(emcTowerContainer, 0.1);
0912   auto [ihcal_mean, ihcal_rms] = extractCaloTiming(ihcalTowerContainer, 0.1);
0913   auto [ohcal_mean, ohcal_rms] = extractCaloTiming(ohcalTowerContainer, 0.1);
0914   
0915   // Require at least one calorimeter has valid timing
0916   bool has_timing = (emcal_mean > -900.f || ihcal_mean > -900.f || ohcal_mean > -900.f);
0917   
0918   std::cout << "  [Timing Check] EMCal: " << emcal_mean << " ns (E=" << m_totalEMCal_energy << " GeV), "
0919             << "IHCal: " << ihcal_mean << " ns (E=" << m_totalIHCal_energy << " GeV), "
0920             << "OHCal: " << ohcal_mean << " ns (E=" << m_totalOHCal_energy << " GeV), "
0921             << "has_timing=" << has_timing << "\n";
0922 
0923     // Check for valid timing if required
0924     if (m_require_valid_timing && !has_timing) {
0925     std::cout << "  [Timing] Event REJECTED - no valid timing in any calorimeter\n";
0926     return Fun4AllReturnCodes::EVENT_OK;
0927   }
0928 
0929     // Check MBD timing cut
0930     bool mbd_cut_pass = true;
0931     if (mbd_time_valid) {
0932       mbd_cut_pass = (mbd_time >= m_mbd_time_min && mbd_time <= m_mbd_time_max);
0933       std::cout << "  [MBD] Timing check: " << mbd_time << " ns, cut=[" 
0934                 << m_mbd_time_min << ", " << m_mbd_time_max << "], pass=" 
0935                 << mbd_cut_pass << "\n";
0936     }
0937     
0938     if (mbd_cut_pass) {
0939       ++m_streak_count;
0940       m_run_streak[m_runnumber]++;
0941       m_streakEvents.emplace_back(m_runnumber, eventnumber);
0942       std::cout << "  [TAG] Event marked as STREAK. run=" << m_runnumber
0943                 << " evt=" << eventnumber << " (streak_count=" << m_streak_count << ")\n";
0944 
0945       // If exclude , skip further processing
0946       if (m_exclude_streaks) {
0947         ++m_excluded_count;
0948         std::cout << "  ***** Event " << eventnumber 
0949                   << " is EXCLUDED - it is a streak *****\n";
0950         return Fun4AllReturnCodes::DISCARDEVENT;  // Skip this event
0951       }
0952 
0953       // Fill streak-specific shapes ( if not excluding)
0954       for (auto it = range.first; it != range.second; ++it) {
0955         const RawCluster* cl = it->second; if (!cl) continue;
0956         const float E  = cl->get_energy();
0957         const float eta = RawClusterUtility::GetPseudorapidity(*cl, vertex);
0958         const float phi = RawClusterUtility::GetAzimuthAngle(*cl, vertex);
0959         const float Et = E / std::cosh(eta);
0960         if (Et < 0.5f) continue;
0961 
0962         ClusterWindows w; int maxieta=0, maxiphi=0; float cog_eta=0, cog_phi=0;
0963         if (!fillClusterWindows(cl, emcCalib, emcRaw, w, maxieta, maxiphi, cog_eta, cog_phi)) continue;
0964 
0965 
0966         float weta=0.f, weta_cog=0.f, weta_cogx=0.f, wphi_cog=0.f, wphi_cogx=0.f;
0967         computeWidths(w, cog_eta, cog_phi, weta_cog, weta_cogx, wphi_cog, wphi_cogx);
0968 
0969         float tavg=-999.f, trms=-999.f; computeTimeMoments(w, tavg, trms);
0970 
0971         h_eta_phi_streak->Fill(phi, eta);
0972         h_phi_Et_streak->Fill(phi, Et);
0973         h_weta_streak->Fill(weta);
0974         h_weta_streak_x->Fill(weta_cogx);
0975         h_wphi_streak->Fill(wphi_cogx);
0976         h_cluster_Et_streak->Fill(Et);
0977 
0978         if (tavg>-998.f) h_cluster_time_streak->Fill(tavg);
0979         if (trms>-998.f) h_time_spread_streak->Fill(trms);
0980       }
0981     } else {
0982       std::cout << "  [TAG] Event NOT a streak (MBD timing failed).\n";
0983     }
0984   } else {
0985     std::cout << "  [TAG] Event NOT a streak."
0986               << " cluster_condition_met=" << cluster_condition_met
0987               << " has_back_to_back=" << has_back_to_back << "\n";
0988   }
0989 
0990   return Fun4AllReturnCodes::EVENT_OK;
0991 }
0992 
0993 // -----------------------------------------------------------
0994 int StreakEventsIdentifier::End(PHCompositeNode* topNode) {
0995   std::cout << "[End] Writing outputs...\n";
0996   if (m_out) {
0997     m_out->cd();
0998     // write histos
0999     h_eta_phi_all->Write(); h_eta_phi_streak->Write();
1000     h_phi_Et_all->Write();  h_phi_Et_streak->Write();
1001     h_weta_all->Write();    h_weta_streak->Write();
1002     h_weta_all_x->Write();    h_weta_streak_x->Write();
1003     h_wphi_all->Write();    h_wphi_streak->Write(); 
1004     h_cluster_Et_all->Write(); h_cluster_Et_streak->Write();
1005     h_asymmetry->Write(); h_jet_dphi->Write(); h_jet_asym->Write();
1006     h_dphi_vs_pt_ratio->Write(); h_jet_pt_vs_phi->Write();
1007     h_cluster_time_all->Write(); h_cluster_time_streak->Write();
1008     h_time_spread_all->Write();  h_time_spread_streak->Write();
1009 
1010   }
1011 
1012   // Event list
1013   {
1014     const std::string evlist = m_outprefix + "_event_list.txt";
1015     std::ofstream fout(evlist);
1016     fout << "# run event totalInRun\n";
1017     for (auto& ev : m_streakEvents) {
1018       const int run = ev.first; const int evt = ev.second;
1019       const int tot = m_run_total[run];
1020       fout << run << " " << evt << " " << tot << "\n";
1021     }
1022     std::cout << "[End] Wrote event list: " << evlist << " (N=" << m_streakEvents.size() << ")\n";
1023   }
1024 
1025   // Run summary
1026   {
1027     const std::string runsum = m_outprefix + std::string("_run_summary.txt");
1028     std::ofstream rs(runsum);
1029     rs << "# RunNumber TotalEvents StreakEvents Rate(%)\n";
1030     for (auto& kv : m_run_total) {
1031       const int run = kv.first; const int tot = kv.second;
1032       const int stk = m_run_streak[run];
1033       const double rate = tot>0 ? 100.0*double(stk)/double(tot) : 0.0;
1034       rs << run << " " << tot << " " << stk << " " << std::fixed << std::setprecision(3) << rate << "\n";
1035     }
1036     std::cout << "[End] Wrote run summary: " << runsum << "\n";
1037   }
1038 
1039   // PNGs
1040   if (!m_png_dir.empty()) {
1041     std::cout << "[End] Saving PNGs into: " << m_png_dir << "\n";
1042     gSystem->mkdir(m_png_dir.c_str(), kTRUE);
1043     savePNGs();
1044   } else {
1045     std::cout << "[End] PNG directory empty; skipping PNGs.\n";
1046   }
1047 
1048   std::cout << "\n=== StreakEventsIdentifier Summary ===" << std::endl;
1049   std::cout << "  Total events processed: " << m_evt_processed << std::endl;
1050   std::cout << "  Total streak events:    " << m_streak_count << std::endl;
1051   if (m_exclude_streaks) {
1052     std::cout << "  Events EXCLUDED:        " << m_excluded_count << std::endl;
1053   }
1054   std::cout << "  Output written to:      " << m_outprefix << "_results.root" << std::endl;
1055   std::cout << "=============================================\n" << std::endl;
1056 
1057   if (m_out) { m_out->Write(); m_out->Close(); }
1058   return 0;
1059 }
1060 
1061 // -----------------------------------------------------------
1062 void StreakEventsIdentifier::bookHistos() {
1063   auto mk1 = [&](const char* n,const char* t,int nb,double lo,double hi){ auto* h=new TH1F(n,t,nb,lo,hi); h->SetDirectory(nullptr); return h; };
1064   auto mk2 = [&](const char* n,const char* t,int nbx,double xlo,double xhi,int nby,double ylo,double yhi){ auto* h=new TH2F(n,t,nbx,xlo,xhi,nby,ylo,yhi); h->SetDirectory(nullptr); return h; };
1065 
1066   std::cout << "[bookHistos] Booking histograms...\n";
1067   h_eta_phi_all     = mk2("h_eta_phi_all"   ,"Eta vs Phi (All);#phi;#eta",64,-3.2,3.2,64,-1.1,1.1);
1068   h_eta_phi_streak  = mk2("h_eta_phi_streak","Eta vs Phi (Streak);#phi;#eta",64,-3.2,3.2,64,-1.1,1.1);
1069   h_phi_Et_all      = mk2("h_phi_Et_all"    ,"Cluster E_{T} vs #phi (All);#phi;E_{T} [GeV]",64,-3.2,3.2,100,0,50);
1070   h_phi_Et_streak   = mk2("h_phi_Et_streak" ,"Cluster E_{T} vs #phi (Streak);#phi;E_{T} [GeV]",64,-3.2,3.2,100,0,50);
1071 
1072   h_weta_all_x    = mk1("h_weta_all_x",   "Cluster w_{#eta} (All, CoGx);w_{#eta}^{CoGx};Counts",100,0,3.0);
1073   h_weta_streak_x = mk1("h_weta_streak_x","Cluster w_{#eta} (Streak, CoGx);w_{#eta}^{CoGx};Counts",100,0,3.0);
1074  
1075   h_weta_all    = mk1("h_weta_all",   "Cluster w_{#eta} (All);w_{#eta};Counts",100,0,3.0);
1076   h_weta_streak = mk1("h_weta_streak","Cluster w_{#eta} (Streak);w_{#eta};Counts",100,0,3.0);
1077   
1078   h_wphi_all    = mk1("h_wphi_all",   "Cluster w_{#phi} (All, CoGx);w_{#phi}^{CoGx};Counts",100,0,3.0);
1079   h_wphi_streak = mk1("h_wphi_streak","Cluster w_{#phi} (Streak, CoGx);w_{#phi}^{CoGx};Counts",100,0,3.0);
1080 
1081   h_cluster_Et_all  = mk1("h_cluster_Et_all","Cluster E_{T} (All);E_{T} [GeV];Counts",100,0,100);
1082   h_cluster_Et_streak= mk1("h_cluster_Et_streak","Cluster E_{T} (Streak);E_{T} [GeV];Counts",100,0,100);
1083   h_asymmetry       = mk1("h_asymmetry"     ,"Hemisphere Energy Asymmetry;E_{diff};Counts",100,-1,1);
1084   h_jet_dphi        = mk1("h_jet_dphi"      ,"#Delta#phi between leading jets;#Delta#phi;Counts",64,0,M_PI);
1085   h_jet_asym        = mk1("h_jet_asym"      ,"Jet energy hemisphere asymmetry;Asymmetry;Counts",100,-1,1);
1086   h_dphi_vs_pt_ratio= mk2("h_dphi_vs_pt_ratio","#Delta#phi vs p_{T} Ratio;#Delta#phi;p_{T,lead}/p_{T,sublead}",64,0,M_PI,100,0,10);
1087   h_jet_pt_vs_phi   = mk2("h_jet_pt_vs_phi" ,"Jet p_{T} vs #phi;#phi;p_{T} [GeV]",64,-M_PI,M_PI,100,0,100);
1088   h_cluster_time_all   = mk1("h_cluster_time_all"  ,"Cluster Timing (All);Time [ns];Counts",100,-50,50);
1089   h_cluster_time_streak= mk1("h_cluster_time_streak","Cluster Timing (Streak);Time [ns];Counts",100,-50,50);
1090   h_time_spread_all    = mk1("h_time_spread_all"   ,"Cluster Time Spread (All);Time RMS [ns];Counts",100,0,20);
1091   h_time_spread_streak = mk1("h_time_spread_streak","Cluster Time Spread (Streak);Time RMS [ns];Counts",100,0,20);
1092   std::cout << "[bookHistos] Done.\n";
1093 }
1094 
1095 void StreakEventsIdentifier::savePNGs() const {
1096   std::cout << "[savePNGs] Producing PNGs...\n";
1097   auto Save1D = [&](TH1* h,const std::string& tag,bool logy=false){ TCanvas c("c1","",800,700); if (logy) c.SetLogy(); h->Draw("HIST"); c.SaveAs((m_png_dir+"/"+tag+".png").c_str()); };
1098   auto Save2D = [&](TH2* h,const std::string& tag){ TCanvas c("c2","",900,800); h->SetContour(60); h->Draw("COLZ"); c.SaveAs((m_png_dir+"/"+tag+".png").c_str()); };
1099 
1100   Save2D(h_eta_phi_all, "eta_phi_all");
1101   Save2D(h_eta_phi_streak, "eta_phi_streak");
1102   Save2D(h_phi_Et_all, "phi_vs_Et_all");
1103   Save2D(h_phi_Et_streak, "phi_vs_Et_streak");
1104   Save1D(h_weta_all, "weta_all");
1105   Save1D(h_weta_streak, "weta_streak");
1106   Save1D(h_weta_all_x, "weta_all_x");
1107   Save1D(h_weta_streak_x, "weta_streak_x");
1108   Save1D(h_wphi_all, "wphi_all");        
1109   Save1D(h_wphi_streak, "wphi_streak");  
1110   Save1D(h_jet_dphi, "jet_dphi");
1111   Save1D(h_asymmetry, "hemi_asym_all");
1112   Save1D(h_jet_asym, "jet_hemi_asym_all");
1113   Save1D(h_cluster_time_all, "cluster_time_all");
1114   Save1D(h_cluster_time_streak, "cluster_time_streak");
1115   Save1D(h_time_spread_all, "time_spread_all");
1116   Save1D(h_time_spread_streak, "time_spread_streak");
1117   std::cout << "[savePNGs] Done.\n";
1118 }
1119 // -----------------------------------------------------------