diff --git a/Common/MathUtils/include/MathUtils/MathBase.h b/Common/MathUtils/include/MathUtils/MathBase.h index c6b3894b9a8c8..f4c26c4a0adfc 100644 --- a/Common/MathUtils/include/MathUtils/MathBase.h +++ b/Common/MathUtils/include/MathUtils/MathBase.h @@ -24,8 +24,16 @@ #include "TLinearFitter.h" #include "TVectorD.h" #include "TMath.h" +#include "TF1.h" +#include "Foption.h" +#include "HFitInterface.h" +#include "TFitResultPtr.h" +#include "TFitResult.h" +#include "Fit/Fitter.h" +#include "Fit/BinData.h" +#include "Math/WrappedMultiTF1.h" -#include +#include "Framework/Logger.h" namespace o2 { @@ -33,6 +41,83 @@ namespace math_utils { namespace math_base { +/// fit 1D array of histogrammed data with generic root function +/// +/// The code was extracted out of ROOT to be able to do fitting on an array with histogrammed data +/// instead of root histograms. +/// It is a stripped down version, so does not provide the same functionality. +/// To be used with care. +/// +/// \param[in] nbins size of the array and number of histogram bins +/// \param[in] arr array with elements +/// \param[in] xMin minimum range of the array +/// \param[in] xMax maximum range of the array +/// \param[in] func fit function +/// +/// +template +TFitResultPtr fit(const size_t nBins, const T* arr, const T xMin, const T xMax, TF1& func, std::string_view option = "") +{ + Foption_t fitOption; + ROOT::Fit::FitOptionsMake(ROOT::Fit::kHistogram, option.data(), fitOption); + + ROOT::Fit::DataRange range(xMin, xMax); + ROOT::Fit::DataOptions opt; + ROOT::Fit::BinData fitdata(opt, range); + fitdata.Initialize(nBins, 1); + + // create an empty TFitResult + std::shared_ptr tfr(new TFitResult()); + // create the fitter from an empty fit result + //std::shared_ptr fitter(new ROOT::Fit::Fitter(std::static_pointer_cast(tfr) ) ); + ROOT::Fit::Fitter fitter(tfr); + //ROOT::Fit::FitConfig & fitConfig = fitter->Config(); + + const double binWidth = double(xMax - xMin) / double(nBins); + + for (Int_t ibin = 0; ibin < nBins; ibin++) { + const double x = double(xMin) + double(ibin + 0.5) * binWidth; + const double y = double(arr[ibin]); + const double ey = std::sqrt(y); + fitdata.Add(x, y, ey); + } + + const int special = func.GetNumber(); + const int npar = func.GetNpar(); + bool linear = func.IsLinear(); + if (special == 299 + npar) { + linear = kTRUE; // for polynomial functions + } + // do not use linear fitter in these case + if (fitOption.Bound || fitOption.Like || fitOption.Errors || fitOption.Gradient || fitOption.More || fitOption.User || fitOption.Integral || fitOption.Minuit) { + linear = kFALSE; + } + + if (special != 0 && !fitOption.Bound && !linear) { + if (special == 100) { + ROOT::Fit::InitGaus(fitdata, &func); // gaussian + } else if (special == 400) { + ROOT::Fit::InitGaus(fitdata, &func); // landau (use the same) + } else if (special == 200) { + ROOT::Fit::InitExpo(fitdata, &func); // exponential + } + } + + if ((linear || fitOption.Gradient)) { + fitter.SetFunction(ROOT::Math::WrappedMultiTF1(func)); + } else { + fitter.SetFunction(static_cast(ROOT::Math::WrappedMultiTF1(func))); + } + + // standard least square fit + const bool fitok = fitter.Fit(fitdata, fitOption.ExecPolicy); + if (!fitok) { + LOGP(warning, "bad fit"); + } + + return TFitResultPtr(tfr); +} + /// fast fit of an array with ranges (histogram) with gaussian function /// /// Fitting procedure: diff --git a/Common/MathUtils/src/MathUtilsLinkDef.h b/Common/MathUtils/src/MathUtilsLinkDef.h index 687d68c4d1fcb..7be4951a0bee5 100644 --- a/Common/MathUtils/src/MathUtilsLinkDef.h +++ b/Common/MathUtils/src/MathUtilsLinkDef.h @@ -19,6 +19,9 @@ #pragma link C++ namespace o2::math_utils::math_base; +#pragma link C++ function o2::math_utils::math_base::fit < float>; +#pragma link C++ function o2::math_utils::math_base::fit < double>; + #pragma link C++ function o2::math_utils::math_base::fitGaus < float>; #pragma link C++ function o2::math_utils::math_base::fitGaus < double>; diff --git a/DataFormats/Detectors/TPC/CMakeLists.txt b/DataFormats/Detectors/TPC/CMakeLists.txt index af8585fc1800f..0f7bbc7509967 100644 --- a/DataFormats/Detectors/TPC/CMakeLists.txt +++ b/DataFormats/Detectors/TPC/CMakeLists.txt @@ -42,7 +42,8 @@ o2_target_root_dictionary( include/DataFormatsTPC/Defs.h include/DataFormatsTPC/dEdxInfo.h include/DataFormatsTPC/CompressedClusters.h - include/DataFormatsTPC/ZeroSuppression.h) + include/DataFormatsTPC/ZeroSuppression.h + include/DataFormatsTPC/ZeroSuppressionLinkBased.h) o2_add_test( ClusterNative diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h index 97342f8caa22e..5e75d2b708bb0 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h @@ -62,8 +62,9 @@ enum class PadSubset : char { /// Statistics type enum class StatisticsType { - GausFit, ///< Use Gaus fit for pedestal and noise - MeanStdDev ///< Use mean and standard deviation + GausFit, ///< Use slow gaus fit (better fit stability) + GausFitFast, ///< Use fast gaus fit (less accurate error treatment) + MeanStdDev ///< Use mean and standard deviation }; // default point definitions for PointND, PointNDlocal, PointNDglobal are in diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/LaserTrack.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/LaserTrack.h index 5fcc377df76c5..7f9bbf388dc5a 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/LaserTrack.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/LaserTrack.h @@ -12,6 +12,7 @@ #define ALICEO2_TPC_LASERTRACK #include +#include #include "ReconstructionDataFormats/Track.h" @@ -80,6 +81,18 @@ class LaserTrackContainer /// \return array of laser tracks const auto& getLaserTracks() const { return mLaserTracks; } + /// dump tracks to a tree for simple visualization + static void dumpToTree(const std::string_view fileName); + + /// get span with tracks in one bundle + gsl::span getTracksInBundle(int side, int rod, int bundle) + { + const int startID = LaserTrack::NumberOfTracks / 2 * side + + LaserTrack::BundlesPerRod * LaserTrack::TracksPerBundle * rod + + LaserTrack::TracksPerBundle * bundle; + return gsl::span(&mLaserTracks[startID], LaserTrack::TracksPerBundle); + } + private: std::array mLaserTracks; diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/ZeroSuppressionLinkBased.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/ZeroSuppressionLinkBased.h new file mode 100644 index 0000000000000..c6275f0a4da49 --- /dev/null +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/ZeroSuppressionLinkBased.h @@ -0,0 +1,180 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file ZeroSuppressionLinkBased.h +/// \brief definitions to deal with the link based zero suppression format +/// \author Jens Wiechula + +#ifndef ALICEO2_DATAFORMATSTPC_ZeroSuppressionLinkBased_H +#define ALICEO2_DATAFORMATSTPC_ZeroSuppressionLinkBased_H + +#include + +namespace o2 +{ +namespace tpc +{ +namespace zerosupp_link_based +{ + +static constexpr uint32_t DataWordSizeBits = 128; ///< size of header word and data words in bits +static constexpr uint32_t DataWordSizeBytes = DataWordSizeBits / 8; ///< size of header word and data words in bytes + +/// header definition of the zero suppressed link based data format +struct Header { + union { + uint64_t word0 = 0; ///< lower 64 bits + struct { /// + uint64_t bitMaskLow : 64; ///< lower bits of the 80 bit bitmask + }; /// + }; /// + /// + union { /// + uint64_t word1 = 0; ///< upper bits of the 80 bit bitmask + struct { /// + uint64_t bitMaskHigh : 16; ///< higher bits of the 80 bit bitmask + uint32_t bunchCrossing : 12; ///< bunch crossing number + uint32_t numWordsPayload : 8; ///< number of 128bit words with 12bit ADC values + uint64_t zero : 28; ///< not used + }; + }; + + std::bitset<80> getChannelBits() + { + return std::bitset<80>((std::bitset<80>(bitMaskHigh) << 64) | std::bitset<80>(bitMaskLow)); + } +}; + +/// empty header for + +/// ADC data container +/// +/// In case of zero suppressed data, the ADC values are stored with 12 bit +/// 10bit + 2bit precision +/// In case of decoded raw data, the pure 10 bit ADC values are stored +/// +/// The data is packed in 128 bit words, or 2x64 bit. Each 64 bit word has 4 bit +/// padding. +/// So it is either 2 x ((5 x 12 bit) + 4 bit padding), or +/// 2 x ((6 x 10 bit) + 4 bit padding) +template +struct Data { + static constexpr uint32_t ChannelsPerHalfWord = DataWordSizeBits / DataBitSizeT / 2; ///< number of ADC values in one 128b word + static constexpr uint32_t DataBitSize = DataBitSizeT; ///< number of bits of the data representation + static constexpr uint32_t SignificantBits = SignificantBitsT; ///< number of bits used for floating point precision + static constexpr uint64_t BitMask = ((uint64_t(1) << DataBitSize) - 1); ///< mask for bits + static constexpr float FloatConversion = 1.f / float(1 << SignificantBits); ///< conversion factor from integer representation to float + + uint64_t adcValues[2]{}; ///< 128bit ADC values (max. 10x12bit) + + /// set ADC 'value' at position 'pos' (0-9) + void setADCValue(uint32_t pos, uint64_t value) + { + const uint32_t word = pos / ChannelsPerHalfWord; + const uint32_t posInWord = pos % ChannelsPerHalfWord; + + const uint64_t set = (value & BitMask) << (posInWord * DataBitSize); + const uint64_t mask = (0xFFFFFFFFFFFFFFFF ^ (BitMask << (posInWord * DataBitSize))); + + adcValues[word] &= mask; + adcValues[word] |= set; + } + + /// set ADC value from float + void setADCValueFloat(uint32_t pos, float value) + { + setADCValue(pos, uint64_t((value + 0.5f * FloatConversion) / FloatConversion)); + } + + /// get ADC value of channel at position 'pos' (0-9) + uint32_t getADCValue(uint32_t pos) + { + const uint32_t word = pos / ChannelsPerHalfWord; + const uint32_t posInWord = pos % ChannelsPerHalfWord; + + return (adcValues[word] >> (posInWord * DataBitSize)) & BitMask; + } + + /// get ADC value in float + float getADCValueFloat(uint32_t pos) + { + return float(getADCValue(pos)) * FloatConversion; + } + + /// reset all ADC values + void reset() + { + adcValues[0] = 0; + adcValues[1] = 0; + } +}; + +template +struct ContainerT; + +template +struct ContainerT { + Header header; ///< header data + Data data[0]; ///< 128 bit words with 12bit ADC values + + uint32_t dataWords() { return header.numWordsPayload + 1; } +}; + +template +struct ContainerT { + Data data[0]; ///< 128 bit words with 12bit ADC values + + uint32_t dataWords() { return 7; } +}; + +/// Container for decoded data, either zero suppressed or pure raw data +/// +/// In case of pure raw data, no header is needed, since all 80 channels will be filled +template +struct Container { + static constexpr uint32_t ChannelsPerWord = DataWordSizeBits / DataBitSizeT; ///< number of ADC values in one 128b word + + ContainerT cont; ///< Templated data container + + /// return 12bit ADC value for a specific word in the data stream + uint32_t getADCValue(uint32_t word) { return cont.data[word / ChannelsPerWord].getADCValue(word % ChannelsPerWord); } + + /// return 12bit ADC value for a specific word in the data stream converted to float + float getADCValueFloat(uint32_t word) { return cont.data[word / ChannelsPerWord].getADCValueFloat(word % ChannelsPerWord); } + + /// set 12bit ADC value for a specific word in the data stream + void setADCValue(uint32_t word, uint64_t value) { cont.data[word / ChannelsPerWord].setADCValue(word % ChannelsPerWord, value); } + + /// return 12bit ADC value for a specific word in the data stream converted to float + void setADCValueFloat(uint32_t word, float value) { cont.data[word / ChannelsPerWord].setADCValueFloat(word % ChannelsPerWord, value); } + + /// reset all ADC values + void reset() + { + for (int i = 0; i < cont.dataWords(); ++i) { + cont.data[i].reset(); + } + } + + /// get position of next container. Validity check to be done outside! + Container* next() + { + return (Container*)(this + cont.dataWords() * DataWordSizeBytes); + } +}; // namespace zerosupp_link_based + +using ContainerZS = Container<>; +using ContainerDecoded = Container<10, 0, false>; + +} // namespace zerosupp_link_based +} // namespace tpc +} // namespace o2 + +#endif diff --git a/DataFormats/Detectors/TPC/src/DataFormatsTPCLinkDef.h b/DataFormats/Detectors/TPC/src/DataFormatsTPCLinkDef.h index e5b6456f88c65..183ef90122d62 100644 --- a/DataFormats/Detectors/TPC/src/DataFormatsTPCLinkDef.h +++ b/DataFormats/Detectors/TPC/src/DataFormatsTPCLinkDef.h @@ -39,5 +39,6 @@ #pragma link C++ class o2::tpc::CompressedClustersCounters + ; #pragma link C++ class o2::tpc::CompressedClustersPtrs_helper < o2::tpc::CompressedClustersCounters> + ; #pragma link C++ class o2::tpc::CompressedClusters + ; +#pragma link C++ enum o2::tpc::StatisticsType; #endif diff --git a/DataFormats/Detectors/TPC/src/LaserTrack.cxx b/DataFormats/Detectors/TPC/src/LaserTrack.cxx index 27b810931fa37..0700cadb6d05f 100644 --- a/DataFormats/Detectors/TPC/src/LaserTrack.cxx +++ b/DataFormats/Detectors/TPC/src/LaserTrack.cxx @@ -12,11 +12,16 @@ /// \brief Laser track parameters /// \author Jens Wiechula, jens.wiechula@ikf.uni-frankfurt.de +#include #include #include #include +#include #include +#include "TFile.h" +#include "TTree.h" + #include "DataFormatsTPC/LaserTrack.h" using namespace o2::tpc; @@ -45,3 +50,22 @@ void LaserTrackContainer::loadTracksFromFile() mLaserTracks[id] = LaserTrack(id, x, alpha, {p0, p1, p2, p3, p4}); } } + +void LaserTrackContainer::dumpToTree(const std::string_view fileName) +{ + LaserTrackContainer c; + c.loadTracksFromFile(); + const auto& tracks = c.getLaserTracks(); + std::vector vtracks; + + for (const auto& track : tracks) { + vtracks.emplace_back(track); + } + + std::unique_ptr fout(TFile::Open(fileName.data(), "recreate")); + TTree t("laserTracks", "Laser Tracks"); + t.Branch("tracks", &vtracks); + t.Fill(); + fout->Write(); + fout->Close(); +} diff --git a/Detectors/TPC/base/CMakeLists.txt b/Detectors/TPC/base/CMakeLists.txt index 8abfe3d1a5ec1..b9a70e55b847d 100644 --- a/Detectors/TPC/base/CMakeLists.txt +++ b/Detectors/TPC/base/CMakeLists.txt @@ -32,6 +32,7 @@ o2_add_library(TPCBase src/PartitionInfo.cxx src/ROC.cxx src/Sector.cxx + src/Utils.cxx PUBLIC_LINK_LIBRARIES Vc::Vc Boost::boost O2::DataFormatsTPC O2::CCDB FairRoot::Base) @@ -58,7 +59,8 @@ o2_target_root_dictionary(TPCBase include/TPCBase/ParameterGEM.h include/TPCBase/PartitionInfo.h include/TPCBase/ROC.h - include/TPCBase/Sector.h) + include/TPCBase/Sector.h + include/TPCBase/Utils.h) o2_add_test(Base COMPONENT_NAME tpc diff --git a/Detectors/TPC/base/include/TPCBase/Painter.h b/Detectors/TPC/base/include/TPCBase/Painter.h index e1a6eaf851e6d..41b1b87f224f1 100644 --- a/Detectors/TPC/base/include/TPCBase/Painter.h +++ b/Detectors/TPC/base/include/TPCBase/Painter.h @@ -16,6 +16,7 @@ /// \author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de /// +#include #include "DataFormatsTPC/Defs.h" class TH1; @@ -48,7 +49,7 @@ namespace painter /// \param CalDet object to draw /// \return TCanvas containing CalDet content template -TCanvas* draw(const CalDet& calDet); +TCanvas* draw(const CalDet& calDet, int nbins1D = 300, float xMin1D = 0, float xMax1D = 0); /// Drawing of a CalDet object /// \param CalArray object to draw @@ -70,6 +71,19 @@ TH2* getHistogram2D(const CalDet& calDet, Side side); template TH2* getHistogram2D(const CalArray& calArray); +/// Create summary canvases for a CalDet object +/// +/// 1 Canvas with 2D and 1D distributions for each side +/// 1 Canvas with 2D distributions for all ROCs +/// 1 Canvas with 1D distributions for all ROCs +/// \param CalDet object to draw +/// \param nbins1D number of bins used for the 1D projections +/// \param xMin1D minimum value for 1D distribution (xMin = 0 and xMax = 0 for auto scaling) +/// \param xMax1D maximum value for 1D distribution (xMin = 0 and xMax = 0 for auto scaling) +/// \return TCanvas containing CalDet content +template +std::vector makeSummaryCanvases(const CalDet& calDet, int nbins1D = 300, float xMin1D = 0, float xMax1D = 0, bool onlyFilled = true); + } // namespace painter } // namespace tpc diff --git a/Detectors/TPC/base/include/TPCBase/Utils.h b/Detectors/TPC/base/include/TPCBase/Utils.h new file mode 100644 index 0000000000000..d3ff0d5e1e1fd --- /dev/null +++ b/Detectors/TPC/base/include/TPCBase/Utils.h @@ -0,0 +1,58 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_TPC_UTILS_H_ +#define ALICEO2_TPC_UTILS_H_ + +/// +/// \file Utils.h +/// \author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de +/// + +#include +#include + +class TObjArray; +class TCanvas; +class TH1; + +namespace o2 +{ +namespace tpc +{ + +template +class CalDet; + +template +class CalArray; + +/// \namespace utils +/// \brief Common utility functions +/// +/// Common utility functions for drawing, saving, ... +/// +/// origin: TPC +/// \author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de + +namespace utils +{ + +const std::vector tokenize(const std::string_view input, const std::string_view pattern); +TH1* getBinInfoXY(int& binx, int& biny, float& bincx, float& bincy); +void addFECInfo(); +void saveCanvases(TObjArray& arr, std::string_view outDir, std::string_view types = "png,pdf", std::string_view rootFileName = ""); +void saveCanvas(TCanvas& c, std::string_view outDir, std::string_view types); + +} // namespace utils +} // namespace tpc +} // namespace o2 + +#endif diff --git a/Detectors/TPC/base/src/Painter.cxx b/Detectors/TPC/base/src/Painter.cxx index e60993b331ac2..ede2f413556d3 100644 --- a/Detectors/TPC/base/src/Painter.cxx +++ b/Detectors/TPC/base/src/Painter.cxx @@ -10,6 +10,7 @@ #include #include +#include #include "TString.h" #include "TAxis.h" @@ -27,7 +28,7 @@ using namespace o2::tpc; template -TCanvas* painter::draw(const CalDet& calDet) +TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float xMax1D) { using DetType = CalDet; using CalType = CalArray; @@ -48,15 +49,15 @@ TCanvas* painter::draw(const CalDet& calDet) TH1::SetDefaultBufferSize(Sector::MAXSECTOR * mapper.getPadsInSector()); auto hAside1D = new TH1F(Form("h_Aside_1D_%s", name.c_str()), Form("%s (A-Side)", title), - 300, 0, 0); //TODO: modify ranges + nbins1D, xMin1D, xMax1D); //TODO: modify ranges auto hCside1D = new TH1F(Form("h_Cside_1D_%s", name.c_str()), Form("%s (C-Side)", title), - 300, 0, 0); //TODO: modify ranges + nbins1D, xMin1D, xMax1D); //TODO: modify ranges - auto hAside2D = new TH2F(Form("h_Aside_2D_%s;x (cm);y (cm)", name.c_str()), Form("%s (A-Side)", title), + auto hAside2D = new TH2F(Form("h_Aside_2D_%s", name.c_str()), Form("%s (A-Side);x (cm);y (cm)", title), 300, -300, 300, 300, -300, 300); - auto hCside2D = new TH2F(Form("h_Cside_2D_%s;x (cm);y (cm)", name.c_str()), Form("%s (C-Side)", title), + auto hCside2D = new TH2F(Form("h_Cside_2D_%s", name.c_str()), Form("%s (C-Side);x (cm);y (cm)", title), 300, -300, 300, 300, -300, 300); for (ROC roc; !roc.looped(); ++roc) { @@ -82,8 +83,15 @@ TCanvas* painter::draw(const CalDet& calDet) } } + if (xMax1D > xMin1D) { + hAside2D->SetMinimum(xMin1D); + hAside2D->SetMaximum(xMax1D); + hCside2D->SetMinimum(xMin1D); + hCside2D->SetMaximum(xMax1D); + } + // ===| Draw histograms |===================================================== - auto c = new TCanvas(Form("c_%s", name.c_str()), title); + auto c = new TCanvas(Form("c_%s", name.c_str()), title, 1000, 1000); c->Divide(2, 2); c->cd(1); @@ -130,8 +138,8 @@ TH2* painter::getHistogram2D(const CalDet& calDet, Side side) std::replace(name.begin(), name.end(), ' ', '_'); const char side_name = (side == Side::A) ? 'A' : 'C'; - auto h2D = new TH2F(Form("h_%cside_2D_%s;x (cm);y (cm)", side_name, name.c_str()), - Form("%s (%c-Side)", title, side_name), + auto h2D = new TH2F(Form("h_%cside_2D_%s", side_name, name.c_str()), + Form("%s (%c-Side);x (cm);y (cm)", title, side_name), 300, -300, 300, 300, -300, 300); for (ROC roc; !roc.looped(); ++roc) { @@ -189,30 +197,118 @@ TH2* painter::getHistogram2D(const CalArray& calArray) return hist; } +template +std::enable_if_t::value, bool> hasData(const CalArray& cal) +{ + return std::abs(cal.getSum()) > T{0}; +} + +template +std::enable_if_t::value, bool> hasData(const CalArray& cal) +{ + return cal.getSum() > T{0}; +} + +template +std::vector painter::makeSummaryCanvases(const CalDet& calDet, int nbins1D, float xMin1D, float xMax1D, bool onlyFilled) +{ + + std::vector vecCanvases; + + auto nROCs = calDet.getData().size(); + + if (onlyFilled) { + nROCs = 0; + for (size_t iroc = 0; iroc < calDet.getData().size(); ++iroc) { + const auto& roc = calDet.getCalArray(iroc); + + if (hasData(roc)) { + ++nROCs; + } + } + + if (!nROCs) { + return vecCanvases; + } + } + + // ===| set up canvases |=== + const std::string_view calName = calDet.getName(); + auto cSides = draw(calDet, nbins1D, xMin1D, xMax1D); + auto cROCs1D = new TCanvas(fmt::format("c_ROCs_{}_1D", calName).data(), fmt::format("{} values for each ROC", calName).data(), 1400, 1000); + auto cROCs2D = new TCanvas(fmt::format("c_ROCs_{}_2D", calName).data(), fmt::format("{} values for each ROC", calName).data(), 1400, 1000); + vecCanvases.emplace_back(cSides); + vecCanvases.emplace_back(cROCs1D); + vecCanvases.emplace_back(cROCs2D); + + cROCs1D->DivideSquare(nROCs); + cROCs2D->DivideSquare(nROCs); + + // ===| produce plots for each ROC |=== + size_t pad = 1; + for (size_t iroc = 0; iroc < calDet.getData().size(); ++iroc) { + const auto& roc = calDet.getCalArray(iroc); + + if (onlyFilled && !hasData(roc)) { + continue; + } + + // ===| 1D histogram |=== + auto h1D = new TH1F(fmt::format("h_{}_{:02d}", calName, iroc).data(), fmt::format("{} distribution ROC {:02d};ADC value", calName, iroc).data(), nbins1D, xMin1D, xMax1D); + for (const auto& val : roc.getData()) { + h1D->Fill(val); + } + + // ===| 2D histogram |=== + auto h2D = painter::getHistogram2D(roc); + h2D->SetStats(0); + if (xMax1D > xMin1D) { + h2D->SetMinimum(xMin1D); + h2D->SetMaximum(xMax1D); + } + h2D->SetUniqueID(iroc); + + cROCs1D->cd(pad); + h1D->Draw(); + + cROCs2D->cd(pad); + h2D->Draw("colz"); + + ++pad; + } + + return vecCanvases; +} + // ===| explicit instantiations |=============================================== // this is required to force the compiler to create instances with the types // we usually would like to deal with -template TCanvas* painter::draw(const CalDet& calDet); +template TCanvas* painter::draw(const CalDet& calDet, int, float, float); +template std::vector painter::makeSummaryCanvases(const CalDet& calDet, int, float, float, bool); template TCanvas* painter::draw(const CalArray& calArray); template TH2* painter::getHistogram2D(const CalDet& calDet, Side side); template TH2* painter::getHistogram2D(const CalArray& calArray); -template TCanvas* painter::draw(const CalDet& calDet); +template TCanvas* painter::draw(const CalDet& calDet, int, float, float); +template std::vector painter::makeSummaryCanvases(const CalDet& calDet, int, float, float, bool); template TCanvas* painter::draw(const CalArray& calArray); template TH2* painter::getHistogram2D(const CalDet& calDet, Side side); template TH2* painter::getHistogram2D(const CalArray& calArray); -template TCanvas* painter::draw(const CalDet& calDet); +template TCanvas* painter::draw(const CalDet& calDet, int, float, float); +template std::vector painter::makeSummaryCanvases(const CalDet& calDet, int, float, float, bool); template TCanvas* painter::draw(const CalArray& calArray); template TH2* painter::getHistogram2D(const CalDet& calDet, Side side); template TH2* painter::getHistogram2D(const CalArray& calArray); -template TCanvas* painter::draw(const CalDet& calDet); +template TCanvas* painter::draw(const CalDet& calDet, int, float, float); +template std::vector painter::makeSummaryCanvases(const CalDet& calDet, int, float, float, bool); template TCanvas* painter::draw(const CalArray& calArray); template TH2* painter::getHistogram2D(const CalDet& calDet, Side side); template TH2* painter::getHistogram2D(const CalArray& calArray); -template TCanvas* painter::draw(const CalDet& calDet); +template TCanvas* painter::draw(const CalDet& calDet, int, float, float); +template std::vector painter::makeSummaryCanvases(const CalDet& calDet, int, float, float, bool); template TCanvas* painter::draw(const CalArray& calArray); template TH2* painter::getHistogram2D(const CalDet& calDet, Side side); template TH2* painter::getHistogram2D(const CalArray& calArray); diff --git a/Detectors/TPC/base/src/TPCBaseLinkDef.h b/Detectors/TPC/base/src/TPCBaseLinkDef.h index 0d1f552d9a8e5..3fdef9306a5e0 100644 --- a/Detectors/TPC/base/src/TPCBaseLinkDef.h +++ b/Detectors/TPC/base/src/TPCBaseLinkDef.h @@ -47,18 +47,25 @@ #pragma link C++ class o2::tpc::Sector; #pragma link C++ namespace o2::tpc::painter; -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray ); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray &); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray &); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray &); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray &); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalArray &); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet ); -#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet ); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet &, int, float, float); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet &, int, float, float); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet &, int, float, float); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet &, int, float, float); +#pragma link C++ function o2::tpc::painter::draw(o2::tpc::CalDet &, int, float, float); +#pragma link C++ function o2::tpc::painter::makeSummaryCanvases(o2::tpc::CalDet &, int, float, float, bool); +#pragma link C++ function o2::tpc::painter::makeSummaryCanvases(o2::tpc::CalDet &, int, float, float, bool); +#pragma link C++ function o2::tpc::painter::makeSummaryCanvases(o2::tpc::CalDet &, int, float, float, bool); +#pragma link C++ function o2::tpc::painter::makeSummaryCanvases(o2::tpc::CalDet &, int, float, float, bool); +#pragma link C++ function o2::tpc::painter::makeSummaryCanvases(o2::tpc::CalDet &, int, float, float, bool); + +//#pragma link C++ class std::vector + ; #pragma link C++ class o2::tpc::ParameterDetector; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::ParameterDetector> + ; #pragma link C++ class o2::tpc::ParameterElectronics; @@ -70,4 +77,10 @@ #pragma link C++ struct o2::tpc::ParameterGEM; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::ParameterGEM> + ; +#pragma link C++ namespace o2::tpc::utils; +#pragma link C++ function o2::tpc::utils::tokenize(const std::string_view, const std::string_view); +#pragma link C++ function o2::tpc::utils::getBinInfoXY(int&, int&, float&, float&); +#pragma link C++ function o2::tpc::utils::addFECInfo(); +#pragma link C++ function o2::tpc::utils::saveCanvases(TObjArray*, std::string_view, std::string_view, std::string_view); +#pragma link C++ function o2::tpc::utils::saveCanvas(TCanvas*, std::string_view, std::string_view); #endif diff --git a/Detectors/TPC/base/src/Utils.cxx b/Detectors/TPC/base/src/Utils.cxx new file mode 100644 index 0000000000000..4a2401c3eace2 --- /dev/null +++ b/Detectors/TPC/base/src/Utils.cxx @@ -0,0 +1,131 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include + +#include "TObject.h" +#include "TObjArray.h" +#include "TCanvas.h" +#include "TH1.h" +#include "TFile.h" + +#include "TPCBase/Mapper.h" +#include "TPCBase/Utils.h" + +using namespace o2::tpc; + +/// Inspired by https://stackoverflow.com/questions/9435385/split-a-string-using-c11 +/// could be optimized for speed, see e.g. https://stackoverflow.com/questions/14205096/c11-regex-slower-than-python +const std::vector utils::tokenize(const std::string_view input, const std::string_view pattern) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(pattern.data()); + std::cregex_token_iterator + first{input.begin(), input.end(), re, -1}, + last; + return {first, last}; +} + +TH1* utils::getBinInfoXY(int& binx, int& biny, float& bincx, float& bincy) +{ + TObject* select = gPad->GetSelected(); + if (!select) { + return nullptr; + } + if (!select->InheritsFrom("TH2")) { + gPad->SetUniqueID(0); + return nullptr; + } + + TH1* h = (TH1*)select; + gPad->GetCanvas()->FeedbackMode(kTRUE); + + const int px = gPad->GetEventX(); + const int py = gPad->GetEventY(); + const float xx = gPad->AbsPixeltoX(px); + const float x = gPad->PadtoX(xx); + const float yy = gPad->AbsPixeltoY(py); + const float y = gPad->PadtoX(yy); + binx = h->GetXaxis()->FindBin(x); + biny = h->GetYaxis()->FindBin(y); + bincx = h->GetXaxis()->GetBinCenter(binx); + bincy = h->GetYaxis()->GetBinCenter(biny); + //printf("binx, biny: %d %d\n",binx,biny); + + return h; +} + +void utils::addFECInfo() +{ + using namespace o2::tpc; + const int event = gPad->GetEvent(); + if (event != 51) { + return; + } + + int binx, biny; + float bincx, bincy; + TH1* h = utils::getBinInfoXY(binx, biny, bincx, bincy); + if (!h) { + return; + } + + const float binValue = h->GetBinContent(binx, biny); + const int row = int(std::floor(bincx)); + const int cpad = int(std::floor(bincy)); + + const auto& mapper = Mapper::instance(); + + const int roc = h->GetUniqueID(); + if (roc < 0 || roc >= (int)ROC::MaxROC) + return; + if (row < 0 || row >= (int)mapper.getNumberOfRowsROC(roc)) + return; + const int nPads = mapper.getNumberOfPadsInRowROC(roc, row); + const int pad = cpad + nPads / 2; + //printf("row %d, cpad %d, pad %d, nPads %d\n", row, cpad, pad, nPads); + if (pad < 0 || pad >= (int)nPads) { + return; + } + const int channel = mapper.getPadNumberInROC(PadROCPos(roc, row, pad)); + + const auto& fecInfo = mapper.getFECInfo(PadROCPos(roc, row, pad)); + + std::string title("#splitline{#lower[.1]{#scale[.5]{"); + title += (roc / 18 % 2 == 0) ? "A" : "C"; + title += fmt::format("{:02d} ({:02d}) row: {:02d}, pad: {:03d}, globalpad: {:05d} (in roc)}}}{#scale[.5]{FEC: {:02d}, Chip: {:02d}, Chn: {:02d}, Value: {:.3f}}}", + roc % 18, roc, row, pad, channel, fecInfo.getIndex(), fecInfo.getSampaChip(), fecInfo.getSampaChannel(), binValue); + + h->SetTitle(title.data()); +} + +void utils::saveCanvases(TObjArray& arr, std::string_view outDir, std::string_view types, std::string_view rootFileName) +{ + for (auto c : arr) { + utils::saveCanvas(*static_cast(c), outDir, types); + } + + if (rootFileName.size()) { + std::unique_ptr outFile(TFile::Open(fmt::format("{}/NoiseAndPedestalCanvases.root", outDir).data(), "recreate")); + arr.Write(arr.GetName(), TObject::kSingleKey); + outFile->Close(); + } +} + +void utils::saveCanvas(TCanvas& c, std::string_view outDir, std::string_view types) +{ + const auto typesVec = tokenize(types, ","); + for (const auto& type : typesVec) { + c.SaveAs(fmt::format("{}/{}.{}", outDir, c.GetName(), type).data()); + } +} diff --git a/Detectors/TPC/calibration/CMakeLists.txt b/Detectors/TPC/calibration/CMakeLists.txt index 372168acecc29..db2761d28adcd 100644 --- a/Detectors/TPC/calibration/CMakeLists.txt +++ b/Detectors/TPC/calibration/CMakeLists.txt @@ -18,6 +18,7 @@ o2_add_library(TPCCalibration src/CalibPulserParam.cxx src/CalibTreeDump.cxx src/DigitDump.cxx + src/DigitDumpParam.cxx src/CalibPadGainTracks.cxx PUBLIC_LINK_LIBRARIES O2::DataFormatsTPC O2::TPCBase O2::TPCReconstruction ROOT::Minuit @@ -31,6 +32,7 @@ o2_target_root_dictionary(TPCCalibration include/TPCCalibration/CalibPulserParam.h include/TPCCalibration/CalibTreeDump.h include/TPCCalibration/DigitDump.h + include/TPCCalibration/DigitDumpParam.h include/TPCCalibration/CalibPadGainTracks.h include/TPCCalibration/FastHisto.h) @@ -63,3 +65,6 @@ o2_add_test_root_macro(macro/dumpDigits.C o2_add_test_root_macro(macro/extractGainMap.C PUBLIC_LINK_LIBRARIES O2::TPCCalibration LABELS tpc) +o2_add_test_root_macro(macro/preparePedestalFiles.C + PUBLIC_LINK_LIBRARIES O2::TPCCalibration + LABELS tpc) diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CalibPedestal.h b/Detectors/TPC/calibration/include/TPCCalibration/CalibPedestal.h index 6c0198aa4ae61..f285f93a8e494 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CalibPedestal.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CalibPedestal.h @@ -109,7 +109,7 @@ class CalibPedestal : public CalibRawBase StatisticsType getStatisticsType() const { return mStatisticsType; } /// Dump the relevant data to file - void dumpToFile(const std::string filename) final; + void dumpToFile(const std::string filename, uint32_t type = 0) final; /// Dummy end event void endEvent() final{}; diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CalibPulser.h b/Detectors/TPC/calibration/include/TPCCalibration/CalibPulser.h index 55cad2d3d319f..4ea939dafbf97 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CalibPulser.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CalibPulser.h @@ -135,7 +135,7 @@ class CalibPulser : public CalibRawBase const CalPad& getQtot() const { return mQtot; } /// Dump the relevant data to file - void dumpToFile(const std::string filename) final; + void dumpToFile(const std::string filename, uint32_t type = 0) final; /// Process the end of one raw reader void endReader() final; diff --git a/Detectors/TPC/calibration/include/TPCCalibration/CalibRawBase.h b/Detectors/TPC/calibration/include/TPCCalibration/CalibRawBase.h index 2e632bc0a20e2..8436ee14bbbc3 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/CalibRawBase.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/CalibRawBase.h @@ -118,7 +118,10 @@ class CalibRawBase void rewindEvents(); /// Dump the relevant data to file - virtual void dumpToFile(const std::string filename) {} + virtual void dumpToFile(const std::string filename, uint32_t type = 0) {} + + /// increment number of events + void incrementNEvents() { ++mNevents; } /// number of processed events size_t getNumberOfProcessedEvents() const { return mNevents; } @@ -129,6 +132,9 @@ class CalibRawBase /// check if present event is complete bool isPresentEventComplete() const { return mRawReaderCRUManager.isEventComplete(mPresentEventNumber); } + /// number of processed time bins in last event + void setNumberOfProcessedTimeBins(size_t timeBins) { mProcessedTimeBins = timeBins; } + /// number of processed time bins in last event size_t getNumberOfProcessedTimeBins() const { return mProcessedTimeBins; } @@ -650,6 +656,11 @@ inline Int_t CalibRawBase::update(const PadROCPos& padROCPos, const CRU& cru, co } int rowOffset = 0; + const int nRowIROC = mMapper.getNumberOfRowsROC(0); + const auto& regionInfo = mMapper.getPadRegionInfo(cru.region()); + const int globalRow = row + (cru.isOROC()) * nRowIROC; + const int rowInRegion = globalRow - regionInfo.getGlobalRowOffset(); + switch (mPadSubset) { case PadSubset::ROC: { break; @@ -659,7 +670,6 @@ inline Int_t CalibRawBase::update(const PadROCPos& padROCPos, const CRU& cru, co } case PadSubset::Partition: { const PartitionInfo& partInfo = mMapper.getPartitionInfo(cru.partition()); - const int nRowIROC = mMapper.getNumberOfRowsROC(0); rowOffset = (cru.isOROC()) * nRowIROC; rowOffset -= partInfo.getGlobalRowOffset(); break; @@ -675,7 +685,7 @@ inline Int_t CalibRawBase::update(const PadROCPos& padROCPos, const CRU& cru, co const float signal = float(data[i]); //printf("Call update: %d, %d (%d), %d, %d, %.3f -- cru: %03d, reg: %02d -- FEC: %02d, Chip: %02d, Chn: %02d\n", roc, row, rowOffset, pad, timeBin, signal, cru.number(), cru.region(), fecInfo.getIndex(), fecInfo.getSampaChip(), fecInfo.getSampaChannel()); // TODO: To be implemented - //updateCRU(cru, row, pad, timeBin, signal); + updateCRU(cru, rowInRegion, pad, timeBin, signal); updateROC(roc, row + rowOffset, pad, timeBin, signal); ++timeBin; } diff --git a/Detectors/TPC/calibration/include/TPCCalibration/DigitDump.h b/Detectors/TPC/calibration/include/TPCCalibration/DigitDump.h index dc3a6a136cb66..a24e18d5c84ab 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/DigitDump.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/DigitDump.h @@ -58,6 +58,9 @@ class DigitDump : public CalibRawBase /// default destructor ~DigitDump() override; + /// initialize DigitDump from DigitDumpParam + void init(); + /// update function called once per digit /// /// \param roc readout chamber @@ -102,6 +105,26 @@ class DigitDump : public CalibRawBase mLastTimeBin = last; } + /// sort the digits + void sortDigits(); + + /// clear the digits + void clearDigits() + { + for (auto& digits : mDigits) { + digits.clear(); + } + } + + /// set in memory only mode + void setInMemoryOnly(bool mode = true) { mInMemoryOnly = mode; } + + /// get in memory mode + bool getInMemoryMode() const { return mInMemoryOnly; } + + /// return digits for specific sector + std::vector& getDigits(int sector) { return mDigits[sector]; } + private: std::unique_ptr mPedestal{}; ///< CalDet object with pedestal information std::unique_ptr mNoise{}; ///< CalDet object with noise @@ -120,6 +143,8 @@ class DigitDump : public CalibRawBase int mADCMin{-100}; ///< minimum adc value int mADCMax{1024}; ///< maximum adc value float mNoiseThreshold{-1}; ///< zero suppression threshold in noise sigma + bool mInMemoryOnly{false}; ///< if processing is only done in memory, no file writing + bool mInitialized{false}; ///< if init was called /// set up the output tree void setupOutputTree(); @@ -128,7 +153,7 @@ class DigitDump : public CalibRawBase void loadNoiseAndPedestal(); /// initialize - void init(); + void initInputOutput(); /// End event function void endEvent() final; diff --git a/Detectors/TPC/calibration/include/TPCCalibration/DigitDumpParam.h b/Detectors/TPC/calibration/include/TPCCalibration/DigitDumpParam.h new file mode 100644 index 0000000000000..4054504480e3d --- /dev/null +++ b/Detectors/TPC/calibration/include/TPCCalibration/DigitDumpParam.h @@ -0,0 +1,39 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file DigitDumpParam.h +/// \brief Implementation of the parameter class for the hardware clusterer +/// \author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de + +#ifndef ALICEO2_TPC_DigitDumpParam_H_ +#define ALICEO2_TPC_DigitDumpParam_H_ + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +namespace o2 +{ +namespace tpc +{ + +struct DigitDumpParam : public o2::conf::ConfigurableParamHelper { + int FirstTimeBin{0}; ///< first time bin used in analysis + int LastTimeBin{1000}; ///< first time bin used in analysis + int ADCMin{-100}; ///< minimum adc value + int ADCMax{1024}; ///< maximum adc value + float NoiseThreshold{-1}; ///< zero suppression threshold in noise sigma + std::string PedestalAndNoiseFile{}; ///< file name for the pedestal and nosie file + + O2ParamDef(DigitDumpParam, "TPCDigitDump"); +}; +} // namespace tpc +} // namespace o2 + +#endif // ALICEO2_TPC_HwClustererParam_H_ diff --git a/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C b/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C index a0f5e6aa874da..974e62940bf6d 100644 --- a/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C +++ b/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C @@ -15,37 +15,63 @@ #include "TFile.h" #include "TPCBase/CalDet.h" #include "TPCBase/Painter.h" +#include "TPCBase/Utils.h" #include "TPad.h" #include "TCanvas.h" #include "TH1F.h" #endif -/// Helper to get active histogram and bin information -TH1* GetBinInfoXY(int& binx, int& biny, float& bincx, float& bincy); - -/// Add fec information to the active histogram title -void addFECInfo(); - /// Open pedestalFile and retrieve noise and pedestal values /// Draw then in separate canvases and add an executable to be able to add /// FEC information to the title -void drawNoiseAndPedestal(TString pedestalFile) +/// \param pedestalFile input file name +/// \param mode 0: one canvas per ROC 2D and 1D, 1: one canvas per full TPC, one canvas with all ROCs, for each noise and pedestal +/// \param outDir output directory to store plos in. Don't store if empty +/// \return Array with canvases +TObjArray* drawNoiseAndPedestal(std::string_view pedestalFile, int mode = 0, std::string_view outDir = "") { + if ((mode != 0) && (mode != 1)) { + return 0x0; + } + + TObjArray* arrCanvases = new TObjArray; + arrCanvases->SetName("NoiseAndPedestals"); + using namespace o2::tpc; - TFile f(pedestalFile); + TFile f(pedestalFile.data()); gROOT->cd(); // ===| load noise and pedestal from file |=== CalDet dummy; - CalDet* pedestal = nullptr; - CalDet* noise = nullptr; - f.GetObject("Pedestals", pedestal); - f.GetObject("Noise", noise); + CalDet* calPedestal = nullptr; + CalDet* calNoise = nullptr; + f.GetObject("Pedestals", calPedestal); + f.GetObject("Noise", calNoise); + + // mode 1 handling + if (mode == 1) { + auto arrPedestals = painter::makeSummaryCanvases(*calPedestal, 120, 20.f, 140.f); + auto arrNoise = painter::makeSummaryCanvases(*calNoise, 100, 0.f, 5.f); + + for (auto c : arrPedestals) { + arrCanvases->Add(c); + } + + for (auto c : arrNoise) { + arrCanvases->Add(c); + } + + if (outDir.size()) { + utils::saveCanvases(*arrCanvases, outDir, "png,pdf", "NoiseAndPedestalCanvases.root"); + } + + return arrCanvases; + } // ===| loop over all ROCs |================================================== - for (int iroc = 0; iroc < pedestal->getData().size(); ++iroc) { - const auto& rocPedestal = pedestal->getCalArray(iroc); - const auto& rocNoise = noise->getCalArray(iroc); + for (size_t iroc = 0; iroc < calPedestal->getData().size(); ++iroc) { + const auto& rocPedestal = calPedestal->getCalArray(iroc); + const auto& rocNoise = calNoise->getCalArray(iroc); // only draw if valid data if (!(std::abs(rocPedestal.getSum() + rocNoise.getSum()) > 0)) { @@ -53,12 +79,18 @@ void drawNoiseAndPedestal(TString pedestalFile) } // ===| histograms for noise and pedestal |=== - auto hPedestal = new TH1F(Form("hPedestal%02d", iroc), Form("Pedestal distribution ROC %02d;ADC value", iroc), 150, 0, 150); - auto hNoise = new TH1F(Form("hNoise%02d", iroc), Form("Noise distribution ROC %02d;ADC value", iroc), 100, 0, 5); + auto hPedestal = new TH1F(Form("hPedestal%02zu", iroc), Form("Pedestal distribution ROC %02zu;ADC value", iroc), 150, 0, 150); + auto hNoise = new TH1F(Form("hNoise%02zu", iroc), Form("Noise distribution ROC %02zu;ADC value", iroc), 100, 0, 5); auto hPedestal2D = painter::getHistogram2D(rocPedestal); hPedestal2D->SetStats(0); + hPedestal2D->SetMinimum(20); + hPedestal2D->SetMaximum(140); + hPedestal2D->SetUniqueID(iroc); auto hNoise2D = painter::getHistogram2D(rocNoise); hNoise2D->SetStats(0); + hNoise2D->SetMinimum(0); + hNoise2D->SetMaximum(5); + hNoise2D->SetUniqueID(iroc); // ===| fill 1D histograms |=== for (const auto& val : rocPedestal.getData()) { @@ -74,92 +106,29 @@ void drawNoiseAndPedestal(TString pedestalFile) } // ===| draw histograms |=== - auto cPedestal = new TCanvas(Form("cPedestal%02d", iroc), Form("Pedestals ROC %02d", iroc)); + auto cPedestal = new TCanvas(Form("cPedestal%02zu", iroc), Form("Pedestals ROC %02zu", iroc)); hPedestal->Draw(); - auto cNoise = new TCanvas(Form("cNoise%02d", iroc), Form("Noise ROC %02d", iroc)); + auto cNoise = new TCanvas(Form("cNoise%02zu", iroc), Form("Noise ROC %02zu", iroc)); hNoise->Draw(); - auto cPedestal2D = new TCanvas(Form("cPedestal2D%0d", iroc), Form("Pedestals2D ROC %02d", iroc)); - cPedestal2D->AddExec(Form("addFECInfoPedestal%02d", iroc), "addFECInfo()"); + auto cPedestal2D = new TCanvas(Form("cPedestal2D%02zu", iroc), Form("Pedestals2D ROC %02zu", iroc)); + cPedestal2D->AddExec(Form("addFECInfoPedestal%02zu", iroc), "o2::tpc::utils::addFECInfo()"); hPedestal2D->Draw("colz"); - hPedestal2D->SetUniqueID(iroc); - auto cNoise2D = new TCanvas(Form("cNoise2D%02d", iroc), Form("Noise2D ROC %02d", iroc)); - cNoise2D->AddExec(Form("addFECInfoNoise%02d", iroc), "addFECInfo()"); + auto cNoise2D = new TCanvas(Form("cNoise2D%02zu", iroc), Form("Noise2D ROC %02zu", iroc)); + cNoise2D->AddExec(Form("addFECInfoNoise%02zu", iroc), "o2::tpc::utils::addFECInfo()"); hNoise2D->Draw("colz"); - hNoise2D->SetUniqueID(iroc); - } -} - -TH1* GetBinInfoXY(int& binx, int& biny, float& bincx, float& bincy) -{ - TObject* select = gPad->GetSelected(); - if (!select) - return 0x0; - if (!select->InheritsFrom("TH2")) { - gPad->SetUniqueID(0); - return 0x0; - } - TH1* h = (TH1*)select; - gPad->GetCanvas()->FeedbackMode(kTRUE); - - const int px = gPad->GetEventX(); - const int py = gPad->GetEventY(); - const float xx = gPad->AbsPixeltoX(px); - const float x = gPad->PadtoX(xx); - const float yy = gPad->AbsPixeltoY(py); - const float y = gPad->PadtoX(yy); - binx = h->GetXaxis()->FindBin(x); - biny = h->GetYaxis()->FindBin(y); - bincx = h->GetXaxis()->GetBinCenter(binx); - bincy = h->GetYaxis()->GetBinCenter(biny); - //printf("binx, biny: %d %d\n",binx,biny); - - return h; -} - -void addFECInfo() -{ - using namespace o2::tpc; - const int event = gPad->GetEvent(); - if (event != 51) { - return; + arrCanvases->Add(cPedestal); + arrCanvases->Add(cPedestal2D); + arrCanvases->Add(cNoise); + arrCanvases->Add(cNoise2D); } - int binx, biny; - float bincx, bincy; - TH1* h = GetBinInfoXY(binx, biny, bincx, bincy); - if (!h) { - return; + if (outDir.size()) { + utils::saveCanvases(*arrCanvases, outDir, "png,pdf", "NoiseAndPedestalCanvases.root"); } - const float binValue = h->GetBinContent(binx, biny); - const int row = int(TMath::Floor(bincx)); - const int cpad = int(TMath::Floor(bincy)); - - const auto& mapper = Mapper::instance(); - - const int roc = h->GetUniqueID(); - if (roc < 0 || roc >= (int)ROC::MaxROC) - return; - if (row < 0 || row >= (int)mapper.getNumberOfRowsROC(roc)) - return; - const int nPads = mapper.getNumberOfPadsInRowROC(roc, row); - const int pad = cpad + nPads / 2; - //printf("row %d, cpad %d, pad %d, nPads %d\n", row, cpad, pad, nPads); - if (pad < 0 || pad >= (int)nPads) { - return; - } - const int channel = mapper.getPadNumberInROC(PadROCPos(roc, row, pad)); - - const auto& fecInfo = mapper.getFECInfo(PadROCPos(roc, row, pad)); - - TString title("#splitline{#lower[.1]{#scale[.5]{"); - title += (roc / 18 % 2 == 0) ? "A" : "C"; - title += Form("%02d (%02d) row: %02d, pad: %03d, globalpad: %05d (in roc)}}}{#scale[.5]{FEC: %02d, Chip: %02d, Chn: %02d, Value: %.3f}}", - roc % 18, roc, row, pad, channel, fecInfo.getIndex(), fecInfo.getSampaChip(), fecInfo.getSampaChannel(), binValue); - - h->SetTitle(title.Data()); + return arrCanvases; } diff --git a/Detectors/TPC/calibration/macro/drawPulser.C b/Detectors/TPC/calibration/macro/drawPulser.C index 86edfc4c95ca7..d499010138a3d 100644 --- a/Detectors/TPC/calibration/macro/drawPulser.C +++ b/Detectors/TPC/calibration/macro/drawPulser.C @@ -15,38 +15,64 @@ #include "TFile.h" #include "TPCBase/CalDet.h" #include "TPCBase/Painter.h" +#include "TPCBase/Utils.h" #include "TPad.h" #include "TCanvas.h" #include "TH1F.h" #include "TString.h" #endif -TH1* GetBinInfoXY(int& binx, int& biny, float& bincx, float& bincy); - -/// Add fec information to the active histogram title -void addFECInfo(); - /// Open pedestalFile and retrieve noise and pedestal values /// Draw then in separate canvases and add an executable to be able to add /// FEC information to the title -void drawPulser(TString pulserFile) +TObjArray* drawPulser(TString pulserFile, int mode = 0, std::string_view outDir = "") { + if ((mode != 0) && (mode != 1)) { + return 0x0; + } + + TObjArray* arrCanvases = new TObjArray; + arrCanvases->SetName("Pulser"); + using namespace o2::tpc; TFile f(pulserFile); gROOT->cd(); // ===| load pulser from file |=== CalDet dummy; - CalDet*t0 = nullptr, *width = nullptr, *qtot = nullptr; - f.GetObject("T0", t0); - f.GetObject("Width", width); - f.GetObject("Qtot", qtot); + CalDet*calT0 = nullptr, *calWidth = nullptr, *calQtot = nullptr; + f.GetObject("T0", calT0); + f.GetObject("Width", calWidth); + f.GetObject("Qtot", calQtot); + + // mode 1 handling + if (mode == 1) { + auto arrT0 = painter::makeSummaryCanvases(*calT0, 100, 238.f, 240.f); + auto arrWidth = painter::makeSummaryCanvases(*calWidth, 100, 0.38f, 0.57f); + auto arrQtot = painter::makeSummaryCanvases(*calQtot, 100, 20.f, 280.f); + + for (auto c : arrT0) { + arrCanvases->Add(c); + } + for (auto c : arrWidth) { + arrCanvases->Add(c); + } + for (auto c : arrQtot) { + arrCanvases->Add(c); + } + + if (outDir.size()) { + utils::saveCanvases(*arrCanvases, outDir, "png,pdf", "PulserCanvases.root"); + } + + return arrCanvases; + } // ===| loop over all ROCs |================================================== - for (int iroc = 0; iroc < int(t0->getData().size()); ++iroc) { - const auto& rocT0 = t0->getCalArray(iroc); - const auto& rocWidth = width->getCalArray(iroc); - const auto& rocQtot = qtot->getCalArray(iroc); + for (int iroc = 0; iroc < int(calT0->getData().size()); ++iroc) { + const auto& rocT0 = calT0->getCalArray(iroc); + const auto& rocWidth = calWidth->getCalArray(iroc); + const auto& rocQtot = calQtot->getCalArray(iroc); // only draw if valid data if (!(std::abs(rocT0.getSum() + rocWidth.getSum() + rocQtot.getSum()) > 0)) { @@ -70,7 +96,7 @@ void drawPulser(TString pulserFile) const float minQtot = medianQtot - 50; const float maxQtot = medianQtot + rangeQtot; - // ===| histograms for t0, width and qtot |=== + // ===| histograms for calT0, calWidth and calQtot |=== auto hT0 = new TH1F(Form("hT0%02d", iroc), Form("T0 distribution ROC %02d;time bins (0.2 #mus)", iroc), 100, minT0, maxT0); auto hWidth = new TH1F(Form("hWidth%02d", iroc), Form("Width distribution ROC %02d;time bins (0.2 #mus)", iroc), 100, minWidth, maxWidth); auto hQtot = new TH1F(Form("hQtot%02d", iroc), Form("Qtot distribution ROC %02d;ADC counts", iroc), 100, minQtot, maxQtot); @@ -115,17 +141,17 @@ void drawPulser(TString pulserFile) cPulser->Divide(3, 2); cPulser->cd(1); - gPad->AddExec(Form("addFECInfoT0%02d", iroc), "addFECInfo()"); + gPad->AddExec(Form("addFECInfoT0%02d", iroc), "o2::tpc::utils::addFECInfo()"); hT02D->Draw("colz"); hT02D->SetUniqueID(iroc); cPulser->cd(2); - gPad->AddExec(Form("addFECInfoWidth%02d", iroc), "addFECInfo()"); + gPad->AddExec(Form("addFECInfoWidth%02d", iroc), "o2::tpc::utils::addFECInfo()"); hWidth2D->Draw("colz"); hWidth2D->SetUniqueID(iroc); cPulser->cd(3); - gPad->AddExec(Form("addFECInfoQtot%02d", iroc), "addFECInfo()"); + gPad->AddExec(Form("addFECInfoQtot%02d", iroc), "o2::tpc::utils::addFECInfo()"); hQtot2D->Draw("colz"); hQtot2D->SetUniqueID(iroc); @@ -138,9 +164,14 @@ void drawPulser(TString pulserFile) cPulser->cd(6); hQtot->Draw(); - cPulser->SaveAs(Form("%s.png", cPulser->GetName())); - cPulser->SaveAs(Form("%s.pdf", cPulser->GetName())); + arrCanvases->Add(cPulser); + } + + if (outDir.size()) { + utils::saveCanvases(*arrCanvases, outDir, "png,pdf", "PulserCanvases.root"); } + + return arrCanvases; } TH1* GetBinInfoXY(int& binx, int& biny, float& bincx, float& bincy) diff --git a/Detectors/TPC/calibration/macro/preparePedestalFiles.C b/Detectors/TPC/calibration/macro/preparePedestalFiles.C new file mode 100644 index 0000000000000..0f4e437a9409c --- /dev/null +++ b/Detectors/TPC/calibration/macro/preparePedestalFiles.C @@ -0,0 +1,312 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include + +#include "TFile.h" +#include "TROOT.h" +#include "TSystem.h" + +#include "TPCBase/Mapper.h" +#include "TPCBase/CalDet.h" +#include "TPCBase/Utils.h" +#endif + +struct LinkInfo { + LinkInfo(int cru, int link) : cru(cru), globalLinkID(link) {} + int cru{0}; + int globalLinkID{0}; + + bool operator<(const LinkInfo& other) const + { + if (cru < other.cru) { + return true; + } + if ((cru == other.cru) && (globalLinkID < other.globalLinkID)) { + return true; + } + return false; + } +}; + +using ValueArray = std::array; +using DataMap = std::map; + +void writeValues(const std::string_view fileName, const DataMap& map); +int getHWChannel(int sampa, int channel, int regionIter); + +/// convert float to integer with fixed precision and max number of digits +template +constexpr uint32_t floatToFixedSize(float value) +{ + constexpr uint32_t DataBitSize = DataBitSizeT; ///< number of bits of the data representation + constexpr uint32_t SignificantBits = SignificantBitsT; ///< number of bits used for floating point precision + constexpr uint64_t BitMask = ((uint64_t(1) << DataBitSize) - 1); ///< mask for bits + constexpr float FloatConversion = 1.f / float(1 << SignificantBits); ///< conversion factor from integer representation to float + + const auto adc = uint32_t((value + 0.5f * FloatConversion) / FloatConversion) & BitMask; + assert(std::abs(value - adc * FloatConversion) <= 0.5f * FloatConversion); + + return adc; +} + +template +constexpr float fixedSizeToFloat(uint32_t value) +{ + constexpr uint32_t SignificantBits = SignificantBitsT; ///< number of bits used for floating point precision + constexpr float FloatConversion = 1.f / float(1 << SignificantBits); ///< conversion factor from integer representation to float + + return float(value) * FloatConversion; +} + +void preparePedestalFiles(const std::string_view pedestalFileName, const std::string_view outputDir = "./", float sigmaNoise = 3, float minADC = 2, float pedestalOffset = 0) +{ + static constexpr float FloatConversion = 1.f / float(1 << 2); + + using namespace o2::tpc; + const auto& mapper = Mapper::instance(); + + TFile f(pedestalFileName.data()); + gROOT->cd(); + + // ===| load noise and pedestal from file |=== + CalDet output("Pedestals"); + CalDet* calPedestal = nullptr; + CalDet* calNoise = nullptr; + f.GetObject("Pedestals", calPedestal); + f.GetObject("Noise", calNoise); + + DataMap pedestalValues; + DataMap thresholdlValues; + + // ===| prepare values |=== + for (size_t iroc = 0; iroc < calPedestal->getData().size(); ++iroc) { + const ROC roc(iroc); + + const auto& rocPedestal = calPedestal->getCalArray(iroc); + const auto& rocNoise = calNoise->getCalArray(iroc); + auto& rocOut = output.getCalArray(iroc); + + const int padOffset = (iroc > 35) ? mapper.getPadsInIROC() : 0; + + // skip empty + if (!(std::abs(rocPedestal.getSum() + rocNoise.getSum()) > 0)) { + continue; + } + + //loop over pads + for (size_t ipad = 0; ipad < rocPedestal.getData().size(); ++ipad) { + const int globalPad = ipad + padOffset; + const FECInfo& fecInfo = mapper.fecInfo(globalPad); + const CRU cru = mapper.getCRU(roc.getSector(), globalPad); + const uint32_t region = cru.region(); + const int cruID = cru.number(); + const int sampa = fecInfo.getSampaChip(); + const int sampaChannel = fecInfo.getSampaChannel(); + //int globalLinkID = fecInfo.getIndex(); + + const PartitionInfo& partInfo = mapper.getMapPartitionInfo()[cru.partition()]; + const int nFECs = partInfo.getNumberOfFECs(); + const int fecOffset = (nFECs + 1) / 2; + const int fecInPartition = fecInfo.getIndex() - partInfo.getSectorFECOffset(); + const int dataWrapperID = fecInPartition >= fecOffset; + const int globalLinkID = (fecInPartition % fecOffset) + dataWrapperID * 12; + + float pedestal = rocPedestal.getValue(ipad); + float noise = std::abs(rocNoise.getValue(ipad)); // it seems with the new fitting procedure, the noise can also be negative, since in gaus sigma is quadratic + if ((pedestal < 0) || (pedestal > 1023) || (noise < 0) || (noise > 1023)) { + printf("Bad pedestal or noise value in ROC %2zu, CRU %3d, fec in CRU: %2d, SAMPA: %d, channel: %2d, pedestal: %.4f, noise %.4f, setting both to 0\n", iroc, cruID, fecInPartition, sampa, sampaChannel, pedestal, noise); + pedestal = 0; + noise = 0; + } + const float threshold = std::max(sigmaNoise * noise, minADC); + + const int hwChannel = getHWChannel(sampa, sampaChannel, region % 2); + // for debugging + //printf("%4d %4d %4d %4d %4d: %u\n", cru.number(), globalLinkID, hwChannel, fecInfo.getSampaChip(), fecInfo.getSampaChannel(), getADCValue(pedestal)); + + const auto adcPedestal = floatToFixedSize(pedestal); + const auto adcThreshold = floatToFixedSize(threshold); + pedestalValues[LinkInfo(cruID, globalLinkID)][hwChannel] = adcPedestal; + thresholdlValues[LinkInfo(cruID, globalLinkID)][hwChannel] = adcThreshold; + // for debugging + //if(!(std::abs(pedestal - fixedSizeToFloat(adcPedestal)) <= 0.5 * 0.25)) { + //printf("%4d %4d %4d %4d %4d: %u %.2f %.4f %.4f\n", cru.number(), globalLinkID, hwChannel, sampa, sampaChannel, adcPedestal, fixedSizeToFloat(adcPedestal), pedestal, pedestal - fixedSizeToFloat(adcPedestal)); + //} + } + } + + writeValues((outputDir + "/pedestal_values.txt").Data(), pedestalValues); + writeValues((outputDir + "/threshold_values.txt").Data(), thresholdlValues); +} + +/// return the hardware channel number as mapped in the CRU +// +int getHWChannel(int sampa, int channel, int regionIter) +{ + const int sampaOffet[5] = {0, 4, 8, 0, 4}; + if (regionIter && sampa == 2) { + channel -= 16; + } + int outch = sampaOffet[sampa] + ((channel % 16) % 2) + 2 * (channel / 16) + (channel % 16) / 2 * 10; + return outch; +} + +/// write values of map to fileName +/// +void writeValues(const std::string_view fileName, const DataMap& map) +{ + std::ofstream str(fileName.data(), std::ofstream::out); + + for (const auto& [linkInfo, data] : map) { + std::string values; + for (const auto& val : data) { + if (values.size()) { + values += ","; + } + values += std::to_string(val); + } + + str << linkInfo.cru << " " + << linkInfo.globalLinkID << " " + << values << "\n"; + } +} + +/// convert HW mapping to sampa and channel number +std::tuple getSampaInfo(int hwChannel, int cruID) +{ + static constexpr int sampaMapping[10] = {0, 0, 1, 1, 2, 3, 3, 4, 4, 2}; + static constexpr int channelOffset[10] = {0, 16, 0, 16, 0, 0, 16, 0, 16, 16}; + const int regionIter = cruID % 2; + + const int istreamm = ((hwChannel % 10) / 2); + const int partitionStream = istreamm + regionIter * 5; + const int sampaOnFEC = sampaMapping[partitionStream]; + const int channel = (hwChannel % 2) + 2 * (hwChannel / 10); + const int channelOnSAMPA = channel + channelOffset[partitionStream]; + + return {sampaOnFEC, channelOnSAMPA}; +} + +/// Test input channel mapping vs output channel mapping +/// +/// Consistency check of mapping +void testChannelMapping(int cruID = 0) +{ + const int sampaMapping[10] = {0, 0, 1, 1, 2, 3, 3, 4, 4, 2}; + const int channelOffset[10] = {0, 16, 0, 16, 0, 0, 16, 0, 16, 16}; + const int regionIter = cruID % 2; + + for (std::size_t ichannel = 0; ichannel < 80; ++ichannel) { + const int istreamm = ((ichannel % 10) / 2); + const int partitionStream = istreamm + regionIter * 5; + const int sampaOnFEC = sampaMapping[partitionStream]; + const int channel = (ichannel % 2) + 2 * (ichannel / 10); + const int channelOnSAMPA = channel + channelOffset[partitionStream]; + + const size_t outch = getHWChannel(sampaOnFEC, channelOnSAMPA, regionIter); + printf("%4zu %4d %4d : %4zu %s\n", outch, sampaOnFEC, channelOnSAMPA, ichannel, (outch != ichannel) ? "============" : ""); + } +} + +/// create cal pad object from HW value file +/// +/// if outputFile is set, write the object to file +/// if calPadName is set use it for the object name in the file. Otherwise the basename of the fileName is used +o2::tpc::CalDet getCalPad(const std::string_view fileName, const std::string_view outputFile = "", std::string_view calPadName = "") +{ + using namespace o2::tpc; + const auto& mapper = Mapper::instance(); + + static constexpr float FloatConversion = 1.f / float(1 << 2); + + int cruID{0}; + int globalLinkID{0}; + int sampaOnFEC{0}; + int channelOnSAMPA{0}; + std::string values; + CalDet calPad(gSystem->BaseName(fileName.data())); + + std::string line; + std::ifstream infile(fileName.data(), std::ifstream::in); + if (!infile.is_open()) { + std::cout << "could not open file " << fileName << "\n"; + return calPad; + } + + while (std::getline(infile, line)) { + std::stringstream streamLine(line); + streamLine >> cruID >> globalLinkID >> values; + + const CRU cru(cruID); + const PartitionInfo& partInfo = mapper.getMapPartitionInfo()[cru.partition()]; + const int nFECs = partInfo.getNumberOfFECs(); + const int fecOffset = (nFECs + 1) / 2; + const int fecInPartition = (globalLinkID < fecOffset) ? globalLinkID : fecOffset + globalLinkID % 12; + + int hwChannel{0}; + for (const auto& val : utils::tokenize(values, ",")) { + std::tie(sampaOnFEC, channelOnSAMPA) = getSampaInfo(hwChannel, cru); + const PadROCPos padROCPos = mapper.padROCPos(cru, fecInPartition, sampaOnFEC, channelOnSAMPA); + const float set = fixedSizeToFloat(uint32_t(std::stoi(val))); + calPad.getCalArray(padROCPos.getROC()).setValue(padROCPos.getRow(), padROCPos.getPad(), set); + ++hwChannel; + } + } + + if (outputFile.size()) { + TFile f(outputFile.data(), "recreate"); + if (!calPadName.size()) { + calPadName = calPad.getName(); + } + f.WriteObject(&calPad, calPadName.data()); + } + return calPad; +} + +/// debug differences between two cal pad objects +void debugDiff(std::string_view file1, std::string_view file2, std::string_view objName) +{ + using namespace o2::tpc; + CalPad dummy; + CalPad* calPad1{nullptr}; + CalPad* calPad2{nullptr}; + + std::unique_ptr tFile1(TFile::Open(file1.data())); + std::unique_ptr tFile2(TFile::Open(file2.data())); + gROOT->cd(); + + tFile1->GetObject(objName.data(), calPad1); + tFile2->GetObject(objName.data(), calPad2); + + for (size_t iroc = 0; iroc < calPad1->getData().size(); ++iroc) { + const auto& calArray1 = calPad1->getCalArray(iroc); + const auto& calArray2 = calPad2->getCalArray(iroc); + // skip empty + if (!(std::abs(calArray1.getSum() + calArray2.getSum()) > 0)) { + continue; + } + + for (size_t ipad = 0; ipad < calArray1.getData().size(); ++ipad) { + const auto val1 = calArray1.getValue(ipad); + const auto val2 = calArray2.getValue(ipad); + + if (std::abs(val2 - val1) >= 0.25) { + printf("%2zu %5zu : %.5f - %.5f = %.2f\n", iroc, ipad, val2, val1, val2 - val1); + } + } + } +} diff --git a/Detectors/TPC/calibration/macro/preparePedestalFiles.sh b/Detectors/TPC/calibration/macro/preparePedestalFiles.sh new file mode 100755 index 0000000000000..d5ea493b26cf7 --- /dev/null +++ b/Detectors/TPC/calibration/macro/preparePedestalFiles.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +usage() { + usage="Usage: +preparePedestalFiles.sh [optional arguments] + +required arguments +-i, --inputFile= : input file name + +optional arguments: +-o, --outputDir= : set output directory for (default: ./) +-m, --minADC= : minimal ADC value accepted for threshold (default: 2) +-s, --sigmaNoise= : number of sigmas for the threshold (default: 3) +-h, --help : show this help message" + + echo "$usage" +} + +usageAndExit() { + usage + if [[ "$0" =~ preparePedestalFiles.sh ]]; then + exit 0 + else + return 0 + fi +} + +# ===| default variable values |================================================ +fileInfo= +outputDir="./" +minADC=2 +sigmaNoise=3 + +# ===| parse command line options |============================================= +OPTIONS=$(getopt -l "inputFile:,outputDir:,minADC:,sigmaNoise:,help" -o "i:o:t:m:s:h" -n "preparePedestalFiles.sh" -- "$@") + +if [ $? != 0 ] ; then + usageAndExit +fi + +eval set -- "$OPTIONS" + +while true; do + case "$1" in + --) shift; break;; + -i|--inputFile) inputFile=$2; shift 2;; + -o|--outputDir) outputDir=$2; shift 2;; + -m|--minADC) minADC=$2; shift 2;; + -s|--sigmaNoise) sigmaNoise=$2; shift 2;; + -h|--help) usageAndExit;; + *) echo "Internal error!" ; exit 1 ;; + esac +done + +# ===| check for required arguments |=========================================== +if [[ -z "$inputFile" ]]; then + usageAndExit +fi + +# ===| command building and execution |========================================= +cmd="root.exe -b -q -l -n -x $O2_SRC/Detectors/TPC/calibration/macro/preparePedestalFiles.C+g'(\"$inputFile\",\"$outputDir\", $sigmaNoise, $minADC)'" +echo "running: $cmd" +eval $cmd diff --git a/Detectors/TPC/calibration/macro/runPedestal.C b/Detectors/TPC/calibration/macro/runPedestal.C index 3df9468eff213..926af70d99e33 100644 --- a/Detectors/TPC/calibration/macro/runPedestal.C +++ b/Detectors/TPC/calibration/macro/runPedestal.C @@ -13,11 +13,13 @@ #include #include #include "TFile.h" +#include "TSystem.h" +#include "TH2.h" #include "TPCCalibration/CalibPedestal.h" #include "TPCCalibration/CalibRawBase.h" #endif -void runPedestal(std::vector fileInfos, TString outputFileName = "", Int_t nevents = 100, Int_t adcMin = 0, Int_t adcMax = 1100, Int_t firstTimeBin = 0, Int_t lastTimeBin = 450, Int_t statisticsType = 0, uint32_t verbosity = 0, uint32_t debugLevel = 0, Int_t firstEvent = 0) +void runPedestal(std::vector fileInfos, TString outputFileName = "", Int_t nevents = 100, Int_t adcMin = 0, Int_t adcMax = 1100, Int_t firstTimeBin = 0, Int_t lastTimeBin = 450, Int_t statisticsType = 0, uint32_t verbosity = 0, uint32_t debugLevel = 0, Int_t firstEvent = 0, Bool_t debugOutput = false) { using namespace o2::tpc; CalibPedestal ped; //(PadSubset::Region); @@ -52,5 +54,26 @@ void runPedestal(std::vector fileInfos, TString outputFileName } ped.dumpToFile(outputFileName.Data()); + auto calibPedestal = ped.getPedestal(); + + if (debugOutput) { + TString debugFile = gSystem->DirName(outputFileName); + debugFile.Append("/"); + debugFile.Append("pedestals_debug.root"); + TFile f(debugFile, "recreate"); + TObjArray arr(72); + for (int i = 0; i < 72; ++i) { + const auto& rocPedestal = calibPedestal.getCalArray(i); + + if (!(std::abs(rocPedestal.getSum()) > 0)) { + continue; + } + + auto ch = ped.createControlHistogram(ROC(i)); + arr.Add(static_cast(ch)); + } + arr.Write("histos", TObject::kSingleKey); + f.Write(); + } std::cout << "To display the pedestals run: root.exe $calibMacroDir/drawNoiseAndPedestal.C'(\"" << outputFileName << "\")'\n"; } diff --git a/Detectors/TPC/calibration/macro/runPedestal.sh b/Detectors/TPC/calibration/macro/runPedestal.sh index 8e57955e8ece5..e7d645fb23774 100755 --- a/Detectors/TPC/calibration/macro/runPedestal.sh +++ b/Detectors/TPC/calibration/macro/runPedestal.sh @@ -21,6 +21,7 @@ usage() { echo " -s, --statType= : statistics type - 0: Gaus fit (default), 1: Mean and StdDev" echo " -v, --verbosity= : set verbosity level for raw reader" echo " -d, --debugLevel= : set debug level for raw reader" + echo " -w, --writeDebug : write debug output histograms" echo " -h, --help : show this help message" } @@ -42,12 +43,13 @@ lastTimeBin=450 statisticsType=0 verbosity=0 debugLevel=0 +writeDebug=0 adcMin=0 adcMax=1100 # ===| parse command line options |============================================= -OPTIONS=$(getopt -l "fileInfo:,outputFile:,firstTimeBin:,lastTimeBin:,nevents:,adcMin:,adcMax:,statType:,verbosity:,debugLevel:,help" -o "i:o:t:f:l:n:m:x:s:v:d:h" -n "runPedestal.sh" -- "$@") +OPTIONS=$(getopt -l "fileInfo:,outputFile:,firstTimeBin:,lastTimeBin:,nevents:,adcMin:,adcMax:,statType:,verbosity:,debugLevel:,writeDebug,help" -o "i:o:t:f:l:n:m:x:s:v:d:wh" -n "runPedestal.sh" -- "$@") if [ $? != 0 ] ; then usageAndExit @@ -68,6 +70,7 @@ while true; do -s|--statType) statisticsType=$2; shift 2;; -v|--verbosity) verbosity=$2; shift 2;; -d|--debugLevel) debugLevel=$2; shift 2;; + -w|--writeDebug) writeDebug=1; shift;; -h|--help) usageAndExit;; *) echo "Internal error!" ; exit 1 ;; esac @@ -90,7 +93,7 @@ fi fileInfo=$(echo $fileInfo | sed "s|^|{\"|;s|,|:$lastTimeBin\",\"|g;s|$|\"}|") # ===| command building and execution |========================================= -cmd="root.exe -b -q -l -n -x $O2_SRC/Detectors/TPC/calibration/macro/runPedestal.C'($fileInfo,\"$outputFile\", $nevents, $adcMin, $adcMax, $firstTimeBin, $lastTimeBin, $statisticsType, $verbosity, $debugLevel)'" +cmd="root.exe -b -q -l -n -x $O2_SRC/Detectors/TPC/calibration/macro/runPedestal.C'($fileInfo,\"$outputFile\", $nevents, $adcMin, $adcMax, $firstTimeBin, $lastTimeBin, $statisticsType, $verbosity, $debugLevel, 0, $writeDebug)'" #cmd="perf record -g -o perf.log root.exe -b -q -l -n -x $O2_SRC/Detectors/TPC/calibration/macro/runPedestal.C'($fileInfo,\"$outputFile\", $nevents, $adcMin, $adcMax, $firstTimeBin, $lastTimeBin, $statisticsType, $verbosity, $debugLevel)'" #cmd="valgrind --tool=callgrind --dump-instr=yes --dump-instr=yes root.exe -b -q -l -n -x $O2_SRC/Detectors/TPC/calibration/macro/runPedestal.C'($fileInfo,\"$outputFile\", $nevents, $adcMin, $adcMax, $firstTimeBin, $lastTimeBin, $statisticsType, $verbosity, $debugLevel)'" echo "running: $cmd" diff --git a/Detectors/TPC/calibration/run/calib-pedestal.cxx b/Detectors/TPC/calibration/run/calib-pedestal.cxx index 5a7f4063230e5..647de34fdbedc 100644 --- a/Detectors/TPC/calibration/run/calib-pedestal.cxx +++ b/Detectors/TPC/calibration/run/calib-pedestal.cxx @@ -15,18 +15,34 @@ #include "Framework/ControlService.h" #include "Framework/Logger.h" #include "Framework/ConfigParamSpec.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" #include "DPLUtils/RawParser.h" #include "Headers/DataHeader.h" +#include "CommonUtils/ConfigurableParam.h" +#include "TPCCalibration/CalibPedestal.h" +#include "TPCReconstruction/RawReaderCRU.h" #include +#include using namespace o2::framework; +// customize the completion policy +void customize(std::vector& policies) +{ + using o2::framework::CompletionPolicy; + policies.push_back(CompletionPolicyHelpers::defineByName("calib-pedestal", CompletionPolicy::CompletionOp::Consume)); +} + // we need to add workflow options before including Framework/runDataProcessing void customize(std::vector& workflowOptions) { - workflowOptions.push_back( - ConfigParamSpec{ - "input-spec", VariantType::String, "A:FLP/RAWDATA", {"selection string input specs"}}); + std::vector options{ + {"input-spec", VariantType::String, "A:TPC/RAWDATA", {"selection string input specs"}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings (e.g.: 'TPCCalibPedestal.FirstTimeBin=10;...')"}}, + {"configFile", VariantType::String, "", {"configuration file for configurable parameters"}}}; + + std::swap(workflowOptions, options); } #include "Framework/runDataProcessing.h" @@ -35,65 +51,182 @@ using RDH = o2::header::RAWDataHeader; void printHeader() { - fmt::print("{:>5} {:>4} {:>4} {:>4} {:>3} {:>4} {:>10} {:>5} {:>1}\n", "PkC", "pCnt", "fId", "Mem", "CRU", "GLID", "HBOrbit", "HB BC", "s"); + LOGP(debug, "{:>5} {:>4} {:>4} {:>4} {:>3} {:>4} {:>10} {:>5} {:>1} {:>10}", "PkC", "pCnt", "fId", "Mem", "CRU", "GLID", "HBOrbit", "HB BC", "s", "Trg"); } void printRDH(const RDH& rdh) { const int globalLinkID = int(rdh.linkID) + (((rdh.word1 >> 32) >> 28) * 12); - fmt::print("{:>5} {:>4} {:>4} {:>4} {:>3} {:>4} {:>10} {:>5} {:>1}\n", (uint64_t)rdh.packetCounter, (uint64_t)rdh.pageCnt, (uint64_t)rdh.feeId, (uint64_t)(rdh.memorySize), (uint64_t)rdh.cruID, (uint64_t)globalLinkID, (uint64_t)rdh.heartbeatOrbit, (uint64_t)rdh.heartbeatBC, (uint64_t)rdh.stop); + LOGP(debug, "{:>5} {:>4} {:>4} {:>4} {:>3} {:>4} {:>10} {:>5} {:>1} {:#010X}", (uint64_t)rdh.packetCounter, (uint64_t)rdh.pageCnt, (uint64_t)rdh.feeId, (uint64_t)(rdh.memorySize), (uint64_t)rdh.cruID, (uint64_t)globalLinkID, (uint64_t)rdh.heartbeatOrbit, (uint64_t)rdh.heartbeatBC, (uint64_t)rdh.stop, (uint64_t)rdh.triggerType); } WorkflowSpec defineDataProcessing(ConfigContext const& config) { + using namespace o2::tpc; - WorkflowSpec workflow; - workflow.emplace_back(DataProcessorSpec{ - "calib-pedestal", - select(config.options().get("input-spec").c_str()), - Outputs{}, - AlgorithmSpec{[](InitContext& setup) { // - return adaptStateless([](InputRecord& inputs, DataAllocator& outputs) { - for (auto& input : inputs) { - const auto* dh = DataRefUtils::getHeader(input); - LOG(INFO) << dh->dataOrigin.as() << "/" << dh->dataDescription.as() << "/" - << dh->subSpecification << " payload size " << dh->payloadSize; - - // there is a bug in InpuRecord::get for vectors of simple types, not catched in - // DataAllocator unit test - //auto data = inputs.get>(input.spec->binding.c_str()); - //LOG(INFO) << "data size " << data.size(); - printHeader(); - try { - const char* pos = input.payload; - const char* last = pos + dh->payloadSize; - - o2::framework::RawParser parser(input.payload, dh->payloadSize); - - //while (pos < last) { - for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { - auto* rdhPtr = it.get_if(); - if (!rdhPtr) { + // set up configuration + o2::conf::ConfigurableParam::updateFromFile(config.options().get("configFile")); + o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2tpccalibration_configuration.ini"); + + struct ProcessAttributes { + CalibPedestal calibPedestal; + rawreader::RawReaderCRUManager rawReader; + uint32_t lastOrbit{0}; + uint64_t lastTFID{0}; + uint32_t maxEvents{100}; + bool quit{false}; + bool dumped{false}; + }; + + auto initFunction = [](InitContext& ic) { + auto processAttributes = std::make_shared(); + // set up calibration + // TODO: + // it is a bit ugly to use the RawReaderCRUManager for this is. + // At some point the raw reader code should be cleaned up and modularized + { + auto& pedestal = processAttributes->calibPedestal; + pedestal.init(); // initialize configuration via configKeyValues + processAttributes->rawReader.createReader(""); + processAttributes->rawReader.setADCDataCallback([&pedestal](const PadROCPos& padROCPos, const CRU& cru, const gsl::span data) -> Int_t { + Int_t timeBins = pedestal.update(padROCPos, cru, data); + pedestal.setNumberOfProcessedTimeBins(std::max(pedestal.getNumberOfProcessedTimeBins(), size_t(timeBins))); + return timeBins; + }); + processAttributes->maxEvents = static_cast(ic.options().get("max-events")); + } + + auto processingFct = [processAttributes](ProcessingContext& pc) { + // in case the maximum number of events was reached don't do further processing + if (processAttributes->quit) { + return; + } + + if (pc.inputs().isValid("TFID")) { + auto tfid = pc.inputs().get("TFID"); + LOGP(info, "TFid: {}", tfid); + processAttributes->lastTFID = tfid; + } + + for (auto& input : pc.inputs()) { + const auto* dh = DataRefUtils::getHeader(input); + + // ---| only process RAWDATA, there might be a nicer way to do this |--- + if (dh == nullptr || dh->dataDescription != o2::header::gDataDescriptionRawData) { + continue; + } + + // ---| extract hardware information to do the processing |--- + const auto subSpecification = dh->subSpecification; + const auto cruID = subSpecification >> 16; + const auto linkID = ((subSpecification + (subSpecification >> 8)) & 0xFF) - 1; + const auto dataWrapperID = ((subSpecification >> 8) & 0xFF) > 0; + const auto globalLinkID = linkID + dataWrapperID * 12; + + // ---| update hardware information in the reader |--- + auto& reader = processAttributes->rawReader.getReaders()[0]; + reader->forceCRU(cruID); + reader->setLink(globalLinkID); + + LOGP(debug, "Specifier: {}/{}/{}", dh->dataOrigin.as(), dh->dataDescription.as(), dh->subSpecification); + LOGP(debug, "Payload size: {}", dh->payloadSize); + LOGP(debug, "CRU: {}; linkID: {}; dataWrapperID: {}; globalLinkID: {}", cruID, linkID, dataWrapperID, globalLinkID); + + printHeader(); + + // TODO: exception handling needed? + try { + o2::framework::RawParser parser(input.payload, dh->payloadSize); + + // TODO: it would be better to have external event handling and then moving the event processing functionality to CalibRawBase and RawReader to not repeat it in other places + rawreader::ADCRawData rawData; + rawreader::GBTFrame gFrame; + + auto& calibPedestal = processAttributes->calibPedestal; + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto* rdhPtr = it.get_if(); + if (!rdhPtr) { + break; + } + const auto& rdh = *rdhPtr; + printRDH(rdh); + // ===| event handling |=== + // + // really ugly, better treatment required extension in DPL + // events are are detected by close by orbit numbers + // might be possible to change this using the TFID information + // + const auto hbOrbit = rdh.heartbeatOrbit; + const auto lastOrbit = processAttributes->lastOrbit; + + if ((lastOrbit > 0) && (hbOrbit > (lastOrbit + 3))) { + calibPedestal.incrementNEvents(); + LOGP(info, "Number of processed events: {} ({})", calibPedestal.getNumberOfProcessedEvents(), processAttributes->maxEvents); + if (calibPedestal.getNumberOfProcessedEvents() >= processAttributes->maxEvents) { + LOGP(info, "Maximm number of events reached ({}), no more processing will be done", processAttributes->maxEvents); + processAttributes->quit = true; break; } - //const auto& rdh = *((RDH*)pos); - const auto& rdh = *rdhPtr; - //const auto payloadSize = rdh.memorySize - rdh.headerSize; - //const auto dataWrapperID = rdh.endPointID; - //const auto linkID = rdh.linkID; - //const auto globalLinkID = linkID + dataWrapperID * 12; - printRDH(rdh); - - //pos += rdh.offsetToNext; } - } catch (const std::runtime_error& e) { - LOG(ERROR) << "can not create raw parser form input data"; - o2::header::hexDump("payload", input.payload, dh->payloadSize, 64); - LOG(ERROR) << e.what(); + + processAttributes->lastOrbit = hbOrbit; + const auto size = it.size(); + auto data = it.data(); + //LOGP(info, "Data size: {}", size); + + int iFrame = 0; + for (int i = 0; i < size; i += 16) { + gFrame.setFrameNumber(iFrame); + gFrame.setPacketNumber(iFrame / 508); + gFrame.readFromMemory(gsl::span(data + i, 16)); + + // extract the half words from the 4 32-bit words + gFrame.getFrameHalfWords(); + + // debug output + //if (CHECK_BIT(mDebugLevel, DebugLevel::GBTFrames)) { + //std::cout << gFrame; + //} + + gFrame.getAdcValues(rawData); + gFrame.updateSyncCheck(false); + + ++iFrame; + } } + + reader->runADCDataCallback(rawData); + } catch (const std::runtime_error& e) { + LOGP(error, "can not create raw parser form input data"); + o2::header::hexDump("payload", input.payload, dh->payloadSize, 64); + LOG(ERROR) << e.what(); } - }); - }}}); + } + + // TODO: For the moment simply dump calibration output to file, to check if everything is working as expected + if (processAttributes->quit && !processAttributes->dumped) { + LOGP(info, "Dumping output"); + processAttributes->calibPedestal.analyse(); + processAttributes->calibPedestal.dumpToFile("pedestals.root"); + processAttributes->dumped = true; + //pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::All); + } + }; + + return processingFct; + }; + + WorkflowSpec workflow; + workflow.emplace_back(DataProcessorSpec{ + "calib-pedestal", + select(config.options().get("input-spec").c_str()), + Outputs{}, + AlgorithmSpec{initFunction}, + Options{{"max-events", VariantType::Int, 100, {"maximum number of events to process"}}}}); + return workflow; } diff --git a/Detectors/TPC/calibration/src/CalibPedestal.cxx b/Detectors/TPC/calibration/src/CalibPedestal.cxx index 59716f11717e0..d30411ed23a32 100644 --- a/Detectors/TPC/calibration/src/CalibPedestal.cxx +++ b/Detectors/TPC/calibration/src/CalibPedestal.cxx @@ -21,6 +21,7 @@ #include "TPCCalibration/CalibPedestal.h" using namespace o2::tpc; +using o2::math_utils::math_base::fit; using o2::math_utils::math_base::fitGaus; using o2::math_utils::math_base::getStatisticsData; using o2::math_utils::math_base::StatisticsData; @@ -29,10 +30,10 @@ CalibPedestal::CalibPedestal(PadSubset padSubset) : CalibRawBase(padSubset), mFirstTimeBin(0), mLastTimeBin(500), - mADCMin(0), - mADCMax(120), + mADCMin(20), + mADCMax(140), mNumberOfADCs(mADCMax - mADCMin + 1), - mStatisticsType(StatisticsType::GausFit), + mStatisticsType(StatisticsType::GausFitFast), mPedestal("Pedestals", padSubset), mNoise("Noise", padSubset), mADCdata() @@ -114,9 +115,16 @@ void CalibPedestal::analyse() float pedestal{}; float noise{}; + TF1 fg("fg", "gaus"); + fg.SetRange(mADCMin - 0.5f, mADCMax + 1.5f); + for (Int_t ichannel = 0; ichannel < numberOfPads; ++ichannel) { size_t offset = ichannel * mNumberOfADCs; if (mStatisticsType == StatisticsType::GausFit) { + fit(mNumberOfADCs, array + offset, float(mADCMin) - 0.5f, float(mADCMax + 1) - 0.5f, fg); // -0.5 since ADC values are discrete + pedestal = fg.GetParameter(1); + noise = fg.GetParameter(2); + } else if (mStatisticsType == StatisticsType::GausFitFast) { fitGaus(mNumberOfADCs, array + offset, float(mADCMin) - 0.5f, float(mADCMax + 1) - 0.5f, fitValues); // -0.5 since ADC values are discrete pedestal = fitValues[1]; noise = fitValues[2]; @@ -125,6 +133,7 @@ void CalibPedestal::analyse() pedestal = data.mCOG; noise = data.mStdDev; } + noise = std::abs(noise); // noise can be negative in gaus fit calROCPedestal.setValue(ichannel, pedestal); calROCNoise.setValue(ichannel, noise); @@ -149,12 +158,16 @@ void CalibPedestal::resetData() } //______________________________________________________________________________ -void CalibPedestal::dumpToFile(const std::string filename) +void CalibPedestal::dumpToFile(const std::string filename, uint32_t type /* = 0*/) { auto f = std::unique_ptr(TFile::Open(filename.c_str(), "recreate")); - f->WriteObject(&mPedestal, "Pedestals"); - f->WriteObject(&mNoise, "Noise"); - f->Close(); + if (type == 0) { + f->WriteObject(&mPedestal, "Pedestals"); + f->WriteObject(&mNoise, "Noise"); + f->Close(); + } else if (type == 1) { + f->WriteObject(this, "CalibPedestal"); + } } //______________________________________________________________________________ diff --git a/Detectors/TPC/calibration/src/CalibPulser.cxx b/Detectors/TPC/calibration/src/CalibPulser.cxx index 1cedad2d47b91..cb53738591a97 100644 --- a/Detectors/TPC/calibration/src/CalibPulser.cxx +++ b/Detectors/TPC/calibration/src/CalibPulser.cxx @@ -249,29 +249,33 @@ void CalibPulser::analyse() } //______________________________________________________________________________ -void CalibPulser::dumpToFile(const std::string filename) +void CalibPulser::dumpToFile(const std::string filename, uint32_t type /* = 0*/) { auto f = std::unique_ptr(TFile::Open(filename.c_str(), "recreate")); - f->WriteObject(&mT0, "T0"); - f->WriteObject(&mWidth, "Width"); - f->WriteObject(&mQtot, "Qtot"); - - if (mDebugLevel) { - printf("dump debug info\n"); - // temporary arrays for writing the objects - TObjArray vT0; - for (auto& val : mT0Histograms) - vT0.Add(val.get()); - TObjArray vWidth; - for (auto& val : mWidthHistograms) - vWidth.Add(val.get()); - TObjArray vQtot; - for (auto& val : mQtotHistograms) - vQtot.Add(val.get()); - - vT0.Write("T0Histograms", TObject::kSingleKey); - vWidth.Write("WidthHistograms", TObject::kSingleKey); - vQtot.Write("QtotHistograms", TObject::kSingleKey); + if (type == 0) { + f->WriteObject(&mT0, "T0"); + f->WriteObject(&mWidth, "Width"); + f->WriteObject(&mQtot, "Qtot"); + + if (mDebugLevel) { + printf("dump debug info\n"); + // temporary arrays for writing the objects + TObjArray vT0; + for (auto& val : mT0Histograms) + vT0.Add(val.get()); + TObjArray vWidth; + for (auto& val : mWidthHistograms) + vWidth.Add(val.get()); + TObjArray vQtot; + for (auto& val : mQtotHistograms) + vQtot.Add(val.get()); + + vT0.Write("T0Histograms", TObject::kSingleKey); + vWidth.Write("WidthHistograms", TObject::kSingleKey); + vQtot.Write("QtotHistograms", TObject::kSingleKey); + } + } else if (type == 1) { + f->WriteObject(this, "CalibPulser"); } f->Close(); diff --git a/Detectors/TPC/calibration/src/DigitDump.cxx b/Detectors/TPC/calibration/src/DigitDump.cxx index 66909e28ae799..85dd0a1f239c5 100644 --- a/Detectors/TPC/calibration/src/DigitDump.cxx +++ b/Detectors/TPC/calibration/src/DigitDump.cxx @@ -19,6 +19,7 @@ #include "TPCBase/Mapper.h" #include "TPCBase/ROC.h" #include "TPCCalibration/DigitDump.h" +#include "TPCCalibration/DigitDumpParam.h" using namespace o2::tpc; @@ -30,6 +31,19 @@ DigitDump::~DigitDump() } } +//______________________________________________________________________________ +void DigitDump::init() +{ + const auto& param = DigitDumpParam::Instance(); + + mFirstTimeBin = param.FirstTimeBin; + mLastTimeBin = param.LastTimeBin; + mADCMin = param.ADCMin; + mADCMax = param.ADCMax; + mNoiseThreshold = param.NoiseThreshold; + mPedestalAndNoiseFile = param.PedestalAndNoiseFile; +} + //______________________________________________________________________________ Int_t DigitDump::updateCRU(const CRU& cru, const Int_t row, const Int_t pad, const Int_t timeBin, const Float_t signal) @@ -38,8 +52,8 @@ Int_t DigitDump::updateCRU(const CRU& cru, const Int_t row, const Int_t pad, return 0; } - if (!mFile) { - init(); + if (!mInitialized) { + initInputOutput(); } Mapper& mapper = Mapper::instance(); @@ -81,7 +95,7 @@ Int_t DigitDump::updateCRU(const CRU& cru, const Int_t row, const Int_t pad, } //______________________________________________________________________________ -void DigitDump::endEvent() +void DigitDump::sortDigits() { // sort digits for (auto& digits : mDigits) { @@ -93,12 +107,17 @@ void DigitDump::endEvent() return false; }); } +} + +//______________________________________________________________________________ +void DigitDump::endEvent() +{ + // sort digits + sortDigits(); mTree->Fill(); - for (auto& digits : mDigits) { - digits.clear(); - } + clearDigits(); } //______________________________________________________________________________ @@ -136,8 +155,11 @@ void DigitDump::setupOutputTree() } //______________________________________________________________________________ -void DigitDump::init() +void DigitDump::initInputOutput() { loadNoiseAndPedestal(); - setupOutputTree(); + if (!mInMemoryOnly) { + setupOutputTree(); + } + mInitialized = true; } diff --git a/Detectors/TPC/calibration/src/DigitDumpParam.cxx b/Detectors/TPC/calibration/src/DigitDumpParam.cxx new file mode 100644 index 0000000000000..b9cc21ab4eb25 --- /dev/null +++ b/Detectors/TPC/calibration/src/DigitDumpParam.cxx @@ -0,0 +1,17 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file DigitDumpParam.cxx +/// \brief Implementation of the parameter class for the hardware clusterer +/// \author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de + +#include "TPCCalibration/DigitDumpParam.h" + +O2ParamImpl(o2::tpc::DigitDumpParam); diff --git a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h index 4ba4d6444caae..df237af65bfd0 100644 --- a/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h +++ b/Detectors/TPC/calibration/src/TPCCalibrationLinkDef.h @@ -17,10 +17,14 @@ #pragma link C++ class o2::tpc::CalibRawBase; #pragma link C++ class o2::tpc::CalibPedestal; #pragma link C++ class o2::tpc::CalibPedestalParam +; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::CalibPedestalParam> + ; #pragma link C++ class o2::tpc::CalibPulser; #pragma link C++ class o2::tpc::CalibPulserParam +; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::CalibPulserParam> + ; #pragma link C++ class o2::tpc::CalibTreeDump; #pragma link C++ class o2::tpc::DigitDump; +#pragma link C++ class o2::tpc::DigitDumpParam; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::DigitDumpParam> + ; #pragma link C++ class o2::tpc::CalibPadGainTracks; #pragma link C++ class o2::tpc::FastHisto +; #pragma link C++ class o2::tpc::FastHisto +; diff --git a/Detectors/TPC/reconstruction/include/TPCReconstruction/RawReaderCRU.h b/Detectors/TPC/reconstruction/include/TPCReconstruction/RawReaderCRU.h index 86c271e0906f3..fe606c3b44834 100644 --- a/Detectors/TPC/reconstruction/include/TPCReconstruction/RawReaderCRU.h +++ b/Detectors/TPC/reconstruction/include/TPCReconstruction/RawReaderCRU.h @@ -600,6 +600,9 @@ class RawReaderCRU /// copy single events to another file void copyEvents(const std::vector& eventNumbers, std::string outputDirectory, std::ios_base::openmode mode = std::ios_base::openmode(0)); + /// run a data filling callback function + void runADCDataCallback(const ADCRawData& rawData); + //=========================================================================== //===| Nested helper classes |=============================================== // @@ -707,9 +710,6 @@ class RawReaderCRU /// fill adc data to output map void fillADCdataMap(const ADCRawData& rawData); - /// run a data filling callback function - void runADCDataCallback(const ADCRawData& rawData); - ClassDefNV(RawReaderCRU, 0); // raw reader class }; // class RawReaderCRU diff --git a/Detectors/TPC/reconstruction/macro/runRecoFromDigits.sh b/Detectors/TPC/reconstruction/macro/runRecoFromDigits.sh index 59d3c7aa09e4b..4ad3028f7ae17 100755 --- a/Detectors/TPC/reconstruction/macro/runRecoFromDigits.sh +++ b/Detectors/TPC/reconstruction/macro/runRecoFromDigits.sh @@ -48,6 +48,6 @@ done # ===| check for required arguments |=========================================== # ===| command building and execution |========================================= -cmd="o2-tpc-reco-workflow --infile $inputFile --disable-mc --configKeyValues 'TPCHwClusterer.peakChargeThreshold=$qMaxThreshold;TPCHwClusterer.isContinuousReadout=0' --output-type $outputType" +cmd="o2-tpc-reco-workflow -b --infile $inputFile --disable-mc --configKeyValues 'TPCHwClusterer.peakChargeThreshold=$qMaxThreshold;TPCHwClusterer.isContinuousReadout=0' --output-type $outputType" echo $cmd eval $cmd diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index dbecde25fd0fe..c9eb3ba7a7bc5 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -16,15 +16,23 @@ o2_add_library(TPCWorkflow src/ClusterDecoderRawSpec.cxx src/CATrackerSpec.cxx src/TrackReaderSpec.cxx + src/RawToDigitsSpec.cxx + src/LinkZSToDigitsSpec.cxx TARGETVARNAME targetName PUBLIC_LINK_LIBRARIES O2::Framework O2::DataFormatsTPC - O2::DPLUtils O2::TPCReconstruction) + O2::DPLUtils O2::TPCReconstruction + O2::TPCCalibration O2::TPCSimulation) o2_add_executable(reco-workflow COMPONENT_NAME tpc SOURCES src/tpc-reco-workflow.cxx PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) +o2_add_executable(raw-to-digits-workflow + COMPONENT_NAME tpc + SOURCES src/tpc-raw-to-digits-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + o2_add_test(workflow COMPONENT_NAME tpc LABELS tpc workflow diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/LinkZSToDigitsSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/LinkZSToDigitsSpec.h new file mode 100644 index 0000000000000..b3439d3f21861 --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/LinkZSToDigitsSpec.h @@ -0,0 +1,33 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file LinkZSToDigitsSpec.h +/// @author Jens Wiechula +/// @since 2020-02-20 +/// @brief Processor spec for running link based zero suppressed data to digit converter + +#ifndef TPC_LinkZSToDigitsSpec_H_ +#define TPC_LinkZSToDigitsSpec_H_ + +#include "Framework/DataProcessorSpec.h" +#include + +namespace o2 +{ +namespace tpc +{ + +/// Processor to convert link based zero suppressed data to simulation digits +o2::framework::DataProcessorSpec getLinkZSToDigitsSpec(int channel, const std::string_view inputDef, std::vector const& tpcSectors); + +} // end namespace tpc +} // end namespace o2 + +#endif // TPC_LinkZSToDigitsSpec_H_ diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/RawToDigitsSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/RawToDigitsSpec.h new file mode 100644 index 0000000000000..c0e1703ee632f --- /dev/null +++ b/Detectors/TPC/workflow/include/TPCWorkflow/RawToDigitsSpec.h @@ -0,0 +1,34 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// @file RawToDigitsSpec.h +/// @author Jens Wiechula +/// @since 2020-01-17 +/// @brief Processor spec for running TPC GBT raw frame to digit converter + +#ifndef TPC_RAWTODIGITSSPEC_H_ +#define TPC_RAWTODIGITSSPEC_H_ + +#include "Framework/DataProcessorSpec.h" +#include + +namespace o2 +{ +namespace tpc +{ + +/// create a processor spec +/// read simulated TPC clusters from file and publish +o2::framework::DataProcessorSpec getRawToDigitsSpec(int channel, const std::string_view inputDef, std::vector const& tpcSectors); + +} // end namespace tpc +} // end namespace o2 + +#endif // TPC_RAWTODIGITSSPEC_H_ diff --git a/Detectors/TPC/workflow/src/LinkZSToDigitsSpec.cxx b/Detectors/TPC/workflow/src/LinkZSToDigitsSpec.cxx new file mode 100644 index 0000000000000..db7e0ff87eda7 --- /dev/null +++ b/Detectors/TPC/workflow/src/LinkZSToDigitsSpec.cxx @@ -0,0 +1,305 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "Framework/WorkflowSpec.h" +#include "Framework/ControlService.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/DataRefUtils.h" +#include "Framework/Lifetime.h" +#include "Framework/Logger.h" +#include "DPLUtils/RawParser.h" +#include "Headers/DataHeader.h" +#include "DataFormatsTPC/TPCSectorHeader.h" +#include "DataFormatsTPC/ZeroSuppressionLinkBased.h" +#include "TPCBase/Digit.h" +#include "TPCBase/CRU.h" +#include "TPCBase/PadSecPos.h" +#include "TPCBase/Mapper.h" +#include "TPCReconstruction/RawReaderCRU.h" +#include "TPCWorkflow/LinkZSToDigitsSpec.h" +#include +#include + +using namespace o2::framework; +using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; + +namespace o2 +{ +namespace tpc +{ + +o2::framework::DataProcessorSpec getLinkZSToDigitsSpec(int channel, const std::string_view inputDef, std::vector const& tpcSectors) +{ + + static constexpr int MaxNumberOfBunches = 3564; + + struct ProcessAttributes { + uint32_t lastOrbit{0}; ///< last processed orbit number + uint32_t firstOrbit{0}; ///< first orbit number, required for time bin calculation + uint32_t firstBC{0}; ///< first bunch crossing number, required for time bin calculation + uint32_t maxEvents{100}; ///< maximum number of events to process + uint32_t processedEvents{0}; ///< number of processed events + uint64_t activeSectors{0}; ///< bit mask of active sectors + bool isContinuous{false}; ///< if data is triggered or continuous + bool quit{false}; ///< if workflow is ready to quit + std::vector tpcSectors{}; ///< tpc sector configuration + std::array, Sector::MAXSECTOR> digitsAll{}; ///< digit vector to be stored inside the file + + /// cleanup of digits + void clearDigits() + { + for (auto& digits : digitsAll) { + digits.clear(); + } + } + + /// Digit sorting according to expected output from simulation + void sortDigits() + { + // sort digits + for (auto& digits : digitsAll) { + std::sort(digits.begin(), digits.end(), [](const auto& a, const auto& b) { + if (a.getTimeStamp() < b.getTimeStamp()) + return true; + if ((a.getTimeStamp() == b.getTimeStamp()) && (a.getRow() < b.getRow())) + return true; + return false; + }); + } + } + }; + + // ===| stateful initialization |============================================= + // + auto initFunction = [channel, tpcSectors](InitContext& ic) { + auto processAttributes = std::make_shared(); + // ===| create and set up processing attributes |=== + { + processAttributes->maxEvents = static_cast(ic.options().get("max-events")); + processAttributes->tpcSectors = tpcSectors; + } + + // ===| data processor |==================================================== + // + auto processingFct = [processAttributes, channel](ProcessingContext& pc) { + if (processAttributes->quit) { + return; + } + + // ===| digit snapshot |=== + // + // lambda that snapshots digits to be sent out; + // prepares and attaches header with sector information + // + auto snapshotDigits = [&pc, processAttributes, channel](std::vector const& digits, int sector) { + o2::tpc::TPCSectorHeader header{sector}; + header.activeSectors = processAttributes->activeSectors; + // digit for now are transported per sector, not per lane + // pc.outputs().snapshot(Output{"TPC", "DIGITS", static_cast(channel), Lifetime::Timeframe, header}, + pc.outputs().snapshot(Output{"TPC", "DIGITS", static_cast(sector), Lifetime::Timeframe, header}, + const_cast&>(digits)); + }; + + auto& mapper = Mapper::instance(); + + // loop over all inputs + for (auto& input : pc.inputs()) { + const auto* dh = DataRefUtils::getHeader(input); + + // select only RAW data + if (dh->dataDescription != o2::header::gDataDescriptionRawData) { + continue; + } + + // ===| extract electronics mapping information |=== + const auto subSpecification = dh->subSpecification; + const auto cruID = subSpecification >> 16; + const auto linkID = ((subSpecification + (subSpecification >> 8)) & 0xFF) - 1; + const auto dataWrapperID = ((subSpecification >> 8) & 0xFF) > 0; + const auto globalLinkID = linkID + dataWrapperID * 12; + const auto sector = cruID / 10; + const CRU cru(cruID); + const int fecLinkOffsetCRU = (mapper.getPartitionInfo(cru.partition()).getNumberOfFECs() + 1) / 2; + const int fecInPartition = (globalLinkID % 12) + (globalLinkID > 11) * fecLinkOffsetCRU; + const int regionIter = cruID % 2; + + const int sampaMapping[10] = {0, 0, 1, 1, 2, 3, 3, 4, 4, 2}; + const int channelOffset[10] = {0, 16, 0, 16, 0, 0, 16, 0, 16, 16}; + + processAttributes->activeSectors |= (0x1 << sector); + + LOGP(debug, "Specifier: {}/{}/{}", dh->dataOrigin.as(), dh->dataDescription.as(), dh->subSpecification); + LOGP(debug, "Payload size: {}", dh->payloadSize); + LOGP(debug, "CRU: {}; linkID: {}; dataWrapperID: {}; globalLinkID: {}", cruID, linkID, dataWrapperID, globalLinkID); + + try { + o2::framework::RawParser parser(input.payload, dh->payloadSize); + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto* rdhPtr = it.get_if(); + if (!rdhPtr) { + break; + } + + const auto& rdh = *rdhPtr; + + // ===| only accept physics triggers |=== + if (rdh.triggerType != 0x10) { + continue; + } + + // ===| event handling |=== + // + // really ugly, better treatment required extension in DPL + // events are are detected by close by orbit numbers + // + const auto hbOrbit = rdh.heartbeatOrbit; + const auto lastOrbit = processAttributes->lastOrbit; + + if (!processAttributes->firstOrbit) { + processAttributes->firstOrbit = hbOrbit; + if (!processAttributes->isContinuous) { + processAttributes->firstBC = rdh.heartbeatBC; + } + } + + const auto globalBCoffset = ((hbOrbit - processAttributes->firstOrbit) * MaxNumberOfBunches - processAttributes->firstBC); // To be calculated + + if ((lastOrbit > 0) && (hbOrbit > (lastOrbit + 3))) { + ++processAttributes->processedEvents; + LOG(INFO) << fmt::format("Number of processed events: {} ({})", processAttributes->processedEvents, processAttributes->maxEvents); + processAttributes->sortDigits(); + + // publish digits of all configured sectors + for (auto isector : processAttributes->tpcSectors) { + snapshotDigits(processAttributes->digitsAll[isector], isector); + } + processAttributes->clearDigits(); + + processAttributes->activeSectors = 0; + if (processAttributes->processedEvents >= processAttributes->maxEvents) { + LOGP(info, "Maximum number of events reached ({}), no more processing will be done", processAttributes->maxEvents); + processAttributes->quit = true; + pc.services().get().endOfStream(); + //pc.services().get().readyToQuit(QuitRequest::All); + pc.services().get().readyToQuit(QuitRequest::Me); + break; + } + } + + processAttributes->lastOrbit = hbOrbit; + const auto size = it.size(); + auto data = it.data(); + LOGP(debug, "Raw data block payload size: {}", size); + + // cast raw data pointer to link based zero suppression definition + zerosupp_link_based::ContainerZS* zsdata = (zerosupp_link_based::ContainerZS*)data; + const zerosupp_link_based::ContainerZS* const zsdataEnd = (zerosupp_link_based::ContainerZS*)(data + size); + + while (zsdata < zsdataEnd) { + const auto channelBits = zsdata->cont.header.getChannelBits(); + const uint32_t numberOfWords = zsdata->cont.header.numWordsPayload; + assert((channelBits.count() - 1) / 10 == numberOfWords - 1); + + std::size_t processedChannels = 0; + for (std::size_t ichannel = 0; ichannel < channelBits.size(); ++ichannel) { + if (!channelBits[ichannel]) { + continue; + } + + // adc value + const auto adcValue = zsdata->getADCValueFloat(processedChannels); + + // pad mapping + // TODO: verify the assumptions of the channel mapping! + // assumes the following sorting (s_chn is the channel on the sampa), + // in this case for even regiona (lower half fec) + // chn# SAMPA s_chn + // 0 0 0 + // 1 0 1 + // 2 0 16 + // 3 0 17 + // 4 1 0 + // 5 1 1 + // 6 1 16 + // 7 1 17 + // 8 2 0 + // 9 2 1 + // + // 10 0 2 + // 11 0 3 + // 12 0 18 + // 13 0 19 + // 14 1 2 + // 15 1 3 + // 16 1 18 + // 17 1 19 + // 18 2 2 + // 19 2 3 + // + // 20 0 4 + // 21 0 5 + // 22 0 20 + // 23 0 21 + // ... + // For the uneven regions (upper half fec), the sampa ordering + // is 3, 3, 3, 3, 4, 4, 4, 4, 2, 2 + const int istreamm = ((ichannel % 10) / 2); + const int partitionStream = istreamm + regionIter * 5; + const int sampaOnFEC = sampaMapping[partitionStream]; + const int channel = (ichannel % 2) + 2 * (ichannel / 10); + const int channelOnSAMPA = channel + channelOffset[partitionStream]; + + const auto padSecPos = mapper.padSecPos(cru, fecInPartition, sampaOnFEC, channelOnSAMPA); + const auto& padPos = padSecPos.getPadPos(); + int timebin = (globalBCoffset + zsdata->cont.header.bunchCrossing) / 8; // To be calculated + + // add digit + processAttributes->digitsAll[sector].emplace_back(cruID, adcValue, padPos.getRow(), padPos.getPad(), timebin); + ++processedChannels; + } + + // go to next time bin + zsdata = zsdata->next(); + } + } + + } catch (const std::runtime_error& e) { + LOG(ERROR) << "can not create raw parser form input data"; + o2::header::hexDump("payload", input.payload, dh->payloadSize, 64); + LOG(ERROR) << e.what(); + } + } + }; + + return processingFct; + }; + + std::stringstream id; + id << "TPCDigitizer" << channel; + + std::vector outputs; // define channel by triple of (origin, type id of data to be sent on this channel, subspecification) + for (auto isector : tpcSectors) { + outputs.emplace_back("TPC", "DIGITS", static_cast(isector), Lifetime::Timeframe); + } + + return DataProcessorSpec{ + id.str().c_str(), + select(inputDef.data()), + outputs, + AlgorithmSpec{initFunction}, + Options{ + {"max-events", VariantType::Int, 100, {"maximum number of events to process"}}, + {"pedestal-file", VariantType::String, "", {"file with pedestals and noise for zero suppression"}}}}; +} +} // namespace tpc +} // namespace o2 diff --git a/Detectors/TPC/workflow/src/RawToDigitsSpec.cxx b/Detectors/TPC/workflow/src/RawToDigitsSpec.cxx new file mode 100644 index 0000000000000..ea902d1e34a45 --- /dev/null +++ b/Detectors/TPC/workflow/src/RawToDigitsSpec.cxx @@ -0,0 +1,224 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "Framework/WorkflowSpec.h" +#include "Framework/ControlService.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/DataRefUtils.h" +#include "Framework/Lifetime.h" +#include "DPLUtils/RawParser.h" +#include "Headers/DataHeader.h" +#include "DataFormatsTPC/TPCSectorHeader.h" +#include "TPCBase/Digit.h" +#include "TPCCalibration/DigitDump.h" +#include "TPCReconstruction/RawReaderCRU.h" +#include "TPCWorkflow/RawToDigitsSpec.h" +#include "Framework/Logger.h" +#include +#include + +using namespace o2::framework; +using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; + +namespace o2 +{ +namespace tpc +{ + +o2::framework::DataProcessorSpec getRawToDigitsSpec(int channel, const std::string_view inputDef, std::vector const& tpcSectors) +{ + + struct ProcessAttributes { + DigitDump digitDump; ///< digits creation class + rawreader::RawReaderCRUManager rawReader; ///< GBT frame decoder + uint32_t lastOrbit{0}; ///< last processed orbit number + uint32_t maxEvents{100}; ///< maximum number of events to process + uint64_t activeSectors{0}; ///< bit mask of active sectors + bool quit{false}; ///< if workflow is ready to quit + std::vector tpcSectors{}; ///< tpc sector configuration + }; + + // ===| stateful initialization |============================================= + // + auto initFunction = [channel, tpcSectors](InitContext& ic) { + // ===| create and set up processing attributes |=== + auto processAttributes = std::make_shared(); + // set up calibration + { + auto& digitDump = processAttributes->digitDump; + digitDump.init(); + digitDump.setInMemoryOnly(); + const auto pedestalFile = ic.options().get("pedestal-file"); + LOGP(info, "Setting pedestal file: {}", pedestalFile); + digitDump.setPedestalAndNoiseFile(pedestalFile); + + processAttributes->rawReader.createReader(""); + processAttributes->rawReader.setADCDataCallback([&digitDump](const PadROCPos& padROCPos, const CRU& cru, const gsl::span data) -> Int_t { + Int_t timeBins = digitDump.update(padROCPos, cru, data); + digitDump.setNumberOfProcessedTimeBins(std::max(digitDump.getNumberOfProcessedTimeBins(), size_t(timeBins))); + return timeBins; + }); + processAttributes->maxEvents = static_cast(ic.options().get("max-events")); + processAttributes->tpcSectors = tpcSectors; + } + + // ===| data processor |==================================================== + // + auto processingFct = [processAttributes, channel](ProcessingContext& pc) { + if (processAttributes->quit) { + return; + } + + // ===| digit snapshot |=== + // + // lambda that snapshots digits to be sent out; + // prepares and attaches header with sector information + // + auto snapshotDigits = [&pc, processAttributes, channel](std::vector const& digits, int sector) { + o2::tpc::TPCSectorHeader header{sector}; + header.activeSectors = processAttributes->activeSectors; + // digit for now are transported per sector, not per lane + // pc.outputs().snapshot(Output{"TPC", "DIGITS", static_cast(channel), Lifetime::Timeframe, header}, + pc.outputs().snapshot(Output{"TPC", "DIGITS", static_cast(sector), Lifetime::Timeframe, header}, + const_cast&>(digits)); + }; + + // loop over all inputs + for (auto& input : pc.inputs()) { + const auto* dh = DataRefUtils::getHeader(input); + + // select only RAW data + if (dh->dataDescription != o2::header::gDataDescriptionRawData) { + continue; + } + + // ===| extract electronics mapping information |=== + const auto subSpecification = dh->subSpecification; + const auto cruID = subSpecification >> 16; + const auto linkID = ((subSpecification + (subSpecification >> 8)) & 0xFF) - 1; + const auto dataWrapperID = ((subSpecification >> 8) & 0xFF) > 0; + const auto globalLinkID = linkID + dataWrapperID * 12; + const auto sector = cruID / 10; + + // update active sectors + processAttributes->activeSectors |= (0x1 << sector); + + // set up mapping information for raw reader + auto& reader = processAttributes->rawReader.getReaders()[0]; + reader->forceCRU(cruID); + reader->setLink(globalLinkID); + + LOGP(debug, "Specifier: {}/{}/{}", dh->dataOrigin.as(), dh->dataDescription.as(), dh->subSpecification); + LOGP(debug, "Payload size: {}", dh->payloadSize); + LOGP(debug, "CRU: {}; linkID: {}; dataWrapperID: {}; globalLinkID: {}", cruID, linkID, dataWrapperID, globalLinkID); + + try { + o2::framework::RawParser parser(input.payload, dh->payloadSize); + + rawreader::ADCRawData rawData; + rawreader::GBTFrame gFrame; + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto* rdhPtr = it.get_if(); + if (!rdhPtr) { + break; + } + const auto& rdh = *rdhPtr; + //printRDH(rdh); + + // ===| event handling |=== + // + // really ugly, better treatment requires extension in DPL + // events are are detected by close by orbit numbers + // + const auto hbOrbit = rdh.heartbeatOrbit; + const auto lastOrbit = processAttributes->lastOrbit; + + if ((lastOrbit > 0) && (hbOrbit > (lastOrbit + 3))) { + auto& digitDump = processAttributes->digitDump; + digitDump.incrementNEvents(); + LOGP(info, "Number of processed events: {} ({})", digitDump.getNumberOfProcessedEvents(), processAttributes->maxEvents); + digitDump.sortDigits(); + + // publish digits of all configured sectors + for (auto isector : processAttributes->tpcSectors) { + snapshotDigits(digitDump.getDigits(isector), isector); + } + digitDump.clearDigits(); + + processAttributes->activeSectors = 0; + if (digitDump.getNumberOfProcessedEvents() >= processAttributes->maxEvents) { + LOGP(info, "Maximum number of events reached ({}), no more processing will be done", processAttributes->maxEvents); + processAttributes->quit = true; + pc.services().get().endOfStream(); + //pc.services().get().readyToQuit(QuitRequest::All); + pc.services().get().readyToQuit(QuitRequest::Me); + break; + } + } + + processAttributes->lastOrbit = hbOrbit; + const auto size = it.size(); + auto data = it.data(); + LOGP(debug, "Raw data block payload size: {}", size); + + int iFrame = 0; + for (int i = 0; i < size; i += 16) { + gFrame.setFrameNumber(iFrame); + gFrame.setPacketNumber(iFrame / 508); + gFrame.readFromMemory(gsl::span(data + i, 16)); + + // extract the half words from the 4 32-bit words + gFrame.getFrameHalfWords(); + + // debug output + //if (CHECK_BIT(mDebugLevel, DebugLevel::GBTFrames)) { + //std::cout << gFrame; + //} + + gFrame.getAdcValues(rawData); + gFrame.updateSyncCheck(false); + + ++iFrame; + } + } + + reader->runADCDataCallback(rawData); + } catch (const std::runtime_error& e) { + LOG(ERROR) << "can not create raw parser form input data"; + o2::header::hexDump("payload", input.payload, dh->payloadSize, 64); + LOG(ERROR) << e.what(); + } + } + }; + + return processingFct; + }; + + std::stringstream id; + id << "TPCDigitizer" << channel; + + std::vector outputs; // define channel by triple of (origin, type id of data to be sent on this channel, subspecification) + for (auto isector : tpcSectors) { + outputs.emplace_back("TPC", "DIGITS", static_cast(isector), Lifetime::Timeframe); + } + + return DataProcessorSpec{ + id.str().c_str(), + select(inputDef.data()), + outputs, + AlgorithmSpec{initFunction}, + Options{ + {"max-events", VariantType::Int, 100, {"maximum number of events to process"}}, + {"pedestal-file", VariantType::String, "", {"file with pedestals and noise for zero suppression"}}}}; +} +} // namespace tpc +} // namespace o2 diff --git a/Detectors/TPC/workflow/src/tpc-raw-to-digits-workflow.cxx b/Detectors/TPC/workflow/src/tpc-raw-to-digits-workflow.cxx new file mode 100644 index 0000000000000..8c245bec30d0c --- /dev/null +++ b/Detectors/TPC/workflow/src/tpc-raw-to-digits-workflow.cxx @@ -0,0 +1,133 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "Framework/WorkflowSpec.h" +#include "Framework/DataProcessorSpec.h" +#include "Framework/ControlService.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "Framework/Logger.h" +#include "Framework/ConfigParamSpec.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Algorithm/RangeTokenizer.h" +#include "TPCWorkflow/RawToDigitsSpec.h" +#include "TPCWorkflow/LinkZSToDigitsSpec.h" +#include "TPCWorkflow/RecoWorkflow.h" +#include "TPCBase/Sector.h" +#include +#include + +#include // to detect number of hardware threads + +using namespace o2::framework; + +// customize the completion policy +void customize(std::vector& policies) +{ + // we customize the completion policy for the writer since it should stream immediately + using CompletionPolicy = o2::framework::CompletionPolicy; + using CompletionPolicyHelpers = o2::framework::CompletionPolicyHelpers; + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-cluster-decoder.*", CompletionPolicy::CompletionOp::Consume)); + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-clusterer.*", CompletionPolicy::CompletionOp::Consume)); + policies.push_back(CompletionPolicyHelpers::defineByName("tpc-tracker.*", CompletionPolicy::CompletionOp::Consume)); +} + +enum class DecoderType { + GBT, ///< GBT frame raw decoding + LinkZS ///< Link based zero suppression +}; + +const std::unordered_map DecoderTypeMap{ + {"GBT", DecoderType::GBT}, + {"LinkZS", DecoderType::LinkZS}, +}; + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + // for the TPC it is useful to take at most half of the available (logical) cores due to memory requirements + int defaultlanes = std::max(1u, std::thread::hardware_concurrency() / 2); + const std::string laneshelp("Number of tpc processing lanes. A lane is a pipeline of algorithms."); + + const std::string sectorshelp("List of TPC sectors, comma separated ranges, e.g. 0-3,7,9-15"); + const std::string sectorDefault = "0-" + std::to_string(o2::tpc::Sector::MAXSECTOR - 1); + const std::string tpcrthelp("Run TPC reco workflow to specified output type, currently supported: 'digits,clusters,tracks'"); + const std::string decoderHelp("Decoder type to use: 'GBT,LinkZS'"); + + std::vector options{ + {"input-spec", VariantType::String, "A:TPC/RAWDATA", {"selection string input specs"}}, + {"tpc-lanes", VariantType::Int, defaultlanes, {laneshelp}}, + {"tpc-sectors", VariantType::String, sectorDefault.c_str(), {sectorshelp}}, + {"tpc-reco-output", VariantType::String, "", {tpcrthelp}}, + {"decoder-type", VariantType::String, "GBT", {decoderHelp}}, + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings (e.g.: 'TPCCalibPedestal.FirstTimeBin=10;...')"}}, + {"configFile", VariantType::String, "", {"configuration file for configurable parameters"}}}; + + std::swap(workflowOptions, options); +} + +#include "Framework/runDataProcessing.h" + +// extract num TPC lanes, a lane is a streaming line of processors (digitizer-clusterizer-etc) +// by default this will be std::max(the number of physical cores, numberofsectors) +// as a temporary means to fully use a machine and as a way to play with different topologies +int getNumTPCLanes(std::vector const& sectors, ConfigContext const& configcontext) +{ + auto lanes = configcontext.options().get("tpc-lanes"); + if (lanes < 0) { + LOG(FATAL) << "tpc-lanes needs to be positive\n"; + return 0; + } + // crosscheck with sectors + return std::min(lanes, (int)sectors.size()); +} + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + + // set up configuration + o2::conf::ConfigurableParam::updateFromFile(configcontext.options().get("configFile")); + o2::conf::ConfigurableParam::updateFromString(configcontext.options().get("configKeyValues")); + o2::conf::ConfigurableParam::writeINI("o2-tpc-raw-reco-workflow_configuration.ini"); + + WorkflowSpec specs; + + // for the moment no parallel workflow, so just one lane hard coded + auto tpcSectors = o2::RangeTokenizer::tokenize(configcontext.options().get("tpc-sectors")); + auto lanes = 1; //getNumTPCLanes(tpcSectors, config); + + const auto decoderType = DecoderTypeMap.at(configcontext.options().get("decoder-type")); + + int fanoutsize = 0; + for (int l = 0; l < lanes; ++l) { + if (decoderType == DecoderType::GBT) { + specs.emplace_back(o2::tpc::getRawToDigitsSpec(fanoutsize, configcontext.options().get("input-spec"), tpcSectors)); + } else if (decoderType == DecoderType::LinkZS) { + specs.emplace_back(o2::tpc::getLinkZSToDigitsSpec(fanoutsize, configcontext.options().get("input-spec"), tpcSectors)); + } else { + LOG(FATAL) << "bad decoder type"; + } + fanoutsize++; + } + + // ===| attach the TPC reco workflow |======================================== + // default is to dump digits + std::string_view recoOuput = "digits"; + const auto tpcRecoOutputType = configcontext.options().get("tpc-reco-output"); + if (!tpcRecoOutputType.empty()) { + recoOuput = tpcRecoOutputType.c_str(); + } + + auto tpcRecoWorkflow = o2::tpc::reco_workflow::getWorkflow(tpcSectors, tpcSectors, false, lanes, "digitizer", recoOuput.data()); + specs.insert(specs.end(), tpcRecoWorkflow.begin(), tpcRecoWorkflow.end()); + + return specs; +}