diff --git a/docs/rmg-commands.md b/docs/rmg-commands.md index 1dd42c1fe..01f8b62bd 100644 --- a/docs/rmg-commands.md +++ b/docs/rmg-commands.md @@ -372,7 +372,7 @@ Register detectors as saved in the GDML auxval structure, as written by pygeomto * **Parameter type** – `s` * **Omittable** – `True` * **Default value** – `All` - * **Candidates** – `All Germanium Optical Scintillator` + * **Candidates** – `All Germanium Optical Scintillator Calorimeter` * **Allowed states** – `PreInit` ### `/RMG/Geometry/IncludeGDMLFile` @@ -404,7 +404,7 @@ register a sensitive detector – Detector type * **Parameter type** – `s` * **Omittable** – `False` - * **Candidates** – `Germanium Optical Scintillator` + * **Candidates** – `Germanium Optical Scintillator Calorimeter` * **Parameter** – `pv_name` – Detector physical volume, accepts regex patterns * **Parameter type** – `s` diff --git a/include/RMGCalorimeterDetector.hh b/include/RMGCalorimeterDetector.hh new file mode 100644 index 000000000..a7819091b --- /dev/null +++ b/include/RMGCalorimeterDetector.hh @@ -0,0 +1,58 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#ifndef _RMG_CALORIMETER_DETECTOR_HH_ +#define _RMG_CALORIMETER_DETECTOR_HH_ + +#include +#include + +#include "G4VSensitiveDetector.hh" + +#include "RMGDetectorHit.hh" + +class G4Step; +class G4HCofThisEvent; +class G4TouchableHistory; +/** @brief Class to describe the germanium detector, mainly handles processing of the detected hits. + * Extends @c G4VSensitiveDetector */ +class RMGCalorimeterDetector : public G4VSensitiveDetector { + + public: + + RMGCalorimeterDetector(); + ~RMGCalorimeterDetector() = default; + + RMGCalorimeterDetector(RMGCalorimeterDetector const&) = delete; + RMGCalorimeterDetector& operator=(RMGCalorimeterDetector const&) = delete; + RMGCalorimeterDetector(RMGCalorimeterDetector&&) = delete; + RMGCalorimeterDetector& operator=(RMGCalorimeterDetector&&) = delete; + + void Initialize(G4HCofThisEvent* hit_coll) override; + + /** @brief Process the detected hits computing the various parameters of a @c + * RMGDetectorHit and then adding this to the hit collection.*/ + bool ProcessHits(G4Step* step, G4TouchableHistory* history) override; + void EndOfEvent(G4HCofThisEvent* hit_coll) override; + + private: + + RMGDetectorHitsCollection* fHitsCollection = nullptr; +}; + + +#endif + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/include/RMGCalorimeterOutputScheme.hh b/include/RMGCalorimeterOutputScheme.hh new file mode 100644 index 000000000..76f8050c4 --- /dev/null +++ b/include/RMGCalorimeterOutputScheme.hh @@ -0,0 +1,65 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#ifndef _RMG_CALORIMETER_OUTPUT_SCHEME_HH_ +#define _RMG_CALORIMETER_OUTPUT_SCHEME_HH_ + +#include +#include +#include + +#include "G4AnalysisManager.hh" +#include "G4GenericMessenger.hh" + +#include "RMGCalorimeterDetector.hh" +#include "RMGDetectorHit.hh" +#include "RMGOutputTools.hh" +#include "RMGVOutputScheme.hh" + +class G4Event; +/** @brief Output scheme for Calorimeters. + * + * @details This output scheme records the hits in the Calorimeters. + * The properties of each @c RMGDetectorHit are recorded: + * - event index, + * - time, + * - energy deposition, + */ +class RMGCalorimeterOutputScheme : public RMGVOutputScheme { + + public: + + RMGCalorimeterOutputScheme() {}; + + /** @brief Sets the names of the output columns, invoked in @c RMGRunAction::SetupAnalysisManager */ + void AssignOutputNames(G4AnalysisManager* ana_man) override; + + /** @brief Store the information from the event, invoked in @c RMGEventAction::EndOfEventAction + * @details Only steps with non-zero energy are stored, unless @c fDiscardZeroEnergyHits is false. + */ + void StoreEvent(const G4Event* event) override; + + protected: + + [[nodiscard]] std::string GetNtupleNameFlat() const override { return "calorimeter"; } + + private: + + RMGDetectorHitsCollection* GetHitColl(const G4Event*); +}; + +#endif + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/include/RMGDetectorMetadata.hh b/include/RMGDetectorMetadata.hh index 7dcd2e877..3f8bc3f86 100644 --- a/include/RMGDetectorMetadata.hh +++ b/include/RMGDetectorMetadata.hh @@ -22,6 +22,7 @@ enum RMGDetectorType { kGermanium, kOptical, kScintillator, + kCalorimeter, }; struct RMGDetectorMetadata { diff --git a/python/remage/ipc.py b/python/remage/ipc.py index 2dd1e615b..dc4092765 100644 --- a/python/remage/ipc.py +++ b/python/remage/ipc.py @@ -55,6 +55,7 @@ import os import signal import subprocess +from collections import defaultdict from ._version import __version__ @@ -188,7 +189,7 @@ def __init__(self, ipc_info): """Storage structure for the IPC messages returned by ``remage-cpp``.""" self.ipc_info = ipc_info - def get(self, name: str, expected_len: int = 1) -> list[str]: + def get(self, name: str, expected_len: int = 1) -> list[str | list[str | tuple]]: """Return all messages of a given key ``name`` from the IPC message list. Parameters @@ -203,9 +204,20 @@ def get(self, name: str, expected_len: int = 1) -> list[str]: if len(msg) == expected_len + 1 and msg[0] == name ] if expected_len == 1: + # remove the unneeded extra dimension return [msg[0] for msg in msgs] return msgs + def get_as_dict(self, *args, **kwargs) -> dict[str, list[str | tuple]]: + """Same as :meth:`.get` but return a dictionary keyed by record key.""" + msgs = self.get(*args, **kwargs) + + d = defaultdict(list) + for k, v in msgs: + d[k].append(v) + + return dict(d) + def get_single(self, name: str, default: str) -> str: """Return the single single value for the key ``name`` or ``default`` if not present. diff --git a/python/remage/post_proc.py b/python/remage/post_proc.py index dd249ad32..993c4fd75 100644 --- a/python/remage/post_proc.py +++ b/python/remage/post_proc.py @@ -1,8 +1,9 @@ from __future__ import annotations +import copy import logging import time -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from contextlib import contextmanager from pathlib import Path @@ -41,8 +42,9 @@ def post_proc( Path(p).suffix.lower() for p in [*remage_files, main_output_file] } - detector_info: list[str] = ipc_info.get("output_ntuple", 2) - detector_info_aux: list[str] = ipc_info.get("output_ntuple_aux", 2) + # these are mappings: -> [,
, ...] + detector_info = ipc_info.get_as_dict("output_ntuple", 2) + detector_info_aux = ipc_info.get_as_dict("output_ntuple_aux", 2) assert len(output_file_exts) == 1 @@ -84,19 +86,17 @@ def post_proc( ) log.info(msg) - # registered scintillator or germanium detectors - registered_detectors = list({det[1] for det in detector_info}) - # extract the additional tables in the output file (not detectors) - extra_tables = list({det[1] for det in detector_info_aux}) with tmp_renamed_files(remage_files) as original_files: # also get the additional tables to forward config = get_reboost_config( - registered_detectors, - extra_tables, + detector_info, + detector_info_aux, time_window=time_window_in_us, ) + msg = f"Reboost config: {config}" + log.debug(msg) # use reboost to post-process outputs reboost.build_hit( @@ -222,8 +222,8 @@ def deduplicate_table( def get_reboost_config( - reshape_table_list: Sequence[str], - other_table_list: Sequence[str], + detector_info: Mapping[str, Sequence[str]], + detector_info_aux: Mapping[str, Sequence[str]], *, time_window: float = 10, ) -> dict: @@ -231,11 +231,11 @@ def get_reboost_config( Parameters ---------- - reshape_table_list - a list of the table in the remage file that need to be reshaped - (i.e. Germanium or Scintillator output) - other_table_list - other tables in the file. + detector_info + a mapping of tables in the remage file that need to be reshaped, keyed + by output scheme name (i.e. RMGGermaniumOutputScheme). + detector_info_aux + same as `detector_info`, but holds non-standard tables. time_window time window to use for building hits (in us). @@ -245,24 +245,42 @@ def get_reboost_config( """ config = {} - # get the config for tables to be reshaped - config["processing_groups"] = [ - { - "name": "all", - "detector_mapping": [{"output": table} for table in reshape_table_list], - "hit_table_layout": f"reboost.shape.group.group_by_time(STEPS, {time_window})", - "operations": { - "t0": { - "expression": "ak.fill_none(ak.firsts(HITS.time, axis=-1), 0)", - "units": "ns", - }, - "evtid": "ak.fill_none(ak.firsts(HITS.evtid, axis=-1), 0)", - }, - } + # build a default config for all tables (exclude calorimeter tables) + table_list = [ + v + for k, vals in detector_info.items() + if k != "RMGCalorimeterOutputScheme" + for v in vals ] + default_config = { + "name": "default", + "detector_mapping": [{"output": table} for table in table_list], + "hit_table_layout": f"reboost.shape.group.group_by_time(STEPS, {time_window})", + "operations": { + "t0": { + "expression": "ak.fill_none(ak.firsts(HITS.time, axis=-1), 0)", + "units": "ns", + }, + "evtid": "ak.fill_none(ak.firsts(HITS.evtid, axis=-1), 0)", + }, + } + config["processing_groups"] = [default_config] + + if "RMGCalorimeterOutputScheme" in detector_info: + # special treatment for the calorimeter output scheme + tables = detector_info["RMGCalorimeterOutputScheme"] + + calo_config = copy.deepcopy(default_config) + calo_config["name"] = "RMGCalorimeterOutputScheme" + calo_config["detector_mapping"] = [{"output": table} for table in tables] + # there is always one energy per event, then remove one dimension from the array + calo_config["operations"]["edep"] = "ak.ravel(HITS.edep)" + + config["processing_groups"].append(calo_config) + # forward other tables as they are - config["forward"] = other_table_list + config["forward"] = [v for vals in detector_info_aux.values() for v in vals] return config diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ebce4776f..2d0ea845c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ set(_root ${PROJECT_SOURCE_DIR}) set(PROJECT_PUBLIC_HEADERS ${_root}/include/RMGAnalysisReader.hh + ${_root}/include/RMGCalorimeterDetector.hh + ${_root}/include/RMGCalorimeterOutputScheme.hh ${_root}/include/RMGDetectorHit.hh ${_root}/include/RMGDetectorMetadata.hh ${_root}/include/RMGExceptionHandler.hh @@ -53,6 +55,8 @@ set(PROJECT_PUBLIC_HEADERS set(PROJECT_SOURCES ${_root}/src/RMGAnalysisReader.cc + ${_root}/src/RMGCalorimeterDetector.cc + ${_root}/src/RMGCalorimeterOutputScheme.cc ${_root}/src/RMGDetectorHit.cc ${_root}/src/RMGExceptionHandler.cc ${_root}/src/RMGHardware.cc diff --git a/src/RMGCalorimeterDetector.cc b/src/RMGCalorimeterDetector.cc new file mode 100644 index 000000000..c4d7b899d --- /dev/null +++ b/src/RMGCalorimeterDetector.cc @@ -0,0 +1,103 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include "RMGCalorimeterDetector.hh" + +#include +#include +#include + +#include "G4HCofThisEvent.hh" +#include "G4OpticalPhoton.hh" +#include "G4SDManager.hh" +#include "G4Step.hh" + +#include "RMGHardware.hh" +#include "RMGLog.hh" +#include "RMGManager.hh" +#include "RMGOutputTools.hh" + + +RMGCalorimeterDetector::RMGCalorimeterDetector() : G4VSensitiveDetector("Calorimeter") { + + // declare only one hit collection. + // NOTE: names in the respective output scheme class must match this + G4VSensitiveDetector::collectionName.insert("Hits"); +} + +void RMGCalorimeterDetector::Initialize(G4HCofThisEvent* hit_coll) { + + // create hits collection object + // NOTE: assumes there is only one collection name (see constructor) + fHitsCollection = new RMGDetectorHitsCollection( + G4VSensitiveDetector::SensitiveDetectorName, + G4VSensitiveDetector::collectionName[0] + ); + + // associate it with the G4HCofThisEvent object + auto hc_id = G4SDManager::GetSDMpointer()->GetCollectionID( + G4VSensitiveDetector::SensitiveDetectorName + "/" + G4VSensitiveDetector::collectionName[0] + ); + hit_coll->AddHitsCollection(hc_id, fHitsCollection); +} + +bool RMGCalorimeterDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*history*/) { + + RMGLog::OutDev(RMGLog::debug, "Processing calorimeter hits"); + + // ignore optical photons + if (step->GetTrack()->GetDefinition() == G4OpticalPhoton::OpticalPhotonDefinition()) return false; + + // we're going to use info from the pre-step point + const auto prestep = step->GetPreStepPoint(); + + // check containment of prestep point + auto prestep_inside = RMGOutputTools::check_step_point_containment( + prestep, + RMGDetectorType::kCalorimeter + ); + + if (not prestep_inside) return false; + + // retrieve unique id for persistency, take from the prestep + const auto pv = prestep->GetTouchableHandle()->GetVolume(); + + auto pv_name = pv->GetName(); + const auto pv_copynr = prestep->GetTouchableHandle()->GetCopyNumber(); + + const auto det_cons = RMGManager::Instance()->GetDetectorConstruction(); + auto det_uid = det_cons->GetDetectorMetadata({pv_name, pv_copynr}).uid; + + RMGLog::OutDev(RMGLog::debug, "Hit in calorimeter nr. ", det_uid, " detected"); + + // create a new hit and fill it + if (fHitsCollection->entries() == 0) { + auto _hit = new RMGDetectorHit(); + _hit->energy_deposition = 0; + fHitsCollection->insert(_hit); + } + auto* hit = static_cast(fHitsCollection->GetHit(0)); + + hit->physical_volume = pv; + hit->detector_uid = det_uid; + hit->energy_deposition += step->GetTotalEnergyDeposit(); + hit->global_time = prestep->GetGlobalTime(); + + return true; +} + +void RMGCalorimeterDetector::EndOfEvent(G4HCofThisEvent* /*hit_coll*/) {} + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/src/RMGCalorimeterOutputScheme.cc b/src/RMGCalorimeterOutputScheme.cc new file mode 100644 index 000000000..13bd6b687 --- /dev/null +++ b/src/RMGCalorimeterOutputScheme.cc @@ -0,0 +1,175 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include "RMGCalorimeterOutputScheme.hh" + +#include + +#include "G4AnalysisManager.hh" +#include "G4Event.hh" +#include "G4EventManager.hh" +#include "G4HCtable.hh" +#include "G4OpticalPhoton.hh" +#include "G4SDManager.hh" + +#include "RMGCalorimeterDetector.hh" +#include "RMGHardware.hh" +#include "RMGIpc.hh" +#include "RMGLog.hh" +#include "RMGManager.hh" +#include "RMGNavigationTools.hh" +#include "RMGOutputManager.hh" +#include "RMGOutputTools.hh" +#include "RMGTools.hh" + +namespace u = CLHEP; + + +void RMGCalorimeterOutputScheme::AssignOutputNames(G4AnalysisManager* ana_man) { + + auto rmg_man = RMGOutputManager::Instance(); + const auto det_cons = RMGManager::Instance()->GetDetectorConstruction(); + const auto detectors = det_cons->GetDetectorMetadataMap(); + + std::set registered_uids; + std::map registered_ntuples; + for (auto&& det : detectors) { + if (det.second.type != RMGDetectorType::kCalorimeter) continue; + + // do not register the ntuple twice if two detectors share their uid. + auto had_uid = registered_uids.emplace(det.second.uid); + if (!had_uid.second) continue; + + auto volumes = RMGNavigationTools::FindPhysicalVolume( + det.second.name, + std::to_string(det.second.copy_nr) + ); + if (volumes.empty()) { + RMGLog::Out( + RMGLog::fatal, + "Could not find detector physical volume in RMGCalorimeterOutputScheme::AssignOutputNames" + ); + } + if (volumes.size() > 1) { + RMGLog::Out( + RMGLog::fatal, + "Found multiple physical volumes for detector Name '{}' (copy number {}) - this is not " + "allowed", + det.second.name, + det.second.copy_nr + ); + } + + auto pv = *volumes.begin(); + auto trees = RMGNavigationTools::FindGlobalPositions(pv); + if (trees.size() > 1) { + RMGLog::Out( + RMGLog::fatal, + "more than one way to reach world volume from detector ", + det.second.name + ); + } + + auto ntuple_name = this->GetNtupleName(det.second); + auto ntuple_reg = registered_ntuples.find(ntuple_name); + if (ntuple_reg != registered_ntuples.end()) { + // ntuple already exists, but also store the ntuple id for the other uid(s). + rmg_man->RegisterNtuple(det.second.uid, ntuple_reg->second, ntuple_name); + continue; + } + + auto id = rmg_man->CreateAndRegisterNtuple( + det.second.uid, + ntuple_name, + "RMGCalorimeterOutputScheme", + ana_man + ); + registered_ntuples.emplace(ntuple_name, id); + + // store the indices + ana_man->CreateNtupleIColumn(id, "evtid"); + if (!fNtuplePerDetector) { ana_man->CreateNtupleIColumn(id, "det_uid"); } + + // store the floating points values + ana_man->CreateNtupleDColumn(id, "edep_in_keV"); + ana_man->CreateNtupleDColumn(id, "time_in_ns"); + ana_man->FinishNtuple(id); + } +} + +RMGDetectorHitsCollection* RMGCalorimeterOutputScheme::GetHitColl(const G4Event* event) { + auto sd_man = G4SDManager::GetSDMpointer(); + + auto hit_coll_id = sd_man->GetCollectionID("Calorimeter/Hits"); + if (hit_coll_id < 0) { + RMGLog::OutDev(RMGLog::error, "Could not find hit collection Calorimeter/Hits"); + return nullptr; + } + + auto hit_coll = dynamic_cast( + event->GetHCofThisEvent()->GetHC(hit_coll_id) + ); + + if (!hit_coll) { + RMGLog::Out(RMGLog::error, "Could not find hit collection associated with event"); + return nullptr; + } + + return hit_coll; +} + + +void RMGCalorimeterOutputScheme::StoreEvent(const G4Event* event) { + + auto hit_coll = GetHitColl(event); + if (!hit_coll) return; + + if (hit_coll->entries() <= 0) { + RMGLog::OutDev(RMGLog::debug, "Hit collection is empty"); + return; + } else { + RMGLog::OutDev(RMGLog::debug, "Hit collection contains ", hit_coll->entries(), " hits"); + } + + auto rmg_man = RMGOutputManager::Instance(); + if (rmg_man->IsPersistencyEnabled()) { + RMGLog::OutDev(RMGLog::debug, "Filling persistent data vectors"); + const auto ana_man = G4AnalysisManager::Instance(); + + for (auto hit : *hit_coll->GetVector()) { + + // return if no energy is deposited + if (!hit or (hit->energy_deposition == 0)) continue; + + hit->Print(); + auto ntupleid = rmg_man->GetNtupleID(hit->detector_uid); + + int col_id = 0; + // store the indices + ana_man->FillNtupleIColumn(ntupleid, col_id++, event->GetEventID()); + if (!fNtuplePerDetector) { + ana_man->FillNtupleIColumn(ntupleid, col_id++, hit->detector_uid); + } + + ana_man->FillNtupleDColumn(ntupleid, col_id++, hit->energy_deposition / u::keV); + ana_man->FillNtupleDColumn(ntupleid, col_id++, hit->global_time / u::ns); + + // NOTE: must be called here for hit-oriented output + ana_man->AddNtupleRow(ntupleid); + } + } +} + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/src/RMGGermaniumDetector.cc b/src/RMGGermaniumDetector.cc index f879464e5..7d414ada4 100644 --- a/src/RMGGermaniumDetector.cc +++ b/src/RMGGermaniumDetector.cc @@ -57,7 +57,6 @@ bool RMGGermaniumDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*histo RMGLog::OutDev(RMGLog::debug, "Processing germanium detector hits"); - // return if no energy is deposited // ignore optical photons if (step->GetTrack()->GetDefinition() == G4OpticalPhoton::OpticalPhotonDefinition()) return false; diff --git a/src/RMGGermaniumOutputScheme.cc b/src/RMGGermaniumOutputScheme.cc index 1387469a3..6a9726ae2 100644 --- a/src/RMGGermaniumOutputScheme.cc +++ b/src/RMGGermaniumOutputScheme.cc @@ -241,6 +241,7 @@ void RMGGermaniumOutputScheme::StoreEvent(const G4Event* event) { for (auto hit : *hit_coll->GetVector()) { + // do not store event if no energy is deposited if (!hit or (hit->energy_deposition == 0 and this->fDiscardZeroEnergyHits)) continue; hit->Print(); diff --git a/src/RMGHardware.cc b/src/RMGHardware.cc index 62a9cfc26..5aa2673c6 100644 --- a/src/RMGHardware.cc +++ b/src/RMGHardware.cc @@ -27,6 +27,8 @@ namespace fs = std::filesystem; #include "G4UserLimits.hh" #include "G4VPhysicalVolume.hh" +#include "RMGCalorimeterDetector.hh" +#include "RMGCalorimeterOutputScheme.hh" #include "RMGConfig.hh" #include "RMGGermaniumDetector.hh" #include "RMGGermaniumOutputScheme.hh" @@ -257,6 +259,10 @@ void RMGHardware::ConstructSDandField() { obj = new RMGScintillatorDetector(); output = std::make_shared(); break; + case RMGDetectorType::kCalorimeter: + obj = new RMGCalorimeterDetector(); + output = std::make_shared(); + break; default: RMGLog::OutDev( RMGLog::fatal,