diff --git a/Detectors/FIT/FT0/calibration/CMakeLists.txt b/Detectors/FIT/FT0/calibration/CMakeLists.txt index d103b4a9a18b6..75cf762dad991 100644 --- a/Detectors/FIT/FT0/calibration/CMakeLists.txt +++ b/Detectors/FIT/FT0/calibration/CMakeLists.txt @@ -10,26 +10,51 @@ # or submit itself to any jurisdiction. o2_add_library(FT0Calibration - SOURCES - src/FT0TimeOffsetSlotContainer.cxx - PUBLIC_LINK_LIBRARIES - O2::DataFormatsFT0 - O2::CommonDataFormat - O2::DetectorsCalibration - ) - o2_target_root_dictionary(FT0Calibration - HEADERS - include/FT0Calibration/FT0TimeOffsetSlotContainer.h - ) - o2_add_executable(ft0-time-offset-calib - COMPONENT_NAME calibration - SOURCES workflow/FT0TimeOffsetCalibration-Workflow.cxx - PUBLIC_LINK_LIBRARIES - O2::FT0Calibration O2::FITCalibration - ) - o2_add_executable(ft0-time-spectra-processor - COMPONENT_NAME calibration - SOURCES workflow/FT0TimeSpectraProcessor-Workflow.cxx - PUBLIC_LINK_LIBRARIES - O2::FT0Calibration - ) + SOURCES + src/FT0TimeOffsetSlotContainer.cxx + src/EventsPerBcCalibrator.cxx + PUBLIC_LINK_LIBRARIES + O2::DetectorsCalibration + O2::Framework + O2::CommonUtils + Microsoft.GSL::GSL + O2::DataFormatsFT0 + O2::CommonDataFormat + O2::Steer + O2::CCDB + ROOT::Minuit + ROOT::Hist + ) + +o2_target_root_dictionary(FT0Calibration + HEADERS + include/FT0Calibration/FT0TimeOffsetSlotContainer.h + include/FT0Calibration/EventsPerBcCalibrator.h + ) + +o2_add_executable(ft0-time-offset-calib + COMPONENT_NAME calibration + SOURCES + workflow/FT0TimeOffsetCalibration-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration O2::FITCalibration + ) + +o2_add_executable(ft0-time-spectra-processor + COMPONENT_NAME calibration + SOURCES + workflow/FT0TimeSpectraProcessor-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration + ) + +o2_add_executable(ft0-events-per-bc-processor + COMPONENT_NAME calibration + SOURCES + workflow/FT0EventsPerBcProcessor-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration + O2::Framework + O2::CCDB +) + \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/README.md b/Detectors/FIT/FT0/calibration/README.md new file mode 100644 index 0000000000000..2b14cf4fa594f --- /dev/null +++ b/Detectors/FIT/FT0/calibration/README.md @@ -0,0 +1,62 @@ +# Calibrations + +## Events per BC Calibration +### Description +Generates histograms of **Events per Bunch Crossing (BC)**. Events can be filtered by applying amplitude thresholds to the **A-side** and **C-side**. + +### Command-Line Options +| Option | Default | Description | +| :--- | :--- | :--- | +| `--slot-len-sec` | `3600` | Duration of each slot in seconds. | +| `--slot-len-tf` | `0` | Slot length in Time Frames (TFs). | +| `--one-object-per-run` | — | If set, the workflow creates only one calibration object per run. | +| `--min-entries-number` | `0` | Minimum number of entries required for a slot to be valid. | +| `--min-ampl-side-a` | `-2147483648` | Amplitude threshold for Side A events. | +| `--min-ampl-side-c` | `-2147483648` | Amplitude threshold for Side C events. | + +--- + +## How to Run + +### Simulation Data +First, it is important to digitize data with a non-zero run number, orbit, and timestamp. To set these parameters, one can use the `--configKeyValues` option, as shown in the example below. +``` +o2-sim-digitizer-workflow \ +--onlyDet FT0 \ +--configKeyValues="HBFUtils.nHBFPerTF=128;HBFUtils.orbitFirst=128;HBFUtils.orbitFirstSampled=256;HBFUtils.runNumber=560560;HBFUtils.startTime=1768464099000" +``` + +To process simulation data, digits must first be converted to RAW format. The `o2-ft0-digi2raw` tool performs this conversion and generates the required configuration file. + +Once converted, you can run the calibration either as a single integrated workflow or by spawning as the sender and receiver components separately. + +#### Single Workflow Example +Execute the following command within the simulation directory: +``` +o2-raw-file-reader-workflow --input-conf FT0raw.cfg --loop -1 \ +| o2-ft0-flp-dpl-workflow --condition-backend=http://localhost:8080 \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080 +``` + +Sender example (in simulation directory): +``` +o2-raw-file-reader-workflow --input-conf FT0raw.cfg --loop -1 \ +| o2-ft0-flp-dpl-workflow --condition-backend=http://localhost:8080 \ +| o2-dpl-output-proxy --channel-config "name=downstream,method=connect,address=tcp://localhost:30453,type=push,transport=zeromq" --dataspec "downstream:FT0/DIGITSBC" +``` + +Receiver example: +``` +o2-dpl-raw-proxy --channel-config "name=readout-proxy,type=pull,method=bind,address=tcp://localhost:30453,rateLogging=1,transport=zeromq" --dataspec "A:FT0/DIGITSBC/0" \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10 --min-ampl-side-a=0" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080/ +``` + +### CTF Data +Example: +``` +o2-ctf-reader-workflow --ctf-input ctf.root --onlyDet FT0 \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080/ +``` \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h b/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h new file mode 100644 index 0000000000000..2afdf33980428 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h @@ -0,0 +1,69 @@ +#ifndef O2_FT0TVXPERBCID +#define O2_FT0TVXPERBCID + +#include +#include +#include +#include + +#include "CommonDataFormat/FlatHisto2D.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsFT0/SpectraInfoObject.h" +#include "DataFormatsFT0/Digit.h" +#include "DetectorsCalibration/TimeSlotCalibration.h" +#include "DetectorsCalibration/TimeSlot.h" +#include "CommonDataFormat/TFIDInfo.h" +#include "TH1F.h" +#include "Rtypes.h" + +namespace o2::ft0 +{ +struct EventsPerBc { + EventsPerBc(int32_t minAmplitudeSideA, int32_t minAmplitudeSideC) : mMinAmplitudeSideA(minAmplitudeSideA), mMinAmplitudeSideC(minAmplitudeSideC) {} + + size_t getEntries() const { return entries; } + void print() const; + void fill(const o2::dataformats::TFIDInfo& ti, const gsl::span data); + void merge(const EventsPerBc* prev); + + const int32_t mMinAmplitudeSideA; + const int32_t mMinAmplitudeSideC; + + std::array mTvx{0.0}; + size_t entries{0}; + long startTimeStamp{0}; + long stopTimeStamp{0}; + + ClassDefNV(EventsPerBc, 1); +}; + +class EventsPerBcCalibrator final : public o2::calibration::TimeSlotCalibration +{ + using Slot = o2::calibration::TimeSlot; + using TFType = o2::calibration::TFType; + using EventsHistogram = std::array; + + public: + EventsPerBcCalibrator(uint32_t minNumberOfEntries, int32_t minAmplitudeSideA, int32_t minAmplitudeSideC); + + bool hasEnoughData(const Slot& slot) const override; + void initOutput() override; + void finalizeSlot(Slot& slot) override; + Slot& emplaceNewSlot(bool front, TFType tstart, TFType tend) override; + + const std::vector& getTvxPerBc() { return mTvxPerBcs; } + std::vector>& getTvxPerBcCcdbInfo() { return mTvxPerBcInfos; } + + private: + const uint32_t mMinNumberOfEntries; + const int32_t mMinAmplitudeSideA; + const int32_t mMinAmplitudeSideC; + + std::vector mTvxPerBcs; + std::vector> mTvxPerBcInfos; + + ClassDefOverride(EventsPerBcCalibrator, 1); +}; +} // namespace o2::ft0 + +#endif diff --git a/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx b/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx new file mode 100644 index 0000000000000..e8e6eff79283f --- /dev/null +++ b/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx @@ -0,0 +1,72 @@ +#include "FT0Calibration/EventsPerBcCalibrator.h" +#include "CommonUtils/MemFileHelper.h" + +namespace o2::ft0 +{ +void EventsPerBc::print() const +{ + LOG(info) << entries << " entries"; +} + +void EventsPerBc::fill(const o2::dataformats::TFIDInfo& ti, const gsl::span data) +{ + size_t oldEntries = entries; + for (const auto& digit : data) { + double isVertex = digit.mTriggers.getVertex(); + if (digit.mTriggers.getAmplA() < mMinAmplitudeSideA || digit.mTriggers.getAmplC() < mMinAmplitudeSideC) { + continue; + } + mTvx[digit.mIntRecord.bc] += isVertex; + entries += isVertex; + } + LOG(debug) << "Container is filled with " << entries - oldEntries << " new events"; +} + +void EventsPerBc::merge(const EventsPerBc* prev) +{ + for (int bc = 0; bc < o2::constants::lhc::LHCMaxBunches; bc++) { + mTvx[bc] += prev->mTvx[bc]; + } + entries += prev->entries; +} + +void EventsPerBcCalibrator::initOutput() +{ + mTvxPerBcs.clear(); + mTvxPerBcInfos.clear(); +} + +EventsPerBcCalibrator::EventsPerBcCalibrator(uint32_t minNumberOfEntries, int32_t minAmplitudeSideA, int32_t minAmplitudeSideC) : mMinNumberOfEntries(minNumberOfEntries), mMinAmplitudeSideA(minAmplitudeSideA), mMinAmplitudeSideC(minAmplitudeSideC) +{ + LOG(info) << "Defined threshold for number of entires per slot: " << mMinNumberOfEntries; + LOG(info) << "Defined threshold for side A amplitude for event: " << mMinAmplitudeSideA; + LOG(info) << "Defined threshold for side C amplitude for event: " << mMinAmplitudeSideC; +} + +bool EventsPerBcCalibrator::hasEnoughData(const EventsPerBcCalibrator::Slot& slot) const +{ + return slot.getContainer()->entries > mMinNumberOfEntries; +} + +void EventsPerBcCalibrator::finalizeSlot(EventsPerBcCalibrator::Slot& slot) +{ + LOG(info) << "Finalizing slot from " << slot.getStartTimeMS() << " to " << slot.getEndTimeMS(); + o2::ft0::EventsPerBc* data = slot.getContainer(); + mTvxPerBcs.emplace_back(std::move(data->mTvx)); + + auto clName = o2::utils::MemFileHelper::getClassName(mTvxPerBcs.back()); + auto flName = o2::ccdb::CcdbApi::generateFileName(clName); + + std::map metaData; + mTvxPerBcInfos.emplace_back(std::make_unique("FT0/Calib/EventsPerBc", clName, flName, metaData, slot.getStartTimeMS(), slot.getEndTimeMS())); + LOG(info) << "Created object valid from " << mTvxPerBcInfos.back()->getStartValidityTimestamp() << " to " << mTvxPerBcInfos.back()->getEndValidityTimestamp(); +} + +EventsPerBcCalibrator::Slot& EventsPerBcCalibrator::emplaceNewSlot(bool front, TFType tstart, TFType tend) +{ + auto& cont = getSlots(); + auto& slot = front ? cont.emplace_front(tstart, tend) : cont.emplace_back(tstart, tend); + slot.setContainer(std::make_unique(mMinAmplitudeSideA, mMinAmplitudeSideC)); + return slot; +} +} // namespace o2::ft0 \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h b/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h index 49f72e8cbdfff..6a12994ae77d9 100644 --- a/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h +++ b/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h @@ -16,7 +16,9 @@ #pragma link off all functions; #pragma link C++ class o2::ft0::FT0TimeOffsetSlotContainer + ; +#pragma link C++ class o2::ft0::EventsPerBcCalibrator + ; #pragma link C++ class o2::calibration::TimeSlot < o2::ft0::FT0TimeOffsetSlotContainer>; #pragma link C++ class o2::calibration::TimeSlotCalibration < o2::ft0::FT0TimeOffsetSlotContainer>; - +#pragma link C++ class o2::calibration::TimeSlot < o2::ft0::EventsPerBc> + ; +#pragma link C++ class o2::calibration::TimeSlotCalibration < o2::ft0::EventsPerBc> + ; #endif diff --git a/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx new file mode 100644 index 0000000000000..d6c4459c29b79 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx @@ -0,0 +1,35 @@ +#include "FT0EventsPerBcSpec.h" +#include +o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& cfgc) +{ + using namespace o2::framework; + using o2::calibration::FT0EventsPerBcProcessor; + std::vector inputs; + auto ccdbRequest = std::make_shared(true, // orbitResetTime + false, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputs); + inputs.emplace_back("digits", "FT0", "DIGITSBC"); + std::vector outputs; + outputs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, "EventsPerBc"}, Lifetime::Sporadic); + outputs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, "EventsPerBc"}, Lifetime::Sporadic); + DataProcessorSpec dataProcessorSpec{ + "FT0EventsPerBcProcessor", + inputs, + outputs, + AlgorithmSpec(adaptFromTask(ccdbRequest)), + Options{ + {"slot-len-sec", VariantType::UInt32, 3600u, {"Duration of each slot in seconds"}}, + {"slot-len-tf", VariantType::UInt32, 0u, {"Slot length in Time Frames (TFs)"}}, + {"one-object-per-run", VariantType::Bool, false, {"If set, workflow creates only one calibration object per run"}}, + {"min-entries-number", VariantType::UInt32, 0u, {"Minimum number of entries required for a slot to be valid"}}, + {"min-ampl-side-a", VariantType::Int, 0, {"Amplitude threshold for Side A events"}}, + {"min-ampl-side-c", VariantType::Int, 0, {"Amplitude threshold for Side C events"}}}}; + + WorkflowSpec workflow; + workflow.emplace_back(dataProcessorSpec); + return workflow; +} \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h new file mode 100644 index 0000000000000..53bef2a3979ac --- /dev/null +++ b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h @@ -0,0 +1,122 @@ + +#ifndef O2_CALIBRATION_FT0_EVENTS_PER_BC_CALIBRATOR_H +#define O2_CALIBRATION_FT0_EVENTS_PER_BC_CALIBRATOR_H + +#include "Framework/runDataProcessing.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamSpec.h" +#include +#include "Framework/DeviceSpec.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/Task.h" +#include "DetectorsCalibration/Utils.h" +#include "DetectorsBase/GRPGeomHelper.h" + +#include "DataFormatsFT0/Digit.h" +#include "FT0Calibration/EventsPerBcCalibrator.h" + +namespace o2::calibration +{ +class FT0EventsPerBcProcessor final : public o2::framework::Task +{ + public: + FT0EventsPerBcProcessor(std::shared_ptr request) : mCCDBRequest(request) {} + + void init(o2::framework::InitContext& ic) final + { + o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); + if (ic.options().hasOption("slot-len-sec")) { + mSlotLenSec = ic.options().get("slot-len-sec"); + } + if (ic.options().hasOption("one-object-per-run")) { + mOneObjectPerRun = ic.options().get("one-object-per-run"); + } + if (ic.options().hasOption("slot-len-tf")) { + mSlotLen = ic.options().get("slot-len-tf"); + } + if (ic.options().hasOption("min-entries-number")) { + mMinNumberOfEntries = ic.options().get("min-entries-number"); + } + if (ic.options().hasOption("min-ampl-side-a")) { + mMinAmplitudeSideA = ic.options().get("min-ampl-side-a"); + } + if (ic.options().hasOption("min-ampl-side-c")) { + mMinAmplitudeSideC = ic.options().get("min-ampl-side-c"); + } + + mCalibrator = std::make_unique(mMinNumberOfEntries, mMinAmplitudeSideA, mMinAmplitudeSideC); + + if (mOneObjectPerRun) { + LOG(info) << "Only one object will be created at the end of run"; + mCalibrator->setUpdateAtTheEndOfRunOnly(); + } + if (mOneObjectPerRun == false && mSlotLen == 0) { + LOG(info) << "Defined slot interval to " << mSlotLenSec << " seconds"; + mCalibrator->setSlotLengthInSeconds(mSlotLenSec); + } + if (mOneObjectPerRun == false && mSlotLen != 0) { + LOG(info) << "Defined slot interval to " << mSlotLen << " TFS"; + mCalibrator->setSlotLength(mSlotLen); + } + } + + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) + { + o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + } + + void run(o2::framework::ProcessingContext& pc) final + { + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + auto digits = pc.inputs().get>("digits"); + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); + if (digits.size() == 0) { + return; + } + mCalibrator->process(digits); + if (mOneObjectPerRun == false) { + sendOutput(pc.outputs()); + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + LOG(info) << "Received end-of-stream, checking for slot to finalize..."; + mCalibrator->checkSlotsToFinalize(); + sendOutput(ec.outputs()); + mCalibrator->initOutput(); + } + + void sendOutput(o2::framework::DataAllocator& output) + { + using o2::framework::Output; + const auto& tvxHists = mCalibrator->getTvxPerBc(); + auto& infos = mCalibrator->getTvxPerBcCcdbInfo(); + for (unsigned int idx = 0; idx < tvxHists.size(); idx++) { + auto& info = infos[idx]; + const auto& payload = tvxHists[idx]; + + auto image = o2::ccdb::CcdbApi::createObjectImage(&payload, info.get()); + LOG(info) << "Sending object " << info->getPath() << "/" << info->getFileName() << " of size " << image->size() + << " bytes, valid for " << info->getStartValidityTimestamp() << " : " << info->getEndValidityTimestamp(); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBPayload, "EventsPerBc", idx}, *image.get()); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBWrapper, "EventsPerBc", idx}, *info.get()); + } + + if (tvxHists.size()) { + mCalibrator->initOutput(); + } + } + + private: + std::shared_ptr mCCDBRequest; + std::unique_ptr mCalibrator; + bool mOneObjectPerRun; + uint32_t mSlotLenSec; + o2::calibration::TFType mSlotLen; + uint32_t mMinNumberOfEntries; + int32_t mMinAmplitudeSideA; + int32_t mMinAmplitudeSideC; +}; +} // namespace o2::calibration +#endif \ No newline at end of file diff --git a/Detectors/FIT/FT0/macros/CMakeLists.txt b/Detectors/FIT/FT0/macros/CMakeLists.txt index c4ed27d2513ba..d0d4dd4f28a73 100644 --- a/Detectors/FIT/FT0/macros/CMakeLists.txt +++ b/Detectors/FIT/FT0/macros/CMakeLists.txt @@ -12,3 +12,7 @@ o2_add_test_root_macro(FT0Misaligner.C PUBLIC_LINK_LIBRARIES O2::CCDB O2::FT0Simulation LABELS ft0) + +o2_add_test_root_macro(FT0readEventsPerBc.C + PUBLIC_LINK_LIBRARIES O2::CCDB + LABELS ft0) diff --git a/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C b/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C new file mode 100644 index 0000000000000..db7dfc7b08359 --- /dev/null +++ b/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C @@ -0,0 +1,42 @@ + +#if !defined(__CLING__) || defined(__ROOTCLING__) + +#include +#include +#include "CCDB/CcdbApi.h" +#include "TH1F.h" +#endif + +#include "Framework/Logger.h" +#include "CommonConstants/LHCConstants.h" + +using EventsArray = std::array; +std::unique_ptr hist; +std::unique_ptr canvas; + +void FT0readEventsPerBc(std::string ccdbUrl, long timestamp) +{ + o2::ccdb::CcdbApi ccdbApi; + ccdbApi.init(ccdbUrl); + const std::string ccdbPath = "FT0/Calib/EventsPerBc"; + std::map metadata; + + if (timestamp < 0) { + timestamp = o2::ccdb::getCurrentTimestamp(); + } + + EventsArray* events = ccdbApi.retrieveFromTFileAny(ccdbPath, metadata, timestamp); + + if (!events) { + LOGP(fatal, "EventsPerBc object not found in {}/{} for timestamp {}.", ccdbUrl, ccdbPath, timestamp); + return; + } + + hist = std::make_unique("eventsPerBcHist", "Events per BC", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches - 1); + for (int idx = 0; idx < o2::constants::lhc::LHCMaxBunches; idx++) { + hist->Fill(idx, (*events)[idx]); + } + canvas = std::make_unique(); + hist->Draw(); + canvas->Draw(); +} \ No newline at end of file