From 6b731f27dcf91f3441df1a24d8faffd87dc2d85d Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 13:51:08 +0200 Subject: [PATCH 01/12] feat: add graph helper --- Modules/FIT/Common/include/FITCommon/HelperGraph.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/FIT/Common/include/FITCommon/HelperGraph.h b/Modules/FIT/Common/include/FITCommon/HelperGraph.h index 485f53ae7e..6fe1d1b0ff 100644 --- a/Modules/FIT/Common/include/FITCommon/HelperGraph.h +++ b/Modules/FIT/Common/include/FITCommon/HelperGraph.h @@ -28,7 +28,7 @@ namespace o2::quality_control_modules::fit::helper { /// \brief Factory that forwards ctor arguments to the chosen GraphType. -/// Example: +/// \example /// auto g = makeGraph("name","title",n,x,y,ex,ey); /// \author Jakub Muszyński jakub.milosz.muszynski@cern.ch template From acc8579961d307e3e4706d2797d172946950f572 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 14:51:56 +0200 Subject: [PATCH 02/12] chore: docstring changes --- Modules/FIT/Common/include/FITCommon/HelperGraph.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/FIT/Common/include/FITCommon/HelperGraph.h b/Modules/FIT/Common/include/FITCommon/HelperGraph.h index 6fe1d1b0ff..485f53ae7e 100644 --- a/Modules/FIT/Common/include/FITCommon/HelperGraph.h +++ b/Modules/FIT/Common/include/FITCommon/HelperGraph.h @@ -28,7 +28,7 @@ namespace o2::quality_control_modules::fit::helper { /// \brief Factory that forwards ctor arguments to the chosen GraphType. -/// \example +/// Example: /// auto g = makeGraph("name","title",n,x,y,ex,ey); /// \author Jakub Muszyński jakub.milosz.muszynski@cern.ch template From 4b6d7c9f87ee63f779d67ed4668e5c5718a7a13c Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 15:05:09 +0200 Subject: [PATCH 03/12] feat: move AmpPerChannel to prod hists in aging --- Modules/FIT/FT0/include/FT0/AgingLaserTask.h | 2 +- Modules/FIT/FT0/src/AgingLaserTask.cxx | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserTask.h index 37172b56bb..0bc2417284 100644 --- a/Modules/FIT/FT0/include/FT0/AgingLaserTask.h +++ b/Modules/FIT/FT0/include/FT0/AgingLaserTask.h @@ -109,7 +109,7 @@ class AgingLaserTask final : public TaskInterface // Debug histograms // Ampltiude per channel - std::unique_ptr mDebugHistAmpVsCh; ///< Amplitude per channel (detector + reference channels) + std::unique_ptr mHistAmpVsCh; ///< Amplitude per channel (detector + reference channels) // Ampltidue histograms for reference channel peaks std::map> mMapDebugHistAmp; ///< Amplitude (both ADCs and peaks) diff --git a/Modules/FIT/FT0/src/AgingLaserTask.cxx b/Modules/FIT/FT0/src/AgingLaserTask.cxx index 07823ecb45..0afd2b2c61 100644 --- a/Modules/FIT/FT0/src/AgingLaserTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserTask.cxx @@ -125,6 +125,7 @@ void AgingLaserTask::initialize(o2::framework::InitContext&) // Initialize histograms // Amplitude per channel + mHistAmpVsCh = std::make_unique("AmpPerChannel", "Amplitude vs channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); mHistAmpVsChADC0 = std::make_unique("AmpPerChannelADC0", "Amplitude vs channel (ADC0);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); mHistAmpVsChADC1 = std::make_unique("AmpPerChannelADC1", "Amplitude vs channel (ADC1);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); mHistAmpVsChPeak1ADC0 = std::make_unique("AmpPerChannelPeak1ADC0", "Amplitude vs channel (peak 1, ADC0);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); @@ -154,12 +155,11 @@ void AgingLaserTask::initialize(o2::framework::InitContext&) getObjectsManager()->setDefaultDrawOptions(mHistTimeVsCh.get(), "COLZ"); getObjectsManager()->setDefaultDrawOptions(mHistTimeVsChPeak1.get(), "COLZ"); getObjectsManager()->setDefaultDrawOptions(mHistTimeVsChPeak2.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmpVsCh.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsCh.get(), "COLZ"); // Debug histograms - // Amplitude per channel - mDebugHistAmpVsCh = std::make_unique("AmpPerChannel", "Amplitude vs channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); - // Time per channel mDebugHistTimeVsChADC0 = std::make_unique("TimePerChannelADC0", "Time vs channel (ADC0);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); mDebugHistTimeVsChADC1 = std::make_unique("TimePerChannelADC1", "Time vs channel (ADC1);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); @@ -215,10 +215,6 @@ void AgingLaserTask::initialize(o2::framework::InitContext&) } if (mDebug) { - // Amplitude per channel - getObjectsManager()->startPublishing(mDebugHistAmpVsCh.get()); - getObjectsManager()->setDefaultDrawOptions(mDebugHistAmpVsCh.get(), "COLZ"); - // Time per channel getObjectsManager()->startPublishing(mDebugHistTimeVsChADC0.get()); getObjectsManager()->startPublishing(mDebugHistTimeVsChADC1.get()); @@ -347,7 +343,7 @@ void AgingLaserTask::monitorData(o2::framework::ProcessingContext& ctx) bcHasReferenceChAmpCutADC1 = (isRef && isAmpCutOk && isADC1) || bcHasReferenceChAmpCutADC1; // Fill amplitude and time per channel histograms - mDebugHistAmpVsCh->Fill(chId, chAmp); + mHistAmpVsCh->Fill(chId, chAmp); mHistTimeVsCh->Fill(chId, chTime); isADC0 ? mHistAmpVsChADC0->Fill(chId, chAmp) : mHistAmpVsChADC1->Fill(chId, chAmp); isADC0 ? mDebugHistTimeVsChADC0->Fill(chId, chTime) : mDebugHistTimeVsChADC1->Fill(chId, chTime); @@ -475,7 +471,7 @@ void AgingLaserTask::reset() mHistTimeVsChPeak2->Reset(); // Amplitude per channel - mDebugHistAmpVsCh->Reset(); + mHistAmpVsCh->Reset(); // Amplitude histograms for reference channel peaks for (auto& entry : mMapDebugHistAmp) { From 9eb6de997b480d0bc25d9663e233cc89696a28a1 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 15:05:23 +0200 Subject: [PATCH 04/12] feat: add aging laser post proc task --- Modules/FIT/FT0/CMakeLists.txt | 2 + .../FT0/include/FT0/AgingLaserPostProcTask.h | 59 ++++++ Modules/FIT/FT0/include/FT0/LinkDef.h | 1 + .../FIT/FT0/src/AgingLaserPostProcTask.cxx | 178 ++++++++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h create mode 100644 Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx diff --git a/Modules/FIT/FT0/CMakeLists.txt b/Modules/FIT/FT0/CMakeLists.txt index 8a7ca510dd..f27c414dab 100644 --- a/Modules/FIT/FT0/CMakeLists.txt +++ b/Modules/FIT/FT0/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(O2QcFT0) target_sources(O2QcFT0 PRIVATE src/AgingLaserTask.cxx + src/AgingLaserPostProcTask.cxx src/DigitQcTask.cxx src/GenericCheck.cxx src/CFDEffCheck.cxx @@ -34,6 +35,7 @@ install(TARGETS O2QcFT0 add_root_dictionary(O2QcFT0 HEADERS include/FT0/AgingLaserTask.h + include/FT0/AgingLaserPostProcTask.h include/FT0/DigitQcTask.h include/FT0/PostProcTask.h include/FT0/CFDEffCheck.h diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h new file mode 100644 index 0000000000..02b5e6a5f5 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -0,0 +1,59 @@ +// Copyright 2019-2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General +// Public License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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 AgingLaserPostProcTask.h +/// \author Andreas Molander , Jakub Muszyński +/// \brief Post-processing task that derives a per-channel +/// weighted-mean amplitude, normalised to reference channels. + +#ifndef QC_MODULE_FT0_AGINGLASERPOSTPROC_H +#define QC_MODULE_FT0_AGINGLASERPOSTPROC_H + +#include "QualityControl/PostProcessingInterface.h" +#include + +#include +#include +#include + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief Post-processing task that derives a per-channel +/// weighted-mean amplitude, normalised to reference channels. +class AgingLaserPostProcTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + AgingLaserPostProcTask() = default; + ~AgingLaserPostProcTask() override; + + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + static constexpr std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; + + std::vector mDetectorChIDs; ///< Detector (target) channels + std::vector mReferenceChIDs; ///< Reference channels + + double mADCSearchMin = 150.; ///< lower edge of peak-search window (ADC) for ref channels + double mADCSearchMax = 600.; ///< upper edge of peak-search window (ADC) for ref channels + double mFracWindowA = 0.25; ///< low fractional window parameter a + double mFracWindowB = 0.25; ///< high fractional window parameter b + + std::unique_ptr mAmpVsChNormWeightedMeanA; + std::unique_ptr mAmpVsChNormWeightedMeanC; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_AGINGLASERPOSTPROC_H \ No newline at end of file diff --git a/Modules/FIT/FT0/include/FT0/LinkDef.h b/Modules/FIT/FT0/include/FT0/LinkDef.h index 7933a1aec0..2748953e33 100644 --- a/Modules/FIT/FT0/include/FT0/LinkDef.h +++ b/Modules/FIT/FT0/include/FT0/LinkDef.h @@ -14,5 +14,6 @@ #pragma link C++ class o2::quality_control_modules::ft0::RecPointsQcTask + ; #pragma link C++ class o2::quality_control_modules::ft0::AgingLaserTask + ; +#pragma link C++ class o2::quality_control_modules::ft0::AgingLaserPostProcTask + ; #endif diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx new file mode 100644 index 0000000000..ec89aaab8c --- /dev/null +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -0,0 +1,178 @@ +// Copyright 2019-2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General +// Public License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// 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 AgingLaserPostProcTask.h +/// \author Andreas Molander , Jakub Muszyński + +#include "FT0/AgingLaserPostProcTask.h" + +#include "Common/Utils.h" +#include "FITCommon/HelperHist.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::ft0 +{ + +AgingLaserPostProcTask::~AgingLaserPostProcTask() = default; + +void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Info) << "initialize AgingLaserPostProcTask" << ENDM; + + const std::string detChs = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "detectorChannelIDs", ""); + if (!detChs.empty()) { + mDetectorChIDs = fit::helper::parseParameters(detChs, ","); + } else { // default: all detector channels 0–207 + mDetectorChIDs.resize(208); + std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); + } + + const std::string refChs = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "referenceChannelIDs", ""); + if (!refChs.empty()) { + mReferenceChIDs = fit::helper::parseParameters(refChs, ","); + } else { // default: 208–210 + for (uint8_t ch = 208; ch < 211; ++ch) + mReferenceChIDs.push_back(ch); + } + + mADCSearchMin = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "adcSearchMin", mADCSearchMin); + mADCSearchMax = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "adcSearchMax", mADCSearchMax); + mFracWindowA = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "fracWindowA", mFracWindowA); + mFracWindowB = o2::quality_control_modules::common::getFromConfig( + mCustomParameters, "fracWindowB", mFracWindowB); + + ILOG(Info) << "detector channels : " << detChs << ENDM; + ILOG(Info) << "reference channels : " << refChs << ENDM; + ILOG(Info) << "ADC search window : [" << mADCSearchMin << ", " << mADCSearchMax << "]" << ENDM; + ILOG(Info) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; + + mAmpVsChNormWeightedMeanA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanA", "AmpPerChannelNormWeightedMeanA", + 96, 0, 96); + + mAmpVsChNormWeightedMeanC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanC", "AmpPerChannelNormWeightedMeanC", + 112, 96, 208); +} + +void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv) +{ + mAmpVsChNormWeightedMeanA->Reset(); + mAmpVsChNormWeightedMeanC->Reset(); + + /* ---- fetch source histogram ---- */ + auto& qcdb = srv.get(); + auto moIn = qcdb.retrieveMO("FT0/MO/AgingLaser", "AmpPerChannel", t.timestamp, t.activity); + TH2* h2amp = moIn ? dynamic_cast(moIn->getObject()) : nullptr; + + if (!h2amp) { + ILOG(Error) << "Could not retrieve FT0/MO/AgingLaser/AmpPerChannel for timestamp " + << t.timestamp << ENDM; + return; + } + + /* ---- 1. Reference-channel Gaussian fits ---- */ + std::vector refMus; + + for (auto chId : mReferenceChIDs) { + auto h1 = std::unique_ptr(h2amp->ProjectionY( + Form("ref_%d", chId), chId + 1, chId + 1)); + + const int binLow = h1->FindBin(mADCSearchMin); + const int binHigh = h1->FindBin(mADCSearchMax); + + int binMax = binLow; + for (int b = binLow + 1; b <= binHigh; ++b) { + if (h1->GetBinContent(b) > h1->GetBinContent(binMax)) { + binMax = b; + } + } + const double xMax = h1->GetBinCenter(binMax); + const double winLo = TMath::Max(0., (1. - mFracWindowA) * xMax); + const double winHi = (1. + mFracWindowB) * xMax; + + TF1 g("g", "gaus", winLo, winHi); + if (h1->Fit(&g, "QNRS") == 0) { // 0 = fit OK + refMus.push_back(g.GetParameter(1)); + } else { + ILOG(Warning) << "Gaussian fit failed for reference channel " << int(chId) << ENDM; + } + } + + const unsigned nRef = refMus.size(); + if (nRef == 0) { + ILOG(Error) << "No successful reference fits – cannot normalise." << ENDM; + return; + } + const double norm = std::accumulate(refMus.begin(), refMus.end(), 0.) / nRef; + + /* ---- 2. Loop over ALL channels ---- */ + auto processChannel = [&](uint8_t chId) { + auto h1 = std::unique_ptr(h2amp->ProjectionY( + Form("proj_%d", chId), chId + 1, chId + 1)); + + // global maximum + const int binMax = h1->GetMaximumBin(); + const double xMax = h1->GetBinCenter(binMax); + const double winLo = TMath::Max(0., (1. - mFracWindowA) * xMax); + const double winHi = (1. + mFracWindowB) * xMax; + + double num = 0., den = 0.; + const int binLo = h1->FindBin(winLo); + const int binHi = h1->FindBin(winHi); + for (int b = binLo; b <= binHi; ++b) { + const double w = h1->GetBinContent(b); + const double x = h1->GetBinCenter(b); + num += w * x; + den += w; + } + + const double wMean = (den > 0.) ? num / den : 0.; + const double val = (norm > 0.) ? wMean / norm : 0.; + if (chId < 96) { + mAmpVsChNormWeightedMeanA->SetBinContent(chId + 1, val); + } else if (chId >= 96 && chId <= 208) { + mAmpVsChNormWeightedMeanC->SetBinContent(chId - 95, val); + } + }; + + for (uint8_t ch = 0; ch < sNCHANNELS_PM; ++ch) + processChannel(ch); + + ILOG(Info) << "update done – " << nRef << " reference fits, norm=" << norm << ENDM; +} + +void AgingLaserPostProcTask::finalize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Info) << "finalize AgingLaserPostProcTask" << ENDM; +} + +} // namespace o2::quality_control_modules::ft0 From 00c5d67592e220dc9a1b41cf8d4aaa8be96c8c6b Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 15:05:34 +0200 Subject: [PATCH 05/12] chore: add example workflow for aging trending --- .../FIT/FT0/etc/ft0-aging-laser-postproc.json | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json new file mode 100644 index 0000000000..eee6602fd6 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -0,0 +1,171 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + } + }, + + "tasks": { + "AgingLaser": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "referenceChannelIDs": "208,209,210", + "laserTriggerBCs": "0,1783", + "detectorBCdelay": "131", + "referencePeak1BCdelays": "115,115,115", + "referencePeak2BCdelays": "136,142,135", + "debug": "false" + } + } + }, + + "postprocessing": { + "AgingLaserPostProc": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserPostProcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "extendedTaskParameters": { + "default": { + "adcSearchMin": "150", + "adcSearchMax": "600", + "fracWindowA": "0.25", + "fracWindowB": "0.25", + "detectorChannelIDs": "0-207", + "referenceChannelIDs": "208,210,211" + } + }, + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], + "stopTrigger": [ "userorcontrol" ] + }, + "AgingLaserTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "FT0", + + "resumeTrend": "true", + "producePlotsOnUpdate": "true", + "colorPalette": "1", + + "dataSources": [ + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanA" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "0","1","2","3","4","5","6","7","8","9", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59", + "60","61","62","63","64","65","66","67","68","69", + "70","71","72","73","74","75","76","77","78","79", + "80","81","82","83","84","85","86","87","88","89", + "90","91","92","93","94","95", "96" + ] + ], + "sliceLabels": [ + [ "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "81","82","83","84","85","86","87","88","89","90", + "91","92","93","94","95", "96" + ] + ], + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanC" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "96","97","98","99","100","101","102","103","104","105", + "106","107","108","109","110","111","112","113","114","115", + "116","117","118","119","120","121","122","123","124","125", + "126","127","128","129","130","131","132","133","134","135", + "136","137","138","139","140","141","142","143","144","145", + "146","147","148","149","150","151","152","153","154","155", + "156","157","158","159","160","161","162","163","164","165", + "166","167","168","169","170","171","172","173","174","175", + "176","177","178","179","180","181","182","183","184","185", + "186","187","188","189","190","191","192","193","194","195", + "196","197","198","199","200","201","202","203","204","205", + "206","207", "208" + ] + ], + "sliceLabels": [ + [ "97","98","99","100","101","102","103","104","105","106", + "107","108","109","110","111","112","113","114","115","116", + "117","118","119","120","121","122","123","124","125","126", + "127","128","129","130","131","132","133","134","135","136", + "137","138","139","140","141","142","143","144","145","146", + "147","148","149","150","151","152","153","154","155","156", + "157","158","159","160","161","162","163","164","165","166", + "167","168","169","170","171","172","173","174","175","176", + "177","178","179","180","181","182","183","184","185","186", + "187","188","189","190","191","192","193","194","195","196", + "197","198","199","200","201","202","203","204","205","206", + "207","208" + ] + ], + "moduleName": "QcCommon" + } + ], + + "plots": [ + { + "name": "ft0_aging_laser_A", + "title": "FT0 Aging Monitoring: A-side (ch 1-96)", + "varexp": "AmpPerChannelNormWeightedMeanA.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "12", + "legendTextSize": "0.018", + "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_C", + "title": "FT0 Aging Monitoring: C-side (ch 97-208)", + "varexp": "AmpPerChannelNormWeightedMeanC.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "14", + "legendTextSize": "0.018", + "colorPalette": "1" + } + ], + + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA" ], + "stopTrigger": [ "userorcontrol" ] + } + } + } +} \ No newline at end of file From 83c6f4bcbda1cc308a65665ae0a4bfe2f2f6aff5 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 12 Aug 2025 15:51:35 +0200 Subject: [PATCH 06/12] doc: extend README --- Modules/FIT/FT0/README.md | 147 ++++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 13 deletions(-) diff --git a/Modules/FIT/FT0/README.md b/Modules/FIT/FT0/README.md index a96c101768..37468def8b 100644 --- a/Modules/FIT/FT0/README.md +++ b/Modules/FIT/FT0/README.md @@ -14,20 +14,141 @@ The schematics of the LCS is shown below. Per laser pulse, there will be two sig More information about the LCS and the hardware side of the aging monitoring can be found [here](https://indico.cern.ch/event/1229241/contributions/5172798/attachments/2561719/4420583/Ageing-related%20tasks.pdf). -### `AgingLaserTask` configuration +--- -An example configuration can be found in `etc/ft0-aging-laser.json`. The task parameters are: +### AgingLaserTask -- `detectorChannelIDs`: list of detector channels to be monitored. Omit this parameter to use all. -- `referenceChannelIDs`: the reference channel IDs, should be: "208, 209, 210, 211". -- `detectorAmpCut`: Lower cut on the detector amplitude in ADC ch, default "0". **TODO**: this has no effect at the moment. -- `referenceAmpCut`: Lower cut on the reference channel amplitude in ADC ch to ignore cross talk, default "100". -- `laserTriggerBCs`: list of BCs when the laser fires, should be "0, 1783". -- `detectorBCdelay`: amount of BCs after the laser trigger BCs when the laser pulse is expected in the detector, should be "131". -- `referencePeak1BCdelays`: amount of BCs after the laser trigger BCs when the first laser pulse is expected in the reference channels, should be "115, 115, 115, 115". One value per reference channel, even though they will be the same with the current LCS setup. -- `referencePeak2BCdelays`: amount of BCs after the laser trigger BCs when the second laser pulse is expected in the reference channels, should be "136, 142, 135, 141". One value per reference channel. -- `debug`: If true, an additional set of plots can be produced for debugging purposes, default "false". +#### What it does (high level) -The channel ID and BC values delays are rather fixed and should not change unless the LCS changes significantly. +* Selects laser events in specific bunch crossings (BCs). +* Separately identifies: -**TODO**: should we apply the amplitude cuts in the SliceTrendingTask instead? + * detector-channel laser signals, + * reference-channel peak-1 and peak-2 signals (two per laser shot). +* Fills per-channel **amplitude** and **time** histograms, split by ADC where relevant. +* (Optional) Produces a rich set of debug histograms. + +This task is the *producer* of the raw per-channel spectra used later by post-processing. + +#### Inputs + +* Digits and channel streams (from workflow config), e.g. + + ``` + digits: FT0/DIGITSBC/0 + channels: FT0/DIGITSCH/0 + ``` + +#### Configuration + +Example: `etc/ft0-aging-laser.json`. + +| Key | Type | Default | Meaning | +| ------------------------ | -----------: | ----------------: | -------------------------------------------------------------- | +| `detectorChannelIDs` | list `uint8` | all `0–207` | Detector channels to monitor. | +| `referenceChannelIDs` | list `uint8` | `208,209,210,211` | Reference (laser monitor) channels. | +| `detectorAmpCut` | int | `0` | Minimum ADC for detector channels (**currently not applied**). | +| `referenceAmpCut` | int | `100` | Minimum ADC for reference channels (suppress cross-talk). | +| `laserTriggerBCs` | list `int` | `0,1783` | BCs where the laser is triggered. | +| `detectorBCdelay` | int | `131` | BC shift from trigger to detector signal. | +| `referencePeak1BCdelays` | list `int` | `115,115,115,115` | BC shifts for ref. peak-1 (per reference channel). | +| `referencePeak2BCdelays` | list `int` | `136,142,135,141` | BC shifts for ref. peak-2 (per reference channel). | +| `debug` | bool | `false` | Enable extra (heavy) debug histograms. | + +> The BC delays and channel ID ranges are hardware-driven and stable; adjust only if the LCS timing changes. + +#### Outputs (Monitor Objects) + +Always produced (names correspond to *amplitude/time vs channel*; all TH2I unless noted): + +* **Amplitude vs channel** + + * `AmpPerChannel` - used by postprocessing task + * `AmpVsChADC0` - detector + reference, ADC0 + * `AmpVsChADC1` - detector + reference, ADC1 + * `AmpVsChPeak1ADC0` / `AmpVsChPeak1ADC1` - reference peak-1 + * `AmpVsChPeak2ADC0` / `AmpVsChPeak2ADC1` - reference peak-2 +* **Time vs channel** + + * `TimeVsCh` - detector + reference + * `TimeVsChPeak1` - reference peak-1 (both ADCs) + * `TimeVsChPeak2` - reference peak-2 (both ADCs) + +Debug (enabled with `debug=true`; types indicated): + +* **Reference-channel 1D spectra (per channel, TH1I)** + amplitude: `mMapDebugHistAmp*` (for all/ADC0/ADC1/peak1/peak2/combos) + time: `mMapDebugHistTimePeak{1,2}*` +* **Time vs channel (TH2I)**: `DebugTimeVsChADC{0,1}`, `DebugTimeVsChPeak{1,2}ADC{0,1}` +* **BC distributions (TH1I)**: `DebugBC*`, split by detector/reference, ADC and with/without amplitude cuts +* **Amplitude vs BC (TH2I)** per reference channel: `mMapDebugHistAmpVsBC*` + +> Note: the “time vs BC” debug maps are intentionally disabled in local-batch to avoid ROOT I/O size issues. + +--- + +# AgingLaserPostProcTask + +`o2::quality_control_modules::ft0::AgingLaserPostProcTask` + +#### Purpose + +Reduce the raw per-channel amplitude spectra to **one scalar per channel** representing the **weighted-mean amplitude**, normalized to the reference channels. Output is split into two histograms: A-side (channels 0–95) and C-side (channels 96–207). + +#### Inputs + +* From the QC repository path `FT0/MO/AgingLaser`: + + * `AmpPerChannel` (TH2): amplitude (ADC) vs channel, aggregated by the task. + *(If you renamed the source MO, adjust the retrieval in the task config.)* + +#### Algorithm (per update) + +1. **Reference normalization** + + * For each configured reference channel: + + * Project its slice `AmpPerChannel(ch)` → `TH1`. + * In `[adcSearchMin, adcSearchMax]` find the maximum `x_max`. + * Fit a Gaussian in `[(1−a)·x_max, (1+b)·x_max]` → mean `μ_ref`. + * `norm = average(μ_ref)` over all successful fits. +2. **Per-channel value** + + * For **every** detector channel: + + * Find the global maximum `x_max`. + * In the same fractional window around `x_max`, compute **weighted mean** + `⟨x⟩ = Σ w_i x_i / Σ w_i` with weights `w_i = bin content`. + * Store `value = ⟨x⟩ / norm`. +3. **Publish two TH1F MOs** + + * `AmpPerChannelNormWeightedMeanA`: 96 bins, channels 0–95. + * `AmpPerChannelNormWeightedMeanC`: 112 bins, channels 96–207. + +> Tip: when booking the C-side histogram use upper edge **208** (exclusive) with 112 bins to avoid off-by-one bin widths. + +#### Configuration (extendedTaskParameters) + +| Key | Type | Default | Meaning | +| --------------------- | -----------: | --------: | ---------------------------------------------- | +| `detectorChannelIDs` | list `uint8` | `0–207` | Detector channels to process (subset allowed). | +| `referenceChannelIDs` | list `uint8` | `208–210` | Reference channels used for normalization. | +| `adcSearchMin` | double | `150` | Lower ADC for reference peak search. | +| `adcSearchMax` | double | `600` | Upper ADC for reference peak search. | +| `fracWindowA` | double | `0.25` | Low fractional window, `a` in `(1−a)·x_max`. | +| `fracWindowB` | double | `0.25` | High fractional window, `b` in `(1+b)·x_max`. | + +#### Output objects (names & types) + +* `FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA` (TH1F) +* `FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanC` (TH1F) + +#### Trending (example) + +Having two output MOs, allows us to create two time series, one per side. An example workflow that accomplishes this is in `etc/ft0-aging-laser-postproc.json`. + +#### Notes & gotchas + +* **Off-by-one binning (C side)**: for channels `96–207` you need **112 bins** and an **exclusive** upper edge at `208`. Using `207` with 112 bins yields non-unit bin width and will trip histogram helpers. +* **Reference fits**: if all Gaussian fits fail, the post-proc update exits early and publishes nothing for that cycle. +* **Amplitude cuts**: `detectorAmpCut` is currently not used by the task’s filling logic; if you need a cut in the derived quantity, apply it in post-processing or trending. From 3beb53a00cad8a2bb551b2a6f64b89b9da62b214 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Mon, 18 Aug 2025 15:17:06 +0200 Subject: [PATCH 07/12] feat: changes to task config --- .../FT0/include/FT0/AgingLaserPostProcTask.h | 9 +- .../FIT/FT0/src/AgingLaserPostProcTask.cxx | 90 +++++++++++-------- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h index 02b5e6a5f5..5632a422c8 100644 --- a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -18,11 +18,13 @@ #define QC_MODULE_FT0_AGINGLASERPOSTPROC_H #include "QualityControl/PostProcessingInterface.h" -#include +#include "FT0Base/Constants.h" +#include "FITCommon/PostProcHelper.h" #include #include #include +#include namespace o2::quality_control_modules::ft0 { @@ -35,6 +37,7 @@ class AgingLaserPostProcTask final : public quality_control::postprocessing::Pos AgingLaserPostProcTask() = default; ~AgingLaserPostProcTask() override; + void configure(const boost::property_tree::ptree& config) override; void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; @@ -42,6 +45,8 @@ class AgingLaserPostProcTask final : public quality_control::postprocessing::Pos private: static constexpr std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; + std::string mAmpMoPath = "FT0/MO/AgingLaser"; //< Path to amplitude monitor object + std::vector mDetectorChIDs; ///< Detector (target) channels std::vector mReferenceChIDs; ///< Reference channels @@ -52,6 +57,8 @@ class AgingLaserPostProcTask final : public quality_control::postprocessing::Pos std::unique_ptr mAmpVsChNormWeightedMeanA; std::unique_ptr mAmpVsChNormWeightedMeanC; + + o2::quality_control_modules::fit::PostProcHelper mPostProcHelper; }; } // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx index ec89aaab8c..4a696d80ba 100644 --- a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file AgingLaserPostProcTask.h +/// \file AgingLaserPostProcTask.cxx /// \author Andreas Molander , Jakub Muszyński #include "FT0/AgingLaserPostProcTask.h" @@ -28,47 +28,64 @@ #include using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::fit; namespace o2::quality_control_modules::ft0 { AgingLaserPostProcTask::~AgingLaserPostProcTask() = default; -void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) +void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) { - ILOG(Info) << "initialize AgingLaserPostProcTask" << ENDM; - - const std::string detChs = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "detectorChannelIDs", ""); - if (!detChs.empty()) { - mDetectorChIDs = fit::helper::parseParameters(detChs, ","); - } else { // default: all detector channels 0–207 - mDetectorChIDs.resize(208); - std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); + const char* cfgPath = Form("qc.postprocessing.%s", getID().c_str()); + const char* cfgCustom = Form("%s.custom", cfgPath); + + mPostProcHelper.configure(cfg, cfgPath, "FT0"); + + auto key = [&cfgCustom](const std::string& e) { return Form("%s.%s", cfgCustom, e.c_str()); }; + + mAmpMoPath = helper::getConfigFromPropertyTree(cfg, key("agingTaskSourcePath"), mAmpMoPath); + + mDetectorChIDs.resize(208); + std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); + const std::string detSkip = + helper::getConfigFromPropertyTree(cfg, key("ignoreDetectorChannels"), ""); + if (!detSkip.empty()) { + auto toSkip = fit::helper::parseParameters(detSkip, ","); + for (auto s : toSkip) { + mDetectorChIDs.erase(std::remove(mDetectorChIDs.begin(), mDetectorChIDs.end(), s), + mDetectorChIDs.end()); + } } - const std::string refChs = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "referenceChannelIDs", ""); - if (!refChs.empty()) { - mReferenceChIDs = fit::helper::parseParameters(refChs, ","); - } else { // default: 208–210 - for (uint8_t ch = 208; ch < 211; ++ch) - mReferenceChIDs.push_back(ch); + // Reference channels: default 208–210, then remove skipped ones + mReferenceChIDs.clear(); + for (uint8_t ch = 208; ch < 211; ++ch) { + mReferenceChIDs.push_back(ch); } + const std::string refSkip = + helper::getConfigFromPropertyTree(cfg, key("ignoreRefChannels"), ""); + if (!refSkip.empty()) { + auto toSkip = fit::helper::parseParameters(refSkip, ","); + for (auto s : toSkip) { + mReferenceChIDs.erase(std::remove(mReferenceChIDs.begin(), mReferenceChIDs.end(), s), + mReferenceChIDs.end()); + } + } + + mADCSearchMin = helper::getConfigFromPropertyTree(cfg, key("refPeakWindowMin"), mADCSearchMin); + mADCSearchMax = helper::getConfigFromPropertyTree(cfg, key("refPeakWindowMax"), mADCSearchMax); + mFracWindowA = helper::getConfigFromPropertyTree(cfg, key("fracWindowLow"), mFracWindowA); + mFracWindowB = helper::getConfigFromPropertyTree(cfg, key("fracWindowHigh"), mFracWindowB); +} - mADCSearchMin = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "adcSearchMin", mADCSearchMin); - mADCSearchMax = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "adcSearchMax", mADCSearchMax); - mFracWindowA = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "fracWindowA", mFracWindowA); - mFracWindowB = o2::quality_control_modules::common::getFromConfig( - mCustomParameters, "fracWindowB", mFracWindowB); +void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Debug, Devel) << "initialize AgingLaserPostProcTask" << ENDM; - ILOG(Info) << "detector channels : " << detChs << ENDM; - ILOG(Info) << "reference channels : " << refChs << ENDM; - ILOG(Info) << "ADC search window : [" << mADCSearchMin << ", " << mADCSearchMax << "]" << ENDM; - ILOG(Info) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; + ILOG(Debug, Devel) << "agingTaskSourcePath : " << mAmpMoPath << ENDM; + ILOG(Debug, Devel) << "ADC search window : [" << mADCSearchMin << ", " << mADCSearchMax << "]" << ENDM; + ILOG(Debug, Devel) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; mAmpVsChNormWeightedMeanA = fit::helper::registerHist( getObjectsManager(), @@ -90,11 +107,11 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv /* ---- fetch source histogram ---- */ auto& qcdb = srv.get(); - auto moIn = qcdb.retrieveMO("FT0/MO/AgingLaser", "AmpPerChannel", t.timestamp, t.activity); + auto moIn = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannel", t.timestamp, t.activity); TH2* h2amp = moIn ? dynamic_cast(moIn->getObject()) : nullptr; if (!h2amp) { - ILOG(Error) << "Could not retrieve FT0/MO/AgingLaser/AmpPerChannel for timestamp " + ILOG(Error) << "Could not retrieve " << mAmpMoPath << "/AmpPerChannel for timestamp " << t.timestamp << ENDM; return; } @@ -164,15 +181,16 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv } }; - for (uint8_t ch = 0; ch < sNCHANNELS_PM; ++ch) + for (auto ch : mDetectorChIDs) { processChannel(ch); + } - ILOG(Info) << "update done – " << nRef << " reference fits, norm=" << norm << ENDM; + ILOG(Debug, Devel) << "update done – " << nRef << " reference fits, norm=" << norm << ENDM; } void AgingLaserPostProcTask::finalize(Trigger, framework::ServiceRegistryRef) { - ILOG(Info) << "finalize AgingLaserPostProcTask" << ENDM; + ILOG(Debug, Devel) << "finalize AgingLaserPostProcTask" << ENDM; } -} // namespace o2::quality_control_modules::ft0 +} // namespace o2::quality_control_modules::ft0 \ No newline at end of file From 17c381b24cd685cf4dce1eef7ee0f281a963f783 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 19 Aug 2025 10:58:24 +0200 Subject: [PATCH 08/12] chore: updated workflow --- .../FIT/FT0/etc/ft0-aging-laser-postproc.json | 323 +++++++++--------- 1 file changed, 161 insertions(+), 162 deletions(-) diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json index eee6602fd6..347d3dce40 100644 --- a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -1,171 +1,170 @@ { - "qc": { - "config": { - "database": { - "implementation": "CCDB", - "host": "ccdb-test.cern.ch:8080" + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + } + }, + + "tasks": { + "AgingLaser": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" }, - "conditionDB": { - "url": "alice-ccdb.cern.ch" + "taskParameters": { + "referenceChannelIDs": "208,209,210", + "laserTriggerBCs": "0,1783", + "detectorBCdelay": "131", + "referencePeak1BCdelays": "115,115,115", + "referencePeak2BCdelays": "136,142,135", + "debug": "false" } + } + }, + + "postprocessing": { + "AgingLaserPostProc": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserPostProcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "custom": { + "agingTaskSourcePath": "FT0/MO/AgingLaser", + "refPeakWindowMin": "150", + "refPeakWindowMax": "600", + "fracWindowLow": "0.25", + "fracWindowHigh": "0.25", + "ignoreDetectorChannels": "", + "ignoreRefChannels": "209" + }, + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], + "stopTrigger": [ "userorcontrol" ] }, - - "tasks": { - "AgingLaser": { - "active": "true", - "className": "o2::quality_control_modules::ft0::AgingLaserTask", - "moduleName": "QcFT0", - "detectorName": "FT0", - "cycleDurationSeconds": "60", - "dataSource": { - "type": "direct", - "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + "AgingLaserTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "FT0", + + "resumeTrend": "true", + "producePlotsOnUpdate": "true", + "colorPalette": "1", + + "dataSources": [ + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanA" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "0","1","2","3","4","5","6","7","8","9", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59", + "60","61","62","63","64","65","66","67","68","69", + "70","71","72","73","74","75","76","77","78","79", + "80","81","82","83","84","85","86","87","88","89", + "90","91","92","93","94","95", "96" + ] + ], + "sliceLabels": [ + [ "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "81","82","83","84","85","86","87","88","89","90", + "91","92","93","94","95", "96" + ] + ], + "moduleName": "QcCommon" }, - "taskParameters": { - "referenceChannelIDs": "208,209,210", - "laserTriggerBCs": "0,1783", - "detectorBCdelay": "131", - "referencePeak1BCdelays": "115,115,115", - "referencePeak2BCdelays": "136,142,135", - "debug": "false" + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanC" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "96","97","98","99","100","101","102","103","104","105", + "106","107","108","109","110","111","112","113","114","115", + "116","117","118","119","120","121","122","123","124","125", + "126","127","128","129","130","131","132","133","134","135", + "136","137","138","139","140","141","142","143","144","145", + "146","147","148","149","150","151","152","153","154","155", + "156","157","158","159","160","161","162","163","164","165", + "166","167","168","169","170","171","172","173","174","175", + "176","177","178","179","180","181","182","183","184","185", + "186","187","188","189","190","191","192","193","194","195", + "196","197","198","199","200","201","202","203","204","205", + "206","207", "208" + ] + ], + "sliceLabels": [ + [ "97","98","99","100","101","102","103","104","105","106", + "107","108","109","110","111","112","113","114","115","116", + "117","118","119","120","121","122","123","124","125","126", + "127","128","129","130","131","132","133","134","135","136", + "137","138","139","140","141","142","143","144","145","146", + "147","148","149","150","151","152","153","154","155","156", + "157","158","159","160","161","162","163","164","165","166", + "167","168","169","170","171","172","173","174","175","176", + "177","178","179","180","181","182","183","184","185","186", + "187","188","189","190","191","192","193","194","195","196", + "197","198","199","200","201","202","203","204","205","206", + "207","208" + ] + ], + "moduleName": "QcCommon" } - } - }, - - "postprocessing": { - "AgingLaserPostProc": { - "active": "true", - "className": "o2::quality_control_modules::ft0::AgingLaserPostProcTask", - "moduleName": "QcFT0", - "detectorName": "FT0", - "extendedTaskParameters": { - "default": { - "adcSearchMin": "150", - "adcSearchMax": "600", - "fracWindowA": "0.25", - "fracWindowB": "0.25", - "detectorChannelIDs": "0-207", - "referenceChannelIDs": "208,210,211" - } + ], + + "plots": [ + { + "name": "ft0_aging_laser_A", + "title": "FT0 Aging Monitoring: A-side (ch 1-96)", + "varexp": "AmpPerChannelNormWeightedMeanA.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "12", + "legendTextSize": "0.018", + "colorPalette": "1" }, - "initTrigger": [ "userorcontrol" ], - "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], - "stopTrigger": [ "userorcontrol" ] - }, - "AgingLaserTrending": { - "active": "true", - "className": "o2::quality_control::postprocessing::SliceTrendingTask", - "moduleName": "QualityControl", - "detectorName": "FT0", - - "resumeTrend": "true", - "producePlotsOnUpdate": "true", - "colorPalette": "1", - - "dataSources": [ - { - "type": "repository", - "path": "FT0/MO/AgingLaserPostProc", - "names": [ "AmpPerChannelNormWeightedMeanA" ], - "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", - "axisDivision": [ - [ "0","1","2","3","4","5","6","7","8","9", - "10","11","12","13","14","15","16","17","18","19", - "20","21","22","23","24","25","26","27","28","29", - "30","31","32","33","34","35","36","37","38","39", - "40","41","42","43","44","45","46","47","48","49", - "50","51","52","53","54","55","56","57","58","59", - "60","61","62","63","64","65","66","67","68","69", - "70","71","72","73","74","75","76","77","78","79", - "80","81","82","83","84","85","86","87","88","89", - "90","91","92","93","94","95", "96" - ] - ], - "sliceLabels": [ - [ "1","2","3","4","5","6","7","8","9","10", - "11","12","13","14","15","16","17","18","19","20", - "21","22","23","24","25","26","27","28","29","30", - "31","32","33","34","35","36","37","38","39","40", - "41","42","43","44","45","46","47","48","49","50", - "51","52","53","54","55","56","57","58","59","60", - "61","62","63","64","65","66","67","68","69","70", - "71","72","73","74","75","76","77","78","79","80", - "81","82","83","84","85","86","87","88","89","90", - "91","92","93","94","95", "96" - ] - ], - "moduleName": "QcCommon" - }, - { - "type": "repository", - "path": "FT0/MO/AgingLaserPostProc", - "names": [ "AmpPerChannelNormWeightedMeanC" ], - "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", - "axisDivision": [ - [ "96","97","98","99","100","101","102","103","104","105", - "106","107","108","109","110","111","112","113","114","115", - "116","117","118","119","120","121","122","123","124","125", - "126","127","128","129","130","131","132","133","134","135", - "136","137","138","139","140","141","142","143","144","145", - "146","147","148","149","150","151","152","153","154","155", - "156","157","158","159","160","161","162","163","164","165", - "166","167","168","169","170","171","172","173","174","175", - "176","177","178","179","180","181","182","183","184","185", - "186","187","188","189","190","191","192","193","194","195", - "196","197","198","199","200","201","202","203","204","205", - "206","207", "208" - ] - ], - "sliceLabels": [ - [ "97","98","99","100","101","102","103","104","105","106", - "107","108","109","110","111","112","113","114","115","116", - "117","118","119","120","121","122","123","124","125","126", - "127","128","129","130","131","132","133","134","135","136", - "137","138","139","140","141","142","143","144","145","146", - "147","148","149","150","151","152","153","154","155","156", - "157","158","159","160","161","162","163","164","165","166", - "167","168","169","170","171","172","173","174","175","176", - "177","178","179","180","181","182","183","184","185","186", - "187","188","189","190","191","192","193","194","195","196", - "197","198","199","200","201","202","203","204","205","206", - "207","208" - ] - ], - "moduleName": "QcCommon" - } - ], - - "plots": [ - { - "name": "ft0_aging_laser_A", - "title": "FT0 Aging Monitoring: A-side (ch 1-96)", - "varexp": "AmpPerChannelNormWeightedMeanA.meanY:multigraphtime", - "selection": "", - "option": "A*L PMC PLC", - "graphAxisLabel": ":", - "graphYRange": "0:1.5", - "legendNColums": "12", - "legendTextSize": "0.018", - "colorPalette": "1" - }, - { - "name": "ft0_aging_laser_C", - "title": "FT0 Aging Monitoring: C-side (ch 97-208)", - "varexp": "AmpPerChannelNormWeightedMeanC.meanY:multigraphtime", - "selection": "", - "option": "A*L PMC PLC", - "graphAxisLabel": ":", - "graphYRange": "0:1.5", - "legendNColums": "14", - "legendTextSize": "0.018", - "colorPalette": "1" - } - ], - - "initTrigger": [ "userorcontrol" ], - "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA" ], - "stopTrigger": [ "userorcontrol" ] - } - } + { + "name": "ft0_aging_laser_C", + "title": "FT0 Aging Monitoring: C-side (ch 97-208)", + "varexp": "AmpPerChannelNormWeightedMeanC.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "14", + "legendTextSize": "0.018", + "colorPalette": "1" + } + ], + + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA" ], + "stopTrigger": [ "userorcontrol" ] + } } +} } \ No newline at end of file From e59797dc12a32c2cbf28969d4aa09f3de812562b Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Tue, 19 Aug 2025 18:01:31 +0200 Subject: [PATCH 09/12] feat: change custom param config --- .../FIT/FT0/etc/ft0-aging-laser-postproc.json | 20 +++++++++++------- .../FT0/include/FT0/AgingLaserPostProcTask.h | 2 ++ .../FIT/FT0/src/AgingLaserPostProcTask.cxx | 21 +++++++------------ 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json index 347d3dce40..f48d034976 100644 --- a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -38,14 +38,18 @@ "className": "o2::quality_control_modules::ft0::AgingLaserPostProcTask", "moduleName": "QcFT0", "detectorName": "FT0", - "custom": { - "agingTaskSourcePath": "FT0/MO/AgingLaser", - "refPeakWindowMin": "150", - "refPeakWindowMax": "600", - "fracWindowLow": "0.25", - "fracWindowHigh": "0.25", - "ignoreDetectorChannels": "", - "ignoreRefChannels": "209" + "extendedTaskParameters": { + "default": { + "default": { + "agingTaskSourcePath": "FT0/MO/AgingLaser", + "refPeakWindowMin": "150", + "refPeakWindowMax": "600", + "fracWindowLow": "0.30", + "fracWindowHigh": "0.30", + "ignoreDetectorChannels": "", + "ignoreRefChannels": "209" + } + } }, "initTrigger": [ "userorcontrol" ], "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h index 5632a422c8..ea2d2ba1e2 100644 --- a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -26,6 +26,8 @@ #include #include +using namespace o2::quality_control::core; + namespace o2::quality_control_modules::ft0 { diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx index 4a696d80ba..c95d9b1dc8 100644 --- a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -37,19 +37,12 @@ AgingLaserPostProcTask::~AgingLaserPostProcTask() = default; void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) { - const char* cfgPath = Form("qc.postprocessing.%s", getID().c_str()); - const char* cfgCustom = Form("%s.custom", cfgPath); - - mPostProcHelper.configure(cfg, cfgPath, "FT0"); - - auto key = [&cfgCustom](const std::string& e) { return Form("%s.%s", cfgCustom, e.c_str()); }; - - mAmpMoPath = helper::getConfigFromPropertyTree(cfg, key("agingTaskSourcePath"), mAmpMoPath); + mAmpMoPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingTaskSourcePath", mAmpMoPath); mDetectorChIDs.resize(208); std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); const std::string detSkip = - helper::getConfigFromPropertyTree(cfg, key("ignoreDetectorChannels"), ""); + o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ignoreDetectorChannels", ""); if (!detSkip.empty()) { auto toSkip = fit::helper::parseParameters(detSkip, ","); for (auto s : toSkip) { @@ -64,7 +57,7 @@ void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) mReferenceChIDs.push_back(ch); } const std::string refSkip = - helper::getConfigFromPropertyTree(cfg, key("ignoreRefChannels"), ""); + o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ignoreRefChannels", ""); if (!refSkip.empty()) { auto toSkip = fit::helper::parseParameters(refSkip, ","); for (auto s : toSkip) { @@ -73,10 +66,10 @@ void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) } } - mADCSearchMin = helper::getConfigFromPropertyTree(cfg, key("refPeakWindowMin"), mADCSearchMin); - mADCSearchMax = helper::getConfigFromPropertyTree(cfg, key("refPeakWindowMax"), mADCSearchMax); - mFracWindowA = helper::getConfigFromPropertyTree(cfg, key("fracWindowLow"), mFracWindowA); - mFracWindowB = helper::getConfigFromPropertyTree(cfg, key("fracWindowHigh"), mFracWindowB); + mADCSearchMin = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "refPeakWindowMin", mADCSearchMin); + mADCSearchMax = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "refPeakWindowMax", mADCSearchMax); + mFracWindowA = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowLow", mFracWindowA); + mFracWindowB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowHigh", mFracWindowB); } void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) From 2fe9230efd29733c0531d63b4a627267d258e8f8 Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Wed, 20 Aug 2025 14:31:07 +0200 Subject: [PATCH 10/12] feat: BC-based approach to ref channel fitting --- .../FIT/FT0/etc/ft0-aging-laser-postproc.json | 8 ++-- .../FT0/include/FT0/AgingLaserPostProcTask.h | 2 - .../FIT/FT0/src/AgingLaserPostProcTask.cxx | 37 ++++++++++++------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json index f48d034976..4cc6bbe99d 100644 --- a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -22,11 +22,11 @@ "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" }, "taskParameters": { - "referenceChannelIDs": "208,209,210", + "referenceChannelIDs": "208,209,210,211", "laserTriggerBCs": "0,1783", "detectorBCdelay": "131", - "referencePeak1BCdelays": "115,115,115", - "referencePeak2BCdelays": "136,142,135", + "referencePeak1BCdelays": "115,115,115,115", + "referencePeak2BCdelays": "136,142,135,141", "debug": "false" } } @@ -42,8 +42,6 @@ "default": { "default": { "agingTaskSourcePath": "FT0/MO/AgingLaser", - "refPeakWindowMin": "150", - "refPeakWindowMax": "600", "fracWindowLow": "0.30", "fracWindowHigh": "0.30", "ignoreDetectorChannels": "", diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h index ea2d2ba1e2..77173f7c04 100644 --- a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -52,8 +52,6 @@ class AgingLaserPostProcTask final : public quality_control::postprocessing::Pos std::vector mDetectorChIDs; ///< Detector (target) channels std::vector mReferenceChIDs; ///< Reference channels - double mADCSearchMin = 150.; ///< lower edge of peak-search window (ADC) for ref channels - double mADCSearchMax = 600.; ///< upper edge of peak-search window (ADC) for ref channels double mFracWindowA = 0.25; ///< low fractional window parameter a double mFracWindowB = 0.25; ///< high fractional window parameter b diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx index c95d9b1dc8..6ac1eefc13 100644 --- a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -66,8 +66,6 @@ void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) } } - mADCSearchMin = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "refPeakWindowMin", mADCSearchMin); - mADCSearchMax = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "refPeakWindowMax", mADCSearchMax); mFracWindowA = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowLow", mFracWindowA); mFracWindowB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowHigh", mFracWindowB); } @@ -77,7 +75,6 @@ void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) ILOG(Debug, Devel) << "initialize AgingLaserPostProcTask" << ENDM; ILOG(Debug, Devel) << "agingTaskSourcePath : " << mAmpMoPath << ENDM; - ILOG(Debug, Devel) << "ADC search window : [" << mADCSearchMin << ", " << mADCSearchMax << "]" << ENDM; ILOG(Debug, Devel) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; mAmpVsChNormWeightedMeanA = fit::helper::registerHist( @@ -101,7 +98,17 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv /* ---- fetch source histogram ---- */ auto& qcdb = srv.get(); auto moIn = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannel", t.timestamp, t.activity); + auto moIn0 = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannelPeak1ADC0", t.timestamp, t.activity); + auto moIn1 = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannelPeak1ADC1", t.timestamp, t.activity); TH2* h2amp = moIn ? dynamic_cast(moIn->getObject()) : nullptr; + TH2* h2amp0 = moIn0 ? dynamic_cast(moIn0->getObject()) : nullptr; + TH2* h2amp1 = moIn1 ? dynamic_cast(moIn1->getObject()) : nullptr; + TH2* h2ampMerged = nullptr; + if (h2amp0 && h2amp1) { + h2ampMerged = new TH2F("h2ampMerged", "h2ampMerged", 96, 0, 96, 112, 96, 208); + h2ampMerged->Add(h2amp0); + h2ampMerged->Add(h2amp1); + } if (!h2amp) { ILOG(Error) << "Could not retrieve " << mAmpMoPath << "/AmpPerChannel for timestamp " @@ -109,22 +116,26 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv return; } + if (!h2amp0 || !h2amp1) { + ILOG(Error) << "Could not retrieve " << mAmpMoPath << "/AmpPerChannelPeak1ADC0 or " << mAmpMoPath << "/AmpPerChannelPeak1ADC1 for timestamp " + << t.timestamp << ENDM; + return; + } + + if (!h2ampMerged) { + ILOG(Error) << "Could not create merged histogram from " << mAmpMoPath << "/AmpPerChannelPeak1ADC0 and " << mAmpMoPath << "/AmpPerChannelPeak1ADC1 for timestamp " + << t.timestamp << ENDM; + return; + } + /* ---- 1. Reference-channel Gaussian fits ---- */ std::vector refMus; for (auto chId : mReferenceChIDs) { - auto h1 = std::unique_ptr(h2amp->ProjectionY( + auto h1 = std::unique_ptr(h2ampMerged->ProjectionY( Form("ref_%d", chId), chId + 1, chId + 1)); - const int binLow = h1->FindBin(mADCSearchMin); - const int binHigh = h1->FindBin(mADCSearchMax); - - int binMax = binLow; - for (int b = binLow + 1; b <= binHigh; ++b) { - if (h1->GetBinContent(b) > h1->GetBinContent(binMax)) { - binMax = b; - } - } + int binMax = h1->GetMaximumBin(); const double xMax = h1->GetBinCenter(binMax); const double winLo = TMath::Max(0., (1. - mFracWindowA) * xMax); const double winHi = (1. + mFracWindowB) * xMax; From feb9362e2ecbfc889d50d269cf5320f133b42f7d Mon Sep 17 00:00:00 2001 From: mvishiu11 Date: Fri, 22 Aug 2025 09:42:00 +0200 Subject: [PATCH 11/12] feat: proper histogram binning --- Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx index 6ac1eefc13..7e02437d8d 100644 --- a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -105,7 +105,7 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv TH2* h2amp1 = moIn1 ? dynamic_cast(moIn1->getObject()) : nullptr; TH2* h2ampMerged = nullptr; if (h2amp0 && h2amp1) { - h2ampMerged = new TH2F("h2ampMerged", "h2ampMerged", 96, 0, 96, 112, 96, 208); + h2ampMerged = new TH2F("h2ampMerged", "h2ampMerged", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); h2ampMerged->Add(h2amp0); h2ampMerged->Add(h2amp1); } From 5f3de4a6b13cc170559ef916d8f8c596aed6e769 Mon Sep 17 00:00:00 2001 From: Andreas Molander Date: Mon, 3 Nov 2025 18:12:23 +0100 Subject: [PATCH 12/12] FIT: Publish relative aging histograms - Add amplitude histograms that are normalized with the amplitudes collected just after the last aging correction --- Modules/FIT/FT0/README.md | 197 ++++++++++-------- .../FIT/FT0/etc/ft0-aging-laser-postproc.json | 164 +++++++++++---- Modules/FIT/FT0/etc/ft0-aging-laser.json | 6 +- .../FT0/include/FT0/AgingLaserPostProcTask.h | 20 +- .../FIT/FT0/src/AgingLaserPostProcTask.cxx | 157 +++++++++++--- 5 files changed, 382 insertions(+), 162 deletions(-) diff --git a/Modules/FIT/FT0/README.md b/Modules/FIT/FT0/README.md index 37468def8b..da1fda55c1 100644 --- a/Modules/FIT/FT0/README.md +++ b/Modules/FIT/FT0/README.md @@ -1,154 +1,169 @@ # FT0 quality control -## Aging monitoring +# Aging monitoring -The aging monitoring of FT0 is performed by 1 minute long laser runs that should be launched after each beam dump. A dedicated QC task is analyzing the laser data: `o2::quality_control_modules::ft0::AgingLaserTask`. +_The following documentation consern FT0 aging **monitoring**. Software to deduce the aging **correction** will come later._ -At the moment the QC task is adapted to the FT0 laser calibration system (LCS) and the monitoring of the FT0 aging. If needed, the task can be generalized to work with other FIT detectors. +The aging monitoring of FT0 is performed by 1 minute long laser runs that are launched after each beam dump. -### Monitoring principles +Dedicated QC tasks analyze the data: + +- `o2::quality_control_modules::ft0::AgingLaserTask` - raw collection of the data +- `o2::quality_control_modules::ft0::AgingLaserPostProc` - post processing of the data +- `o2::quality_control::postprocessing::SliceTrendingTask` - trending of the post processing data + +At the moment, the QC task is adapted to the FT0 laser calibration system (LCS) and the monitoring of the FT0 aging. If needed, the task can be generalized to work with other FIT detectors. + +## Monitoring principles The schematics of the LCS is shown below. Per laser pulse, there will be two signals in each reference channel and one signal in each detector channel. The signals are separated in time by well defined delays, so one can identify them by BC ID. +The basic idea is to monitor the amplitudes seen in the detector during the laser runs. The reference channels don't age and the amplitudes in these are used as a normalization factor for the detector channel amplitudes. + More information about the LCS and the hardware side of the aging monitoring can be found [here](https://indico.cern.ch/event/1229241/contributions/5172798/attachments/2561719/4420583/Ageing-related%20tasks.pdf). --- +## Aging monitoring QC tasks + ### AgingLaserTask -#### What it does (high level) +The `AgingLaserTask` task collects the raw data from the laser runs. -* Selects laser events in specific bunch crossings (BCs). -* Separately identifies: +**Procedure:** - * detector-channel laser signals, - * reference-channel peak-1 and peak-2 signals (two per laser shot). -* Fills per-channel **amplitude** and **time** histograms, split by ADC where relevant. -* (Optional) Produces a rich set of debug histograms. +* Selects laser events with specific BC IDs +* Separately identifies: + * detector channel laser signals + * reference channel signals, separating the two signals per laser pulse +* Fills per-channel **amplitude** and **time** histograms, both ADCs together as well as separated by ADC +* (Optional) produces a rich set of debug histograms -This task is the *producer* of the raw per-channel spectra used later by post-processing. +This task is the *producer* of the raw per-channel spectra used later in post-processing. -#### Inputs +#### Input -* Digits and channel streams (from workflow config), e.g. +* Digits and channel streams, specified in the config as - ``` - digits: FT0/DIGITSBC/0 - channels: FT0/DIGITSCH/0 + ```json + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, ``` #### Configuration -Example: `etc/ft0-aging-laser.json`. - -| Key | Type | Default | Meaning | -| ------------------------ | -----------: | ----------------: | -------------------------------------------------------------- | -| `detectorChannelIDs` | list `uint8` | all `0–207` | Detector channels to monitor. | -| `referenceChannelIDs` | list `uint8` | `208,209,210,211` | Reference (laser monitor) channels. | -| `detectorAmpCut` | int | `0` | Minimum ADC for detector channels (**currently not applied**). | -| `referenceAmpCut` | int | `100` | Minimum ADC for reference channels (suppress cross-talk). | -| `laserTriggerBCs` | list `int` | `0,1783` | BCs where the laser is triggered. | -| `detectorBCdelay` | int | `131` | BC shift from trigger to detector signal. | -| `referencePeak1BCdelays` | list `int` | `115,115,115,115` | BC shifts for ref. peak-1 (per reference channel). | -| `referencePeak2BCdelays` | list `int` | `136,142,135,141` | BC shifts for ref. peak-2 (per reference channel). | +An example configuration can be found in [etc/ft0-aging-laser.json](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/FIT/FT0/etc/ft0-aging-laser.json). The task parameters are listed in the table below. + +| Key | Type | Default | Meaning | +| ------------------------ | -----------: | ----------------: | --------------------------------------------------------------- | +| `detectorChannelIDs` | list `uint8` | | Detector channels to monitor, omit to use all | +| `referenceChannelIDs` | list `uint8` | `208,209,210,211` | Reference channels to monitor | +| `detectorAmpCut` | int | `0` | Minimum amplitude for detector channels (**currently not applied**) | +| `referenceAmpCut` | int | `100` | Minimum amplitude for reference channels (suppress noise cross-talk) | +| `laserTriggerBCs` | list `int` | `0,1783` | BCs where the laser is fired | +| `detectorBCdelay` | int | `131` | BC delay from laser pulse to detector signal | +| `referencePeak1BCdelays` | list `int` | `115,115,115,115` | BC delay from laser pulse to the first reference channel signal | +| `referencePeak2BCdelays` | list `int` | `136,142,135,141` | BC delay from laser pulse to the second reference channel signal | | `debug` | bool | `false` | Enable extra (heavy) debug histograms. | -> The BC delays and channel ID ranges are hardware-driven and stable; adjust only if the LCS timing changes. - -#### Outputs (Monitor Objects) +> The BC delays and channel ID ranges are stable and set by hardware; adjust only if the LCS changes. -Always produced (names correspond to *amplitude/time vs channel*; all TH2I unless noted): +#### Output -* **Amplitude vs channel** +| Name | Type | Description | +|--------------------------|------|------------------------------------------------------------------| +| `AmpPerChannel` | TH2I | Amplitude distribution per channel (both ADCs) | +| `AmpPerChannelADC0` | TH2I | Amplitude distribution per channel for ADC0 | +| `AmpPerChannelADC1` | TH2I | Amplitude distribution per channel for ADC1 | +| `AmpPerChannelPeak1ADC0` | TH2I | Amplitude distribution per channel for the first peak with ADC0 | +| `AmpPerChannelPeak1ADC1` | TH2I | Amplitude distribution per channel for the first peak with ADC1 | +| `AmpPerChannelPeak2ADC0` | TH2I | Amplitude distribution per channel for the second peak with ADC0 | +| `AmpPerChannelPeak2ADC1` | TH2I | Amplitude distribution per channel for the second peak with ADC1 | +| `TimePerChannel` | TH2I | Time distribution per channel (both ADCs) | +| `TimePerChannelPeak1` | TH2I | Time distribution per channel for the first peak (both ADCs) | +| `TimePerChannelPeak2` | TH2I | Time distribution per channel for the second peak (both ADCs) | - * `AmpPerChannel` - used by postprocessing task - * `AmpVsChADC0` - detector + reference, ADC0 - * `AmpVsChADC1` - detector + reference, ADC1 - * `AmpVsChPeak1ADC0` / `AmpVsChPeak1ADC1` - reference peak-1 - * `AmpVsChPeak2ADC0` / `AmpVsChPeak2ADC1` - reference peak-2 -* **Time vs channel** +> A set of debug histograms can be set if the `debug` parameter is set to `true`. See the task header file for the definition of these histograms. - * `TimeVsCh` - detector + reference - * `TimeVsChPeak1` - reference peak-1 (both ADCs) - * `TimeVsChPeak2` - reference peak-2 (both ADCs) +#### TODO -Debug (enabled with `debug=true`; types indicated): +- The MO's should be distinguished by: + - Gain setting (number of ADC channels per MIP) + - In the future we will fetch this from CCDB, but at the moment we pass the value via the QC config. + - The gain could be stored in the MO metadata. This can then be used as we wish in the post processing. + - B field -> We decided not to care about this (???) + - Can be fetched from GRP in CCDB (?) +- Should we have some out-of-bunch checking as part of a LCS sanity check? -* **Reference-channel 1D spectra (per channel, TH1I)** - amplitude: `mMapDebugHistAmp*` (for all/ADC0/ADC1/peak1/peak2/combos) - time: `mMapDebugHistTimePeak{1,2}*` -* **Time vs channel (TH2I)**: `DebugTimeVsChADC{0,1}`, `DebugTimeVsChPeak{1,2}ADC{0,1}` -* **BC distributions (TH1I)**: `DebugBC*`, split by detector/reference, ADC and with/without amplitude cuts -* **Amplitude vs BC (TH2I)** per reference channel: `mMapDebugHistAmpVsBC*` - -> Note: the “time vs BC” debug maps are intentionally disabled in local-batch to avoid ROOT I/O size issues. - ---- +### AgingLaserPostProcTask -# AgingLaserPostProcTask +The `AgingLaserPostProc` task reads the output from the `AgingLaserTask` and produces output suitable for aging monitoring. -`o2::quality_control_modules::ft0::AgingLaserPostProcTask` - -#### Purpose - -Reduce the raw per-channel amplitude spectra to **one scalar per channel** representing the **weighted-mean amplitude**, normalized to the reference channels. Output is split into two histograms: A-side (channels 0–95) and C-side (channels 96–207). - -#### Inputs +#### Input * From the QC repository path `FT0/MO/AgingLaser`: + * `AmpPerChannel` (TH2): amplitude (ADC) vs channel - * `AmpPerChannel` (TH2): amplitude (ADC) vs channel, aggregated by the task. - *(If you renamed the source MO, adjust the retrieval in the task config.)* - -#### Algorithm (per update) +#### Algorithm 1. **Reference normalization** - * For each configured reference channel: - - * Project its slice `AmpPerChannel(ch)` → `TH1`. - * In `[adcSearchMin, adcSearchMax]` find the maximum `x_max`. - * Fit a Gaussian in `[(1−a)·x_max, (1+b)·x_max]` → mean `μ_ref`. + * Project its slice amplitude distribtion `AmpPerChannel` → `TH1` + * Find the maximum `x_max` + * Fit a Gaussian in `[(1−fracWindowLow)·x_max, (1+fracWindowHigh)·x_max]` → mean `μ_ref`. * `norm = average(μ_ref)` over all successful fits. 2. **Per-channel value** - * For **every** detector channel: - * Find the global maximum `x_max`. * In the same fractional window around `x_max`, compute **weighted mean** `⟨x⟩ = Σ w_i x_i / Σ w_i` with weights `w_i = bin content`. * Store `value = ⟨x⟩ / norm`. -3. **Publish two TH1F MOs** - + * Store `value_corrected = value / value_after_last_aging_correction` +3. **Publish four TH1F MOs** * `AmpPerChannelNormWeightedMeanA`: 96 bins, channels 0–95. * `AmpPerChannelNormWeightedMeanC`: 112 bins, channels 96–207. + * `AmpPerChannelNormWeightedMeanCorrectedA`: 96 bins, channels 0–95. Normalized with amplitudes from the last aging correction. + * `AmpPerChannelNormWeightedMeanCorrectedC`: 112 bins, channels 96–207. Normalized with amplitudes from the last aging correction. +4. **In case of a "reset" run - publish two more TH1F MOs** + * `AmpPerChannelNormWeightedMeanAfterLastCorrectionA`: 96 bins, channels 0–95. Used as normalization to deduce relative aging since last aging correction. + * `AmpPerChannelNormWeightedMeanAfterLastCorrectionC`: 112 bins, channels 96–207. Used as normalization to deduce relative aging since last aging correction. > Tip: when booking the C-side histogram use upper edge **208** (exclusive) with 112 bins to avoid off-by-one bin widths. -#### Configuration (extendedTaskParameters) +#### Configuration -| Key | Type | Default | Meaning | -| --------------------- | -----------: | --------: | ---------------------------------------------- | -| `detectorChannelIDs` | list `uint8` | `0–207` | Detector channels to process (subset allowed). | -| `referenceChannelIDs` | list `uint8` | `208–210` | Reference channels used for normalization. | -| `adcSearchMin` | double | `150` | Lower ADC for reference peak search. | -| `adcSearchMax` | double | `600` | Upper ADC for reference peak search. | -| `fracWindowA` | double | `0.25` | Low fractional window, `a` in `(1−a)·x_max`. | -| `fracWindowB` | double | `0.25` | High fractional window, `b` in `(1+b)·x_max`. | +| Key | Type | Default | Meaning | +|--------------------------|--------------:|----------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `reset` | bool | false | To be set true (only) on the scans following aging corrections. The amplitudes are stored in an additional path, and are used for normalization in following laser scans. | +| `useDeadChannelMap` | bool | true | If true, channels marked dead in the dead channel map are not processed | +| `ignoreDetectorChannels` | list `uint_8` | | Detector channels to ignore | +| `ignoreRefChannels` | list `uint8` | | Reference channels to ignore | +| `fracWindowLow` | double | `0.25` | Low fractional window for the Gaussian fits | +| `fracWindowHigh` | double | `0.25` | High fractional window for the Gaussian fits | +| `agingLaserTaskPath` | string | `FT0/MO/AgingLaser` | Path to the AgingLaser task output in QCDB | +| `agingLaserPostProcPath` | string | `FT0/MO/AgingLaserPostProc` | Path to the AgingLaserPostProc task output in QCDB | -#### Output objects (names & types) +#### Output -* `FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA` (TH1F) -* `FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanC` (TH1F) +| Name | Type | Description | +| ---- | ---- | ----------- | +| `AmpPerChannelNormWeightedMeanA` | TH1F | Weighted means of A-side amplitudes normalized with reference channel amplitudes | +| `AmpPerChannelNormWeightedMeanC` | TH1F | Weighted means of C-side amplitudes normalized with reference channel amplitudes | +| `AmpPerChannelNormWeightedMeanCorrectedA` | TH1F | AmpPerChannelNormWeightedMeanA normalized with the same values from last aging correction | +| `AmpPerChannelNormWeightedMeanCorrectedC` | TH1F | AmpPerChannelNormWeightedMeanC normalized with the same values from last aging correction | +| `AmpPerChannelNormWeightedMeanAfterLastCorrectionA` | TH1F | AmpPerChannelNormWeightedMeanA from the last aging correction | +| `AmpPerChannelNormWeightedMeanAfterLastCorrectionC` | TH1F | AmpPerChannelNormWeightedMeanC from the last aging correction | -#### Trending (example) +#### Trending -Having two output MOs, allows us to create two time series, one per side. An example workflow that accomplishes this is in `etc/ft0-aging-laser-postproc.json`. +Having four output MOs from the `AgingLaserPostProcTask`, allows us to create four time series, one per side. An example QC configuration that accomplishes this is in [`etc/ft0-aging-laser-postproc.json`](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json). #### Notes & gotchas * **Off-by-one binning (C side)**: for channels `96–207` you need **112 bins** and an **exclusive** upper edge at `208`. Using `207` with 112 bins yields non-unit bin width and will trip histogram helpers. -* **Reference fits**: if all Gaussian fits fail, the post-proc update exits early and publishes nothing for that cycle. -* **Amplitude cuts**: `detectorAmpCut` is currently not used by the task’s filling logic; if you need a cut in the derived quantity, apply it in post-processing or trending. +* **Reference fits**: if all Gaussian fits fail, the post-processing update exits early and publishes nothing for that cycle. + diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json index 4cc6bbe99d..3f1992e50f 100644 --- a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -3,35 +3,25 @@ "config": { "database": { "implementation": "CCDB", - "host": "ccdb-test.cern.ch:8080" + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" }, "conditionDB": { "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" } }, - - "tasks": { - "AgingLaser": { - "active": "true", - "className": "o2::quality_control_modules::ft0::AgingLaserTask", - "moduleName": "QcFT0", - "detectorName": "FT0", - "cycleDurationSeconds": "60", - "dataSource": { - "type": "direct", - "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" - }, - "taskParameters": { - "referenceChannelIDs": "208,209,210,211", - "laserTriggerBCs": "0,1783", - "detectorBCdelay": "131", - "referencePeak1BCdelays": "115,115,115,115", - "referencePeak2BCdelays": "136,142,135,141", - "debug": "false" - } - } - }, - "postprocessing": { "AgingLaserPostProc": { "active": "true", @@ -41,32 +31,33 @@ "extendedTaskParameters": { "default": { "default": { - "agingTaskSourcePath": "FT0/MO/AgingLaser", - "fracWindowLow": "0.30", - "fracWindowHigh": "0.30", - "ignoreDetectorChannels": "", - "ignoreRefChannels": "209" + "agingLaserTaskPath": "FT0/MO/AgingLaser", + "agingLaserPostProcPath": "FT0/MO/AgingLaserPostProc", + "fracWindowLow": "0.30", + "fracWindowHigh": "0.30", + "ignoreDetectorChannels": "", + "useDeadChannelMap": "true", + "ignoreRefChannels": "209", + "reset": "false" } } }, - "initTrigger": [ "userorcontrol" ], + "initTrigger": [ "userorcontrol" ], "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], - "stopTrigger": [ "userorcontrol" ] + "stopTrigger": [ "userorcontrol" ] }, "AgingLaserTrending": { "active": "true", "className": "o2::quality_control::postprocessing::SliceTrendingTask", "moduleName": "QualityControl", "detectorName": "FT0", - "resumeTrend": "true", "producePlotsOnUpdate": "true", "colorPalette": "1", - "dataSources": [ { "type": "repository", - "path": "FT0/MO/AgingLaserPostProc", + "path": "FT0/MO/AgingLaserPostProc", "names": [ "AmpPerChannelNormWeightedMeanA" ], "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", "axisDivision": [ @@ -99,7 +90,7 @@ }, { "type": "repository", - "path": "FT0/MO/AgingLaserPostProc", + "path": "FT0/MO/AgingLaserPostProc", "names": [ "AmpPerChannelNormWeightedMeanC" ], "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", "axisDivision": [ @@ -133,13 +124,82 @@ ] ], "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanCorrectedA" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "0","1","2","3","4","5","6","7","8","9", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59", + "60","61","62","63","64","65","66","67","68","69", + "70","71","72","73","74","75","76","77","78","79", + "80","81","82","83","84","85","86","87","88","89", + "90","91","92","93","94","95", "96" + ] + ], + "sliceLabels": [ + [ "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "81","82","83","84","85","86","87","88","89","90", + "91","92","93","94","95", "96" + ] + ], + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanCorrectedC" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "96","97","98","99","100","101","102","103","104","105", + "106","107","108","109","110","111","112","113","114","115", + "116","117","118","119","120","121","122","123","124","125", + "126","127","128","129","130","131","132","133","134","135", + "136","137","138","139","140","141","142","143","144","145", + "146","147","148","149","150","151","152","153","154","155", + "156","157","158","159","160","161","162","163","164","165", + "166","167","168","169","170","171","172","173","174","175", + "176","177","178","179","180","181","182","183","184","185", + "186","187","188","189","190","191","192","193","194","195", + "196","197","198","199","200","201","202","203","204","205", + "206","207", "208" + ] + ], + "sliceLabels": [ + [ "97","98","99","100","101","102","103","104","105","106", + "107","108","109","110","111","112","113","114","115","116", + "117","118","119","120","121","122","123","124","125","126", + "127","128","129","130","131","132","133","134","135","136", + "137","138","139","140","141","142","143","144","145","146", + "147","148","149","150","151","152","153","154","155","156", + "157","158","159","160","161","162","163","164","165","166", + "167","168","169","170","171","172","173","174","175","176", + "177","178","179","180","181","182","183","184","185","186", + "187","188","189","190","191","192","193","194","195","196", + "197","198","199","200","201","202","203","204","205","206", + "207","208" + ] + ], + "moduleName": "QcCommon" } ], - "plots": [ { - "name": "ft0_aging_laser_A", - "title": "FT0 Aging Monitoring: A-side (ch 1-96)", + "name": "ft0_aging_laser_A", + "title": "FT0 Aging Monitoring: A-side (ch 1-96)", "varexp": "AmpPerChannelNormWeightedMeanA.meanY:multigraphtime", "selection": "", "option": "A*L PMC PLC", @@ -150,8 +210,8 @@ "colorPalette": "1" }, { - "name": "ft0_aging_laser_C", - "title": "FT0 Aging Monitoring: C-side (ch 97-208)", + "name": "ft0_aging_laser_C", + "title": "FT0 Aging Monitoring: C-side (ch 97-208)", "varexp": "AmpPerChannelNormWeightedMeanC.meanY:multigraphtime", "selection": "", "option": "A*L PMC PLC", @@ -160,6 +220,30 @@ "legendNColums": "14", "legendTextSize": "0.018", "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_A_corrected", + "title": "FT0 Aging Monitoring: A-side (ch 1-96) - corrected", + "varexp": "AmpPerChannelNormWeightedMeanCorrectedA.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "12", + "legendTextSize": "0.018", + "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_C_corrected", + "title": "FT0 Aging Monitoring: C-side (ch 97-208) - corrected", + "varexp": "AmpPerChannelNormWeightedMeanCorrectedC.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "14", + "legendTextSize": "0.018", + "colorPalette": "1" } ], diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser.json b/Modules/FIT/FT0/etc/ft0-aging-laser.json index 3686779058..25b5652c65 100644 --- a/Modules/FIT/FT0/etc/ft0-aging-laser.json +++ b/Modules/FIT/FT0/etc/ft0-aging-laser.json @@ -3,8 +3,8 @@ "config": { "database": { "implementation": "CCDB", - "#host": "ccdb-test.cern.ch:8080", - "host": "http://localhost:8080", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", "username": "not_applicable", "password": "not_applicable", "name": "not_applicable" @@ -28,7 +28,7 @@ "className": "o2::quality_control_modules::ft0::AgingLaserTask", "moduleName": "QcFT0", "detectorName": "FT0", - "cycleDurationSeconds": "300", + "cycleDurationSeconds": "60", "maxNumberCycles": "-1", "dataSource": { "type": "direct", diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h index 77173f7c04..c2a04c9b6e 100644 --- a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -47,16 +47,30 @@ class AgingLaserPostProcTask final : public quality_control::postprocessing::Pos private: static constexpr std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; - std::string mAmpMoPath = "FT0/MO/AgingLaser"; //< Path to amplitude monitor object + /// Flag to indicate whether the aging correction has recently been performed. + /// If true, the trends should be reset. + /// If true, mAmpVsChNormWeightedMean[A/C]AfterLastCorr will be published to the database, and this will be used + /// as a normalization factor for the datapoints in the new trends. + bool mReset = false; + + std::string mAgingLaserPath = "FT0/MO/AgingLaser"; //< Path to the AgingLaser task output in QCDB + std::string mAgingLaserPostProcPath = "FT0/MO/AgingLaserPostProc"; //< Path to the AgingLaserPostProc task output in QCDB std::vector mDetectorChIDs; ///< Detector (target) channels std::vector mReferenceChIDs; ///< Reference channels - double mFracWindowA = 0.25; ///< low fractional window parameter a - double mFracWindowB = 0.25; ///< high fractional window parameter b + /// Flag to indicate whether to skip processing of channels marked dead in the dead channel map + bool mUseDeadChannelMap = true; + + double mFracWindowA = 0.25; ///< low fractional window parameter a + double mFracWindowB = 0.25; ///< high fractional window parameter b std::unique_ptr mAmpVsChNormWeightedMeanA; std::unique_ptr mAmpVsChNormWeightedMeanC; + std::unique_ptr mAmpVsChNormWeightedMeanAfterLastCorrA; + std::unique_ptr mAmpVsChNormWeightedMeanAfterLastCorrC; + std::unique_ptr mAmpVsChNormWeightedMeanCorrectedA; + std::unique_ptr mAmpVsChNormWeightedMeanCorrectedC; o2::quality_control_modules::fit::PostProcHelper mPostProcHelper; }; diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx index 7e02437d8d..c26db51335 100644 --- a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -15,6 +15,7 @@ #include "FT0/AgingLaserPostProcTask.h" #include "Common/Utils.h" +#include "DataFormatsFIT/DeadChannelMap.h" #include "FITCommon/HelperHist.h" #include "QualityControl/DatabaseInterface.h" #include "QualityControl/QcInfoLogger.h" @@ -24,6 +25,7 @@ #include #include +#include #include #include @@ -37,7 +39,11 @@ AgingLaserPostProcTask::~AgingLaserPostProcTask() = default; void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) { - mAmpMoPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingTaskSourcePath", mAmpMoPath); + mReset = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "reset", false); + ILOG(Info, Support) << "Is this a reset run: " << mReset << ENDM; + + mAgingLaserPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingLaserTaskPath", mAgingLaserPath); + mAgingLaserPostProcPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingLaserPostProcPath", mAgingLaserPostProcPath); mDetectorChIDs.resize(208); std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); @@ -50,6 +56,19 @@ void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) mDetectorChIDs.end()); } } + mUseDeadChannelMap = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useDeadChannelMap", true); + if (mUseDeadChannelMap) { + o2::fit::DeadChannelMap* dcm = retrieveConditionAny("FT0/Calib/DeadChannelMap"); + if (!dcm) { + ILOG(Error) << "Could not retrieve DeadChannelMap from CCDB!" << ENDM; + } else { + for (unsigned chId = 0; chId < dcm->map.size(); chId++) { + if (!dcm->isChannelAlive(chId)) { + mDetectorChIDs.erase(std::remove(mDetectorChIDs.begin(), mDetectorChIDs.end(), chId), mDetectorChIDs.end()); + } + } + } + } // Reference channels: default 208–210, then remove skipped ones mReferenceChIDs.clear(); @@ -74,7 +93,8 @@ void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) { ILOG(Debug, Devel) << "initialize AgingLaserPostProcTask" << ENDM; - ILOG(Debug, Devel) << "agingTaskSourcePath : " << mAmpMoPath << ENDM; + ILOG(Debug, Devel) << "agingTaskSourcePath : " << mAgingLaserPath << ENDM; + ILOG(Debug, Devel) << "agingLaserPostProcPath : " << mAgingLaserPostProcPath << ENDM; ILOG(Debug, Devel) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; mAmpVsChNormWeightedMeanA = fit::helper::registerHist( @@ -88,51 +108,120 @@ void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) quality_control::core::PublicationPolicy::ThroughStop, "", "AmpPerChannelNormWeightedMeanC", "AmpPerChannelNormWeightedMeanC", 112, 96, 208); + + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", + 96, 0, 96); + + mAmpVsChNormWeightedMeanAfterLastCorrC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", + 112, 96, 208); + } + + mAmpVsChNormWeightedMeanCorrectedA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanCorrectedA", "AmpPerChannelNormWeightedMeanCorrectedA", + 96, 0, 96); + mAmpVsChNormWeightedMeanCorrectedC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanCorrectedC", "AmpPerChannelNormWeightedMeanCorrectedC", + 112, 96, 208); } void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv) { mAmpVsChNormWeightedMeanA->Reset(); mAmpVsChNormWeightedMeanC->Reset(); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA->Reset(); + mAmpVsChNormWeightedMeanAfterLastCorrC->Reset(); + } + mAmpVsChNormWeightedMeanCorrectedA->Reset(); + mAmpVsChNormWeightedMeanCorrectedC->Reset(); /* ---- fetch source histogram ---- */ auto& qcdb = srv.get(); - auto moIn = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannel", t.timestamp, t.activity); - auto moIn0 = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannelPeak1ADC0", t.timestamp, t.activity); - auto moIn1 = qcdb.retrieveMO(mAmpMoPath, "AmpPerChannelPeak1ADC1", t.timestamp, t.activity); - TH2* h2amp = moIn ? dynamic_cast(moIn->getObject()) : nullptr; - TH2* h2amp0 = moIn0 ? dynamic_cast(moIn0->getObject()) : nullptr; - TH2* h2amp1 = moIn1 ? dynamic_cast(moIn1->getObject()) : nullptr; - TH2* h2ampMerged = nullptr; - if (h2amp0 && h2amp1) { - h2ampMerged = new TH2F("h2ampMerged", "h2ampMerged", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); - h2ampMerged->Add(h2amp0); - h2ampMerged->Add(h2amp1); - } - if (!h2amp) { - ILOG(Error) << "Could not retrieve " << mAmpMoPath << "/AmpPerChannel for timestamp " + auto moAmpPerChannel = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannel", t.timestamp, t.activity); + auto moAmpPerChannelPeak1ADC0 = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannelPeak1ADC0", t.timestamp, t.activity); + auto moAmpPerChannelPeak1ADC1 = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannelPeak1ADC1", t.timestamp, t.activity); + + TH2* h2AmpPerChannel = moAmpPerChannel ? dynamic_cast(moAmpPerChannel->getObject()) : nullptr; + TH2* h2AmpPerChannelPeak1ADC0 = moAmpPerChannelPeak1ADC0 ? dynamic_cast(moAmpPerChannelPeak1ADC0->getObject()) : nullptr; + TH2* h2AmpPerChannelPeak1ADC1 = moAmpPerChannelPeak1ADC1 ? dynamic_cast(moAmpPerChannelPeak1ADC1->getObject()) : nullptr; + TH2* hAmpPerChannelPeak1 = nullptr; + + if (h2AmpPerChannelPeak1ADC0 && h2AmpPerChannelPeak1ADC1) { + hAmpPerChannelPeak1 = new TH2F("hAmpPerChannelPeak1", "hAmpPerChannelPeak1", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + hAmpPerChannelPeak1->Add(h2AmpPerChannelPeak1ADC0); + hAmpPerChannelPeak1->Add(h2AmpPerChannelPeak1ADC1); + } else { + ILOG(Fatal) << "Could not retrieve " << mAgingLaserPath << "/AmpPerChannelPeak1ADC0 or " << mAgingLaserPath << "/AmpPerChannelPeak1ADC1 for timestamp " << t.timestamp << ENDM; - return; } - if (!h2amp0 || !h2amp1) { - ILOG(Error) << "Could not retrieve " << mAmpMoPath << "/AmpPerChannelPeak1ADC0 or " << mAmpMoPath << "/AmpPerChannelPeak1ADC1 for timestamp " + if (!h2AmpPerChannel) { + ILOG(Fatal) << "Could not retrieve " << mAgingLaserPath << "/AmpPerChannel for timestamp " << t.timestamp << ENDM; - return; } - if (!h2ampMerged) { - ILOG(Error) << "Could not create merged histogram from " << mAmpMoPath << "/AmpPerChannelPeak1ADC0 and " << mAmpMoPath << "/AmpPerChannelPeak1ADC1 for timestamp " + if (!hAmpPerChannelPeak1) { + ILOG(Fatal) << "Could not create merged histogram from " << mAgingLaserPath << "/AmpPerChannelPeak1ADC0 and " << mAgingLaserPath << "/AmpPerChannelPeak1ADC1 for timestamp " << t.timestamp << ENDM; - return; + } + + // Weighted mean of amplitude per detector channel, normalized by the average of the reference channel amplitudes, as stored after performing an aging correction. + // This is used as a normalization factor to get the relative aging in "non-reset" runs since the time of the last aging correction. + std::unique_ptr hAmpVsChWeightedMeanAfterLastCorrA; + std::unique_ptr hAmpVsChWeightedMeanAfterLastCorrC; + + if (!mReset) { + auto validity = qcdb.getLatestObjectValidity("qc/" + mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA"); + long timestamp = validity.getMin(); + + ILOG(Info, Support) << "Retrieving normalization histograms from timestamp " << timestamp << ENDM; + + auto moAmpPerChannelNormWeightedMeanAfterLastCorrA = qcdb.retrieveMO( + mAgingLaserPostProcPath, "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", timestamp); + if (!moAmpPerChannelNormWeightedMeanAfterLastCorrA) { + ILOG(Fatal) << "Failed to retrieve histogram " << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA" + << " for timestamp " << timestamp << "! This is not a 'resetting' run and this histogram is therefore needed." << ENDM; + } else { + hAmpVsChWeightedMeanAfterLastCorrA = std::unique_ptr( + dynamic_cast(moAmpPerChannelNormWeightedMeanAfterLastCorrA->getObject()->Clone())); + } + + auto moAmpPerChannelNormWeightedMeanAfterLastCorrC = qcdb.retrieveMO( + mAgingLaserPostProcPath, "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", timestamp); + if (!moAmpPerChannelNormWeightedMeanAfterLastCorrC) { + ILOG(Fatal) << "Failed to retrieve histogram " << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionC" + << " for timestamp " << timestamp << "! This is not a 'resetting' run and this histogram is therefore needed." + << " Please contact the FIT expert." << ENDM; + } else { + hAmpVsChWeightedMeanAfterLastCorrC = std::unique_ptr( + dynamic_cast(moAmpPerChannelNormWeightedMeanAfterLastCorrC->getObject()->Clone())); + } + + if (!hAmpVsChWeightedMeanAfterLastCorrA || !hAmpVsChWeightedMeanAfterLastCorrC) { + ILOG(Fatal) << "Failed to clone normalization histograms from " + << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA or " + << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionC" + << "! This is not a 'resetting' run and these histograms are therefore needed." << ENDM; + } } /* ---- 1. Reference-channel Gaussian fits ---- */ std::vector refMus; for (auto chId : mReferenceChIDs) { - auto h1 = std::unique_ptr(h2ampMerged->ProjectionY( + auto h1 = std::unique_ptr(hAmpPerChannelPeak1->ProjectionY( Form("ref_%d", chId), chId + 1, chId + 1)); int binMax = h1->GetMaximumBin(); @@ -157,7 +246,7 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv /* ---- 2. Loop over ALL channels ---- */ auto processChannel = [&](uint8_t chId) { - auto h1 = std::unique_ptr(h2amp->ProjectionY( + auto h1 = std::unique_ptr(h2AmpPerChannel->ProjectionY( Form("proj_%d", chId), chId + 1, chId + 1)); // global maximum @@ -180,8 +269,26 @@ void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv const double val = (norm > 0.) ? wMean / norm : 0.; if (chId < 96) { mAmpVsChNormWeightedMeanA->SetBinContent(chId + 1, val); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA->SetBinContent(chId + 1, mAmpVsChNormWeightedMeanA->GetBinContent(chId + 1)); + } + Float_t valCorr = (!mReset ? hAmpVsChWeightedMeanAfterLastCorrA->GetBinContent(chId + 1) : mAmpVsChNormWeightedMeanAfterLastCorrA->GetBinContent(chId + 1)); + if (valCorr == 0) { + ILOG(Error, Support) << "Normalization factor = 0 for channel " << chId + 1 << ". Skipping." << ENDM; + return; + } + mAmpVsChNormWeightedMeanCorrectedA->SetBinContent(chId + 1, val / valCorr); } else if (chId >= 96 && chId <= 208) { mAmpVsChNormWeightedMeanC->SetBinContent(chId - 95, val); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrC->SetBinContent(chId - 95, mAmpVsChNormWeightedMeanC->GetBinContent(chId - 95)); + } + Float_t valCorr = (!mReset ? hAmpVsChWeightedMeanAfterLastCorrC->GetBinContent(chId - 95) : mAmpVsChNormWeightedMeanAfterLastCorrC->GetBinContent(chId - 95)); + if (valCorr == 0) { + ILOG(Error, Support) << "Normalization factor = 0 for channel " << chId + 1 << ". Skipping." << ENDM; + return; + } + mAmpVsChNormWeightedMeanCorrectedC->SetBinContent(chId - 95, val / valCorr); } };