diff --git a/DDDigi/io/Digi2ROOT.cpp b/DDDigi/io/Digi2ROOT.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6fadb41e738b6fbe19cb1112b7a1c6453aed8d50 --- /dev/null +++ b/DDDigi/io/Digi2ROOT.cpp @@ -0,0 +1,476 @@ +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== +#ifndef DIGI_DIGI2ROOT_H +#define DIGI_DIGI2ROOT_H + +// Framework include files +#include <DDDigi/DigiContainerProcessor.h> +#include <DDDigi/DigiData.h> + +/// Namespace for the AIDA detector description toolkit +namespace dd4hep { + + /// Namespace for the Digitization part of the AIDA detector description toolkit + namespace digi { + + /// Event action to support edm4hep output format from DDDigi + /** + * Supported output containers types are: + * - edm4hep::MCParticles aka "MCParticles" + * - edm4hep::CalorimeterHitCollection aka "CalorimeterHits" + * - edm4hep::TrackerHitCollection aka "TracketHits" + * + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_DIGITIZATION + */ + class Digi2ROOTWriter : public DigiContainerSequenceAction { + public: + class internals_t; + + protected: + /// Property: Container names to be loaded + std::string m_output; + /// Property: Processor type to manage containers + std::string m_processor_type; + /// Property: Container / data type mapping + std::map<std::string, std::string> m_containers { }; + /// Reference to internals + std::shared_ptr<internals_t> internals; + + protected: + /// Define standard assignments and constructors + DDDIGI_DEFINE_ACTION_CONSTRUCTORS(Digi2ROOTWriter); + /// Default destructor + virtual ~Digi2ROOTWriter(); + + public: + /// Standard constructor + Digi2ROOTWriter(const kernel_t& kernel, const std::string& nam); + + /// Initialization callback + virtual void initialize(); + + /// Finalization callback + virtual void finalize(); + + /// Adopt new parallel worker + virtual void adopt_processor(DigiContainerProcessor* action, + const std::string& container) override final; + + /// Adopt new parallel worker acting on multiple containers + virtual void adopt_processor(DigiContainerProcessor* action, + const std::vector<std::string>& containers); + + /// Callback to store the run information + void beginRun(); + + /// Callback to store the run information + void endRun(); + + /// Callback to store the Geant4 run information + void saveRun(); + + /// Main functional callback + virtual void execute(context_t& context) const; + }; + + /// Actor to save individual data containers to edm4hep + /** Actor to save individual data containers to edm4hep + * + * This is a typical worker action of the Digi2ROOTWriter + * + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_DIGITIZATION + */ + class Digi2ROOTProcessor : public DigiContainerProcessor { + friend class Digi2ROOTWriter; + + protected: + /// Reference to the edm4hep engine + std::shared_ptr<Digi2ROOTWriter::internals_t> internals; + + public: + /// Standard constructor + Digi2ROOTProcessor(const DigiKernel& krnl, const std::string& nam); + + /// Standard destructor + virtual ~Digi2ROOTProcessor() = default; + + void convert_particles(DigiContext& context, const ParticleMapping& cont) const; + void convert_deposits(DigiContext& context, const DepositVector& cont, const predicate_t& predicate) const; + void convert_deposits(DigiContext& context, const DepositMapping& cont, const predicate_t& predicate) const; + void convert_history(DigiContext& context, const DepositsHistory& cont, work_t& work, const predicate_t& predicate) const; + + /// Main functional callback + virtual void execute(DigiContext& context, work_t& work, const predicate_t& predicate) const override final; + }; + } // End namespace digi +} // End namespace dd4hep +#endif // DIGI_DIGI2ROOT_H + +//========================================================================== +// AIDA Detector description implementation +//-------------------------------------------------------------------------- +// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +// All rights reserved. +// +// For the licensing terms see $DD4hepINSTALL/LICENSE. +// For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +// +// Author : M.Frank +// +//========================================================================== + +/// Framework include files +#include <DD4hep/InstanceCount.h> +#include <DDDigi/DigiContext.h> +#include <DDDigi/DigiPlugins.h> +#include <DDDigi/DigiKernel.h> +#include "DigiIO.h" + +/// ROOT include files +#include <TClass.h> +#include <TFile.h> +#include <TTree.h> +#include <TBranch.h> + +#include <vector> + +/// Namespace for the AIDA detector description toolkit +namespace dd4hep { + + /// Namespace for the Digitization part of the AIDA detector description toolkit + namespace digi { + + /// Helper class to create output in edm4hep format + /** Helper class to create output in edm4hep format + * + * \author M.Frank + * \version 1.0 + * \ingroup DD4HEP_DIGITIZATION + */ + class Digi2ROOTWriter::internals_t { + public: + struct BranchWrapper { + TBranch* branch = nullptr; + void* address = nullptr; + void (*fcn_clear)(void* addr) = 0; + void clear() { this->fcn_clear(this->address); } + template <typename T> T* get() { return (T*)this->address; } + }; + Digi2ROOTWriter* m_parent { nullptr }; + typedef std::map<std::string, BranchWrapper> Collections; + typedef std::map<std::string, TTree*> Sections; + /// Known file sections + Sections m_sections; + /// Collections in the event tree + Collections m_collections; + /// Reference to the ROOT file to open + std::unique_ptr<TFile> m_file; + /// Reference to the event data tree + TTree* m_tree; + /// Property: name of the event tree + std::string m_section; + /// Property: vector with disabled collections + std::vector<std::string> m_disabledCollections; + + /// Total numbe rof events to be processed + long num_events { -1 }; + /// Running event counter + long event_count { 0 }; + + private: + /// Helper to register single collection + template <typename T> T* register_collection(const std::string& name, T* collection); + + public: + /// Default constructor + internals_t() = default; + /// Default destructor + ~internals_t() = default; + + /// Commit data at end of filling procedure + void commit(); + /// Commit data to disk and close output stream + void close(); + + /// Create all collections according to the parent setup (locked) + void create_collections(); + /// Clear collection content: Store is still owner! + void clearCollections(); + /// Access named collection: throws exception ifd the collection is not present (unlocked!) + template <typename T> BranchWrapper& get_collection(const T&); + }; + + template <typename T> T* Digi2ROOTWriter::internals_t::register_collection(const std::string& nam, T* coll) { + struct _do_clear { + static void clear(void* ptr) { T* c = (T*)ptr; c->clear(); } + }; + BranchWrapper bw = { nullptr, (void*)coll, _do_clear::clear }; + m_collections.emplace(nam, bw); + m_parent->debug("+++ created collection %s <%s>", nam.c_str(), typeName(typeid(T)).c_str()); + return coll; + } + + /// Create all collections according to the parent setup + void Digi2ROOTWriter::internals_t::create_collections() { + if ( m_collections.empty() ) { + std::string fname = m_parent->m_output; + m_file.reset(new TFile(fname.c_str(), "RECREATE", "DDDigi data")); + for( auto& cont : m_parent->m_containers ) { + const std::string& nam = cont.first; + const std::string& typ = cont.second; + if ( typ == "MCParticles" ) + register_collection(nam, new std::vector<Particle*>()); + else + register_collection(nam, new std::vector<EnergyDeposit*>()); + } + m_parent->info("+++ Will save %ld events to %s", num_events, m_parent->m_output.c_str()); + } + } + + /// Access named collection: throws exception ifd the collection is not present + template <typename T> + Digi2ROOTWriter::internals_t::BranchWrapper& Digi2ROOTWriter::internals_t::get_collection(const T& cont) { + auto iter = m_collections.find(cont.name); + if ( iter == m_collections.end() ) { + m_parent->except("Error"); + } + return iter->second; + } + + /// Clear collection content: Store is still owner! + void Digi2ROOTWriter::internals_t::clearCollections() { + for( auto& coll : m_collections ) + coll.second.clear(); + } + + /// Commit data at end of filling procedure + void Digi2ROOTWriter::internals_t::commit() { + if ( m_file ) { + for( auto& coll : m_collections ) { + coll.second.branch->Write(); + } + clearCollections(); + return; + } + m_parent->except("+++ Failed to write output file. [Stream is not open]"); + } + + /// Commit data to disk and close output stream + void Digi2ROOTWriter::internals_t::close() { + if ( m_file ) { + TDirectory::TContext ctxt(m_file.get()); + Sections::iterator i = m_sections.find(m_section); + m_parent->info("+++ Closing ROOT output file %s", m_file->GetName()); + if ( i != m_sections.end() ) + m_sections.erase(i); + m_collections.clear(); + m_tree->Write(); + m_file->Close(); + m_tree = nullptr; + } + m_file.reset(); + } + + /// Standard constructor + Digi2ROOTWriter::Digi2ROOTWriter(const DigiKernel& krnl, const std::string& nam) + : DigiContainerSequenceAction(krnl, nam) + { + internals = std::make_shared<internals_t>(); + declareProperty("output", m_output); + declareProperty("containers", m_containers); + declareProperty("processor_type", m_processor_type = "Digi2ROOTProcessor"); + internals->m_parent = this; + InstanceCount::increment(this); + } + + /// Default destructor + Digi2ROOTWriter::~Digi2ROOTWriter() {{ + std::lock_guard<std::mutex> lock(m_kernel.global_io_lock()); + internals->close(); + } + internals.reset(); + InstanceCount::decrement(this); + } + + /// Initialization callback + void Digi2ROOTWriter::initialize() { + if ( m_containers.empty() ) { + warning("+++ No input containers given for attenuation action -- no action taken"); + return; + } + internals->num_events = m_kernel.property("numEvents").value<long>(); + for ( const auto& c : m_containers ) { + Key key(c.first, 0, 0); + auto it = m_registered_processors.find(key); + if ( it == m_registered_processors.end() ) { + std::string nam = name() + ".E4H." + c.first; + auto* conv = createAction<DigiContainerProcessor>(m_processor_type, m_kernel, nam); + if ( !conv ) { + except("+++ Failed to create edm4hep processor: %s of type: %s", + nam.c_str(), m_processor_type.c_str()); + } + conv->property("OutputLevel").set(int(outputLevel())); + adopt_processor(conv, c.first); + conv->release(); // Release processor **after** adoption. + } + } + std::lock_guard<std::mutex> lock(m_kernel.global_io_lock()); + m_parallel = false; + internals->create_collections(); + this->DigiContainerSequenceAction::initialize(); + } + + /// Finalization callback + void Digi2ROOTWriter::finalize() { + internals->close(); + } + + /// Adopt new parallel worker + void Digi2ROOTWriter::adopt_processor(DigiContainerProcessor* action, + const std::string& container) + { + std::size_t idx = container.find('/'); + if ( idx != std::string::npos ) { + std::string nam = container.substr(0, idx); + std::string typ = container.substr(idx+1); + auto* act = dynamic_cast<Digi2ROOTProcessor*>(action); + if ( act ) { // This is not nice! Need to think about something better. + act->internals = this->internals; + } + this->DigiContainerSequenceAction::adopt_processor(action, nam); + m_containers.emplace(nam, typ); + return; + } + except("+++ Invalid container specification: %s. %s", + container.c_str(), "Specify container as tuple: \"<name>/<type>\" !"); + } + + /// Adopt new parallel worker acting on multiple containers + void Digi2ROOTWriter::adopt_processor(DigiContainerProcessor* action, + const std::vector<std::string>& containers) + { + DigiContainerSequenceAction::adopt_processor(action, containers); + } + + /// Main functional callback + void Digi2ROOTWriter::execute(DigiContext& context) const { + std::lock_guard<std::mutex> lock(context.global_io_lock()); + this->DigiContainerSequenceAction::execute(context); + this->internals->commit(); + if ( ++internals->event_count == internals->num_events ) { + internals->close(); + } + } + + /// Callback to store the run information + void Digi2ROOTWriter::beginRun() { + saveRun(); + } + + /// Callback to store the run information + void Digi2ROOTWriter::endRun() { + // saveRun(run); + } + + /// Callback to store the Geant4 run information + void Digi2ROOTWriter::saveRun() { + warning("saveRun(): RunHeader not implemented in EDM4hep, nothing written ..."); + } + + + /// Standard constructor + Digi2ROOTProcessor::Digi2ROOTProcessor(const DigiKernel& krnl, const std::string& nam) + : DigiContainerProcessor(krnl, nam) + { + } + + void Digi2ROOTProcessor::convert_particles(DigiContext& ctxt, + const ParticleMapping& cont) const + { + auto& coll = internals->get_collection(cont); + auto* vec = coll.get<std::vector<const ParticleMapping::value_type*> >(); + vec->reserve(cont.size()); + for( const auto& p : cont ) { + vec->emplace_back(&p); + } + info("%s+++ %-24s added %6ld entries from mask: %04X", + ctxt.event->id(), cont.name.c_str(), vec->size(), cont.key.mask()); + } + + void Digi2ROOTProcessor::convert_deposits(DigiContext& ctxt, + const DepositMapping& cont, + const predicate_t& predicate) const + { + auto& coll = internals->get_collection(cont); + auto* vec = coll.get<std::vector<const DepositMapping::value_type*> >(); + vec->reserve(cont.size()); + for ( const auto& depo : cont ) { + if ( predicate(depo) ) { + vec->emplace_back(&depo); + } + } + info("%s+++ %-24s added %6ld entries from mask: %04X", + ctxt.event->id(), cont.name.c_str(), vec->size(), cont.key.mask()); + } + + void Digi2ROOTProcessor::convert_deposits(DigiContext& ctxt, + const DepositVector& cont, + const predicate_t& predicate) const + { + auto& coll = internals->get_collection(cont); + auto* vec = coll.get<std::vector<const DepositVector::value_type*> >(); + vec->reserve(cont.size()); + for ( const auto& depo : cont ) { + if ( predicate(depo) ) { + vec->emplace_back(&depo); + } + } + info("%s+++ %-24s added %6ld entries from mask: %04X", + ctxt.event->id(), cont.name.c_str(), vec->size(), cont.key.mask()); + } + + void Digi2ROOTProcessor::convert_history(DigiContext& ctxt, + const DepositsHistory& cont, + work_t& work, + const predicate_t& predicate) const + { + info("%s+++ %-32s Segment: %d Predicate:%s Conversion to edm4hep not implemented!", + ctxt.event->id(), cont.name.c_str(), int(work.input.segment->id), + typeName(typeid(predicate)).c_str()); + } + + /// Main functional callback + void Digi2ROOTProcessor::execute(DigiContext& ctxt, work_t& work, const predicate_t& predicate) const { + if ( const auto* p = work.get_input<ParticleMapping>() ) + convert_particles(ctxt, *p); + else if ( const auto* m = work.get_input<DepositMapping>() ) + convert_deposits(ctxt, *m, predicate); + else if ( const auto* v = work.get_input<DepositVector>() ) + convert_deposits(ctxt, *v, predicate); + else if ( const auto* h = work.get_input<DepositsHistory>() ) + convert_history(ctxt, *h, work, predicate); + else + except("Request to handle unknown data type: %s", work.input_type_name().c_str()); + } + + } // End namespace digi +} // End namespace dd4hep + +/// Factory instantiation: +#include <DDDigi/DigiFactories.h> +DECLARE_DIGIACTION_NS(dd4hep::digi,Digi2ROOTWriter) +DECLARE_DIGIACTION_NS(dd4hep::digi,Digi2ROOTProcessor) diff --git a/examples/DDDigi/scripts/TestWriteDigi.py b/examples/DDDigi/scripts/TestWriteDigi.py new file mode 100644 index 0000000000000000000000000000000000000000..4f764a32d595c1c2c7239f7a20c512594e733f64 --- /dev/null +++ b/examples/DDDigi/scripts/TestWriteDigi.py @@ -0,0 +1,38 @@ +# ========================================================================== +# AIDA Detector description implementation +# -------------------------------------------------------------------------- +# Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +# All rights reserved. +# +# For the licensing terms see $DD4hepINSTALL/LICENSE. +# For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +# +# ========================================================================== +from __future__ import absolute_import + + +# --------------------------------------------------------------------------- +def run(): + import DigiTest + digi = DigiTest.Test(geometry=None) + read = digi.input_action('DigiDDG4ROOT/SignalReader', mask=0x0, input=[digi.next_input()]) + dump = digi.event_action('DigiStoreDump/StoreDump', parallel=False) + writ = digi.output_action('Digi2ROOTWriter/EventWriter', + parallel=True, + input_mask=0x0, + input_segment='input', + output='dddigi_write_digi.root') + proc = digi.create_action('Digi2ROOTProcessor/Writer') + hit_type = 'TrackerHits' + if digi.hit_type: + hit_type = digi.hit_type + cont = [c + '/' + hit_type for c in digi.containers()] + writ.adopt_container_processor(proc, cont) + writ.adopt_container_processor(proc, 'MCParticles/MCParticles') + digi.check_creation([read, dump]) + digi.run_checked(num_events=10, num_threads=10, parallel=3) + + +# --------------------------------------------------------------------------- +if __name__ == '__main__': + run()