src/modules/LCIOWriter/LCIOWriterModule.cpp

Implementation of [LCIOWriter] module. More…

Functions

Name
std::array< long double, 3 > getRotationAnglesFromMatrix(ROOT::Math::Rotation3D const & rot_mat)

Detailed Description

Implementation of [LCIOWriter] module.

Copyright: Copyright (c) 2017-2024 CERN and the Allpix Squared authors. This software is distributed under the terms of the MIT License, copied verbatim in the file “LICENSE.md”. 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. SPDX-License-Identifier: MIT

Functions Documentation

function getRotationAnglesFromMatrix

inline std::array< long double, 3 > getRotationAnglesFromMatrix(
    ROOT::Math::Rotation3D const & rot_mat
)

Source code


#include "LCIOWriterModule.hpp"

#include <fstream>
#include <string>
#include <utility>
#include <vector>

#include "core/messenger/Messenger.hpp"
#include "core/utils/log.h"

#include <Math/RotationZYX.h>

#include <IMPL/LCCollectionVec.h>
#include <IMPL/LCEventImpl.h>
#include <IMPL/LCRunHeaderImpl.h>
#include <IMPL/TrackImpl.h>
#include <IMPL/TrackerDataImpl.h>
#include <IMPL/TrackerHitImpl.h>
#include <IMPL/TrackerPulseImpl.h>
#include <IO/LCWriter.h>
#include <IOIMPL/LCFactory.h>
#include <UTIL/CellIDEncoder.h>
#include <lcio.h>

using namespace allpix;
using namespace lcio;

namespace eutelescope {
    static const std::string gTrackerHitEncoding = "sensorID:7,properties:7";
    static const std::string gTrackerPulseEncoding = "sensorID:7,xSeed:12,ySeed:12,xCluSize:5,yCluSize:5,type:5,quality:5";
    static const std::string gTrackerDataEncoding = "sensorID:7,sparsePixelType:5";

    enum HitProperties { kHitInGlobalCoord = 1L << 0, kFittedHit = 1L << 1, kSimulatedHit = 1L << 2, kDeltaHit = 1L << 3 };
    enum ClusterType {
        kEUTelFFClusterImpl = 0,
        kEUTelSparseClusterImpl = 1,
        kEUTelDFFClusterImpl = 2,
        kEUTelBrickedClusterImpl = 3,
        kEUTelGenericSparseClusterImpl = 4,
        kUnknown = 31
    };

} // namespace eutelescope

inline std::array<long double, 3> getRotationAnglesFromMatrix(ROOT::Math::Rotation3D const& rot_mat) {
    double r00 = 0;
    double r01 = 0;
    double r02 = 0;
    double r10 = 0;
    double r11 = 0;
    double r12 = 0;
    double r20 = 0;
    double r21 = 0;
    double r22 = 0;

    rot_mat.GetComponents(r00, r01, r02, r10, r11, r12, r20, r21, r22);

    long double aX = 0;
    long double aY = 0;
    long double aZ = 0;
    static long double const pi = 3.14159265359;
    // This is a correct decomposition for the given rotation order: YXZ, i.e. initial Z-rotation, followed by X and
    // ultimately Y rotation. In the case of a gimbal lock, the angle around the Z axis is (arbitrarily) set to 0.
    if(r12 < 1) {
        if(r12 > -1) {
            aX = std::asin(-r12);
            aY = std::atan2(r02, r22);
            aZ = std::atan2(r10, r11);
        } else /* r12 == -1 */ {
            aX = pi / 2;
            aY = -std::atan2(-r01, r00);
            aZ = 0;
        }
    } else /* r12 == 1 */ {
        aX = -pi / 2;
        aY = std::atan2(-r01, r00);
        aZ = 0;
    }
    std::array<long double, 3> vec = {{aX, aY, aZ}};
    return vec;
}

LCIOWriterModule::LCIOWriterModule(Configuration& config, Messenger* messenger, GeometryManager* geo)
    : SequentialModule(config), messenger_(messenger), geo_mgr_(geo) {
    // Enable multithreading of this module if multithreading is enabled
    allow_multithreading();

    // Set configuration defaults:
    config_.setDefault("file_name", "output.slcio");
    config_.setDefault("geometry_file", "allpix_squared_gear.xml");
    config_.setDefault("pixel_type", 2);
    config_.setDefault("detector_name", "EUTelescope");
    config_.setDefault("dump_mc_truth", false);

    pixel_type_ = config_.get<int>("pixel_type");
    detector_name_ = config_.get<std::string>("detector_name");
    dump_mc_truth_ = config_.get<bool>("dump_mc_truth");
    // There are two ways to configure this module - either by providing a "output_collection_name" or a
    // "detector_assignment". Throws an error if both are provided and defaults back to "output_collection_name" if none are
    // provided
    auto has_short_config = config_.has("output_collection_name");
    auto has_long_config = config_.has("detector_assignment");

    // Bind pixel hits message
    messenger_->bindMulti<PixelHitMessage>(this, MsgFlags::REQUIRED);
    messenger_->bindMulti<MCParticleMessage>(this, MsgFlags::REQUIRED);
    if(dump_mc_truth_) {
        messenger_->bindSingle<MCTrackMessage>(this, MsgFlags::REQUIRED);
    }

    if(has_short_config && has_long_config) {
        throw InvalidCombinationError(config_,
                                      {"output_collection_name", "detector_assignment"},
                                      "Provide either a \"output_collection_name\" or a \"detector_assignment\" "
                                      "configuration parameter. They are mutually exclusive!");
    } else if(!has_short_config && !has_long_config) {
        config_.setDefault("output_collection_name", "zsdata_m26");
        has_short_config = true;
    }

    auto detectors = geo_mgr_->getDetectors();

    if(has_short_config) {
        auto out_col_name = config_.get<std::string>("output_collection_name");
        unsigned sensor_id = 0;
        for(auto const& det : detectors) {
            auto const& det_name = det->getName();
            collections_to_detectors_map_[out_col_name].emplace_back(det_name);
            detector_names_to_id_[det_name] = sensor_id++;
        }
    } else {

        // The 'setup' parameter has a string matrix with three elements per row
        // ["detector_name", "output_collection", "sensor_id"] where the detector_name
        // must correspond to the detector name in the geometry file, the output_collection
        // will be the name of the lcio output collection (multiple detectors can write to
        // the same collection), and sensor_id has to be a unique id which the data corresponding
        // to this sensor will carry
        auto setup = config.getMatrix<std::string>("detector_assignment");
        auto assigned_ids = std::vector<unsigned>{};

        for(auto const& setup_entry : setup) {
            if(setup_entry.size() == 3) {
                auto const& det_name = setup_entry[0];
                auto const& col_name = setup_entry[1];
                auto const& sensor_id_str = setup_entry[2];
                // This map will help determine how many setup we will create (keys) and what
                // detectors write into that collection (values)
                collections_to_detectors_map_[col_name].emplace_back(det_name);

                unsigned sensor_id = 0;
                try {
                    auto sensor_id_unchecked = std::stoi(sensor_id_str);
                    if(sensor_id_unchecked >= 0 && sensor_id_unchecked <= 127) {
                        sensor_id = static_cast<unsigned>(sensor_id_unchecked);
                    } else {
                        throw InvalidValueError(config_,
                                                "detector_assignment",
                                                "The sensor id \"" + std::to_string(sensor_id_unchecked) +
                                                    "\" which was provided for detector \"" + det_name +
                                                    "\" must be positive and less than or equal to 127 (7 bit)");
                    }
                } catch(const std::invalid_argument&) {
                    throw InvalidValueError(config_,
                                            "detector_assignment",
                                            "The sensor id \"" + sensor_id_str +          // NOLINT
                                                "\" which was provided for detector \"" + // NOLINT
                                                det_name +                                // NOLINT
                                                "\" is not a valid integer");             // NOLINT
                }

                if(std::find(assigned_ids.begin(), assigned_ids.end(), sensor_id) == assigned_ids.end()) {
                    assigned_ids.emplace_back(sensor_id);
                    // This map will translate the internally used detector name to the sensor id
                    detector_names_to_id_[det_name] = sensor_id;
                } else {
                    throw InvalidValueError(config_,
                                            "detector_assignment",
                                            "Trying to assign sensor id \"" + std::to_string(sensor_id) +
                                                "\" to detector \"" + det_name + "\", this id is already assigned");
                }

            } else {
                auto error = std::string("The entry: [");
                for(auto const& value : setup_entry) {
                    error.append("\"" + value + "\", ");
                }
                error.pop_back();
                error.pop_back();
                error.append("] should have three entries in following order: [\"detector_name\", \"output_collection\", "
                             "\"sensor_id\"]");
                throw InvalidValueError(config_, "detector_assignment", error);
            }
        }

        // Cross check the detector geometry against the configuration file
        if(setup.size() != detectors.size()) {
            throw InvalidValueError(config_,
                                    "detector_assignment",
                                    "In the configuration file " + std::to_string(setup.size()) +
                                        " detectors are specified, in the geometry " + std::to_string(detectors.size()) +
                                        ", this is a mismatch");
        }
    }
    //
    for(auto const& col_dets_pair : collections_to_detectors_map_) {
        collection_names_vector_.emplace_back(col_dets_pair.first);
        LOG(DEBUG) << "Registered output collection \"" << col_dets_pair.first << "\" for sensors: ";
        for(auto const& det_name : col_dets_pair.second) {
            LOG(DEBUG) << det_name << " ";
            auto det_id = detector_names_to_id_[det_name];
            detector_ids_to_colllection_index_[det_id] = collection_names_vector_.size() - 1;
        }
    }

    for(const auto& det : detectors) {
        auto const& det_name = det->getName();
        auto it = detector_names_to_id_.find(det_name);
        if(it != detector_names_to_id_.end()) {
            LOG(DEBUG) << det_name << " has ID " << detector_names_to_id_[det_name];
        } else {
            throw InvalidValueError(config_,
                                    "detector_assignment",
                                    "Detector \"" + det_name +
                                        "\" is specified in the geometry file, but not provided in the configuration file");
        }
    }
}

void LCIOWriterModule::initialize() {
    // Create the output GEAR file for the detector geometry
    geometry_file_name_ = createOutputFile(config_.get<std::string>("geometry_file"), "xml");
    // Open LCIO file and write run header
    lcio_file_name_ = createOutputFile(config_.get<std::string>("file_name"), "slcio");
    lcWriter_ = std::shared_ptr<IO::LCWriter>(LCFactory::getInstance()->createLCWriter());
    lcWriter_->open(lcio_file_name_, LCIO::WRITE_NEW);
    auto run = std::make_unique<LCRunHeaderImpl>();
    run->setRunNumber(1);
    run->setDetectorName(detector_name_);
    lcWriter_->writeRunHeader(run.get());
}

void LCIOWriterModule::run(Event* event) {
    auto pixel_messages = messenger_->fetchMultiMessage<PixelHitMessage>(this, event);

    auto evt = std::make_unique<LCEventImpl>(); // create the event
    evt->setRunNumber(1);
    evt->setEventNumber(static_cast<int>(event->number)); // set the event attributes
    evt->parameters().setValue("EventType", 2);

    auto output_col_vec = std::vector<LCCollectionVec*>();
    auto output_col_encoder_vec = std::vector<std::unique_ptr<CellIDEncoder<TrackerDataImpl>>>();
    // Prepare dynamic output setup and their CellIDEncoders which are defined by the user's config
    for(size_t i = 0; i < collection_names_vector_.size(); ++i) {
        output_col_vec.emplace_back(new LCCollectionVec(LCIO::TRACKERDATA));
        output_col_encoder_vec.emplace_back(
            std::make_unique<CellIDEncoder<TrackerDataImpl>>(eutelescope::gTrackerDataEncoding, output_col_vec.back()));
    }

    LCCollectionVec* mc_cluster_vec = nullptr;
    LCCollectionVec* mc_cluster_raw_vec = nullptr;
    LCCollectionVec* mc_hit_vec = nullptr;
    LCCollectionVec* mc_track_vec = nullptr;

    std::unique_ptr<CellIDEncoder<TrackerDataImpl>> mc_cluster_raw_encoder = nullptr;
    std::unique_ptr<CellIDEncoder<TrackerPulseImpl>> mc_cluster_encoder = nullptr;
    std::unique_ptr<CellIDEncoder<TrackerHitImpl>> mc_hit_encoder = nullptr;

    // The detector id is only attached to the message, not the MCParticle, thus we store it here
    auto mcp_to_det_id = std::map<MCParticle const*, unsigned>{};
    // Multiple pixel hits can be assigned to a single MCParticle, here we store them in a LCIO 'float vector' to create the
    // Monte Carlo truth cluster
    auto mcp_to_pixel_data_vec = std::map<MCParticle const*, std::vector<std::vector<float>>>{};

    if(dump_mc_truth_ == true) {
        // Prepare static Monte-Carlo output setup and their CellIDEncoders which are the same every time
        mc_cluster_vec = new LCCollectionVec(LCIO::TRACKERPULSE);
        mc_cluster_raw_vec = new LCCollectionVec(LCIO::TRACKERDATA);
        mc_hit_vec = new LCCollectionVec(LCIO::TRACKERHIT);
        mc_track_vec = new LCCollectionVec(LCIO::TRACK);

        mc_cluster_raw_encoder =
            std::make_unique<CellIDEncoder<TrackerDataImpl>>(eutelescope::gTrackerDataEncoding, mc_cluster_raw_vec);
        mc_cluster_encoder =
            std::make_unique<CellIDEncoder<TrackerPulseImpl>>(eutelescope::gTrackerPulseEncoding, mc_cluster_vec);
        mc_hit_encoder = std::make_unique<CellIDEncoder<TrackerHitImpl>>(eutelescope::gTrackerHitEncoding, mc_hit_vec);
    }

    // In LCIO the 'charge vector' is a vector of floats which correspond to hit pixels, depending on the pixel
    // type in EUTelescope the number of entries per pixel varies
    std::map<unsigned, std::vector<float>> charges;
    for(auto const& det : detector_names_to_id_) {
        charges[det.second] = std::vector<float>{};
    }

    // Receive all pixel messages, fill charge vectors
    for(const auto& hit_msg : pixel_messages) {
        LOG(DEBUG) << hit_msg->getDetector()->getName();
        for(const auto& hitdata : hit_msg->getData()) {

            auto this_hit_charge_vec = std::vector<float>{};

            LOG(DEBUG) << "X: " << hitdata.getPixel().getIndex().x() << ", Y:" << hitdata.getPixel().getIndex().y()
                       << ", Signal: " << hitdata.getSignal();

            unsigned det_id = detector_names_to_id_[hit_msg->getDetector()->getName()];

            switch(pixel_type_) {
            case 1:                                                                               // EUTelSimpleSparsePixel
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                charges[det_id].push_back(static_cast<float>(hitdata.getSignal()));               // signal
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getSignal()));               // signal
                break;
            case 2:  // EUTelGenericSparsePixel
            default: // EUTelGenericSparsePixel is default
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                charges[det_id].push_back(static_cast<float>(hitdata.getSignal()));               // signal
                charges[det_id].push_back(0.0);
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getSignal()));               // signal
                this_hit_charge_vec.push_back(0.0);                                                   // time
                break;
            case 5:                                                                               // EUTelTimepix3SparsePixel
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                charges[det_id].push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                charges[det_id].push_back(static_cast<float>(hitdata.getSignal()));               // signal
                charges[det_id].push_back(0.0);                                                   // time
                charges[det_id].push_back(0.0);                                                   // time
                charges[det_id].push_back(0.0);                                                   // time
                charges[det_id].push_back(0.0);                                                   // time
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().x())); // x
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getPixel().getIndex().y())); // y
                this_hit_charge_vec.push_back(static_cast<float>(hitdata.getSignal()));               // signal
                this_hit_charge_vec.push_back(0.0);                                                   // time
                this_hit_charge_vec.push_back(0.0);                                                   // time
                this_hit_charge_vec.push_back(0.0);                                                   // time
                this_hit_charge_vec.push_back(0.0);                                                   // time
                break;
            }

            for(auto const& mcp : hitdata.getMCParticles()) {
                mcp_to_det_id[mcp] = det_id;
                mcp_to_pixel_data_vec[mcp].emplace_back(this_hit_charge_vec);
            }
        }
    }

    // Every track will be linked to at least one (typically multiple) MCParticles and thus TrackerData objects
    auto mctrk_to_hit_data_vec = std::map<MCTrack const*, std::vector<TrackerHitImpl*>>{};

    // A MCParticle will be reflected by an LCIO hit and cluster - the hit is stored in a TrackerHit, the cluster in
    // a TrackerPulse linked to a TrackerData object
    if(dump_mc_truth_ == true) {
        for(auto& mcp_pixel_data_vec_pair : mcp_to_pixel_data_vec) {
            auto* mc_tracker_data = new TrackerDataImpl();
            auto* mc_tracker_pulse = new TrackerPulseImpl();
            auto* mc_tracker_hit = new TrackerHitImpl();

            const auto& mc_particle = mcp_pixel_data_vec_pair.first;

            // Every detected pixel hit which had charge contribution from this MCParticle will be added to the cluster
            std::vector<float> truth_cluster_charge_vec;
            for(auto const& pixel_hit_charge_vec : mcp_pixel_data_vec_pair.second) {
                truth_cluster_charge_vec.insert(
                    std::end(truth_cluster_charge_vec), std::begin(pixel_hit_charge_vec), std::end(pixel_hit_charge_vec));
            }

            mc_tracker_data->setChargeValues(truth_cluster_charge_vec);
            (*mc_cluster_raw_encoder)["sensorID"] = mcp_to_det_id[mc_particle];
            (*mc_cluster_raw_encoder)["sparsePixelType"] = pixel_type_;
            mc_cluster_raw_encoder->setCellID(mc_tracker_data);
            mc_cluster_raw_vec->push_back(mc_tracker_data);

            mc_tracker_pulse->setTrackerData(mc_tracker_data);
            (*mc_cluster_encoder)["sensorID"] = mcp_to_det_id[mc_particle];
            (*mc_cluster_encoder)["type"] = 1; // corresponds to kEUTelGenericSparseClusterImpl
            mc_cluster_encoder->setCellID(mc_tracker_pulse);
            mc_cluster_vec->push_back(mc_tracker_pulse);

            // we take the centre of the MCParticle to be the global z-position
            auto const& hit_start_pos = mc_particle->getGlobalStartPoint();
            auto const& hit_end_pos = mc_particle->getGlobalEndPoint();
            auto pos_arr = std::array<double, 3>{{0.5 * (hit_start_pos.x() + hit_end_pos.x()),
                                                  0.5 * (hit_start_pos.y() + hit_end_pos.y()),
                                                  0.5 * (hit_start_pos.z() + hit_end_pos.z())}};
            mc_tracker_hit->setPosition(pos_arr.data());
            mc_tracker_hit->setType(1); // corresponds to kEUTelGenericSparseClusterImpl
            (*mc_hit_encoder)["sensorID"] = mcp_to_det_id[mc_particle];

            int hit_properties = eutelescope::HitProperties::kHitInGlobalCoord + eutelescope::HitProperties::kSimulatedHit;
            if(mc_particle->getTrack()->getParent() != nullptr) {
                hit_properties += eutelescope::HitProperties::kDeltaHit;
            }
            (*mc_hit_encoder)["properties"] = hit_properties;

            mc_hit_encoder->setCellID(mc_tracker_hit);
            mc_tracker_hit->rawHits() = std::vector<LCObject*>{mc_tracker_data};
            mc_hit_vec->push_back(mc_tracker_hit);
            mctrk_to_hit_data_vec[mc_particle->getTrack()].emplace_back(mc_tracker_hit);
        }
    }
    // Fill hitvector with event data
    for(auto const& det_id_name_pair : detector_names_to_id_) {
        auto det_id = det_id_name_pair.second;
        auto* hit = new TrackerDataImpl();
        hit->setChargeValues(charges[det_id]);
        auto col_index = detector_ids_to_colllection_index_[det_id];
        (*output_col_encoder_vec[col_index])["sensorID"] = det_id;
        (*output_col_encoder_vec[col_index])["sparsePixelType"] = pixel_type_;
        output_col_encoder_vec[col_index]->setCellID(hit);
        output_col_vec[col_index]->push_back(hit);
    }

    if(dump_mc_truth_ == true) {
        LCFlagImpl flag(mc_track_vec->getFlag());
        flag.setBit(LCIO::TRBIT_HITS);
        mc_track_vec->setFlag(flag.getFlag());
        for(auto& pair : mctrk_to_hit_data_vec) {
            auto* track = new TrackImpl();
            for(auto& hit : pair.second) {
                // std::cout << "Got hit: " << hit << " z-pos: " << hit->getPosition()[2] << '\n';
                track->addHit(hit);
            }
            mc_track_vec->push_back(track);
        }

        // Add collection to event and write event to LCIO file
        evt->addCollection(mc_track_vec, "mc_track");
        evt->addCollection(mc_hit_vec, "mc_hit");
        evt->addCollection(mc_cluster_raw_vec, "mc_raw_cluster");
        evt->addCollection(mc_cluster_vec, "mc_cluster");
    }
    for(size_t i = 0; i < collection_names_vector_.size(); i++) {
        evt->addCollection(output_col_vec[i], collection_names_vector_[i]);
    }

    lcWriter_->writeEvent(evt.get()); // write the event to the file
    write_cnt_++;
}

void LCIOWriterModule::finalize() {
    lcWriter_->close();
    // Print statistics
    LOG(STATUS) << "Wrote " << write_cnt_ << " events to file:" << std::endl << lcio_file_name_;

    // Write geometry:
    std::ofstream geometry_file;
    if(!geometry_file_name_.empty()) {
        geometry_file.open(geometry_file_name_, std::ios_base::out | std::ios_base::trunc);
        if(!geometry_file.good()) {
            throw ModuleError("Cannot write to GEAR geometry file");
        }

        auto detectors = geo_mgr_->getDetectors();
        geometry_file << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
                      << "<!-- ?xml-stylesheet type=\"text/xsl\" href=\"https://cern.ch/allpix-squared/\"? -->" << std::endl
                      << "<gear>" << std::endl;

        geometry_file << "  <global detectorName=\"" << detector_name_ << "\"/>" << std::endl;
        if(geo_mgr_->getMagneticFieldType() == MagneticFieldType::CONSTANT) {
            ROOT::Math::XYZVector b_field = geo_mgr_->getMagneticField(ROOT::Math::XYZPoint(0., 0., 0.));
            geometry_file << "  <BField type=\"ConstantBField\" x=\"" << Units::convert(b_field.x(), "T") << "\" y=\""
                          << Units::convert(b_field.y(), "T") << "\" z=\"" << Units::convert(b_field.z(), "T") << "\"/>"
                          << std::endl;
        } else if(geo_mgr_->getMagneticFieldType() == MagneticFieldType::NONE) {
            geometry_file << "  <BField type=\"ConstantBField\" x=\"0.0\" y=\"0.0\" z=\"0.0\"/>" << std::endl;
        } else {
            LOG(WARNING) << "Field type not handled by GEAR geometry. Writing null magnetic field instead.";
            geometry_file << "  <BField type=\"ConstantBField\" x=\"0.0\" y=\"0.0\" z=\"0.0\"/>" << std::endl;
        }

        geometry_file << "  <detectors>" << std::endl;
        geometry_file << "    <detector name=\"SiPlanes\" geartype=\"SiPlanesParameters\">" << std::endl;
        geometry_file << "      <siplanesType type=\"TelescopeWithoutDUT\"/>" << std::endl;
        geometry_file << "      <siplanesNumber number=\"" << detectors.size() << "\"/>" << std::endl;
        geometry_file << "      <siplanesID ID=\"" << 0 << "\"/>" << std::endl;
        geometry_file << "      <layers>" << std::endl;

        for(auto& detector : detectors) {
            // Write header for the layer:
            geometry_file << "      <!-- Allpix Squared Detector: " << detector->getName()
                          << " - type: " << detector->getType() << " -->" << std::endl;
            geometry_file << "        <layer>" << std::endl;

            auto position = detector->getPosition();

            auto model = detector->getModel();
            auto npixels = model->getNPixels();
            auto pitch = model->getPixelSize();

            auto total_size = model->getSize();
            auto sensitive_size = model->getSensorSize();

            // Write ladder
            geometry_file << "          <ladder ID=\"" << detector_names_to_id_[detector->getName()] << "\"" << std::endl;
            geometry_file << "            positionX=\"" << Units::convert(position.x(), "mm") << "\"\tpositionY=\""
                          << Units::convert(position.y(), "mm") << "\"\tpositionZ=\"" << Units::convert(position.z(), "mm")
                          << "\"" << std::endl;

            auto angles = getRotationAnglesFromMatrix(detector->getOrientation());

            geometry_file << "            rotationZY=\"" << Units::convert(-angles[0], "deg") << "\"     rotationZX=\""
                          << Units::convert(-angles[1], "deg") << "\"   rotationXY=\"" << Units::convert(-angles[2], "deg")
                          << "\"" << std::endl;
            geometry_file << "            sizeX=\"" << Units::convert(total_size.x(), "mm") << "\"\tsizeY=\""
                          << Units::convert(total_size.y(), "mm") << "\"\tthickness=\""
                          << Units::convert(total_size.z(), "mm") << "\"" << std::endl;
            geometry_file << "            radLength=\"93.65\"" << std::endl;
            geometry_file << "            />" << std::endl;

            // Write sensitive
            geometry_file << "          <sensitive ID=\"" << detector_names_to_id_[detector->getName()] << "\"" << std::endl;
            geometry_file << "            positionX=\"" << Units::convert(position.x(), "mm") << "\"\tpositionY=\""
                          << Units::convert(position.y(), "mm") << "\"\tpositionZ=\"" << Units::convert(position.z(), "mm")
                          << "\"" << std::endl;
            geometry_file << "            sizeX=\"" << Units::convert(npixels.x() * pitch.x(), "mm") << "\"\tsizeY=\""
                          << Units::convert(npixels.y() * pitch.y(), "mm") << "\"\tthickness=\""
                          << Units::convert(sensitive_size.z(), "mm") << "\"" << std::endl;
            geometry_file << "            npixelX=\"" << npixels.x() << "\"\tnpixelY=\"" << npixels.y() << "\"" << std::endl;
            geometry_file << "            pitchX=\"" << Units::convert(pitch.x(), "mm") << "\"\tpitchY=\""
                          << Units::convert(pitch.y(), "mm") << "\"\tresolution=\""
                          << Units::convert(pitch.x() / std::sqrt(12), "mm") << "\"" << std::endl;
            geometry_file << "            rotation1=\"1.0\"\trotation2=\"0.0\"" << std::endl;
            geometry_file << "            rotation3=\"0.0\"\trotation4=\"1.0\"" << std::endl;
            geometry_file << "            radLength=\"93.65\"" << std::endl;
            geometry_file << "            />" << std::endl;

            // End the layer:
            geometry_file << "        </layer>" << std::endl;
        }

        // Close XML tree:
        geometry_file << "      </layers>" << std::endl
                      << "    </detector>" << std::endl
                      << "  </detectors>" << std::endl
                      << "</gear>" << std::endl;

        LOG(STATUS) << "Wrote GEAR geometry to file:" << std::endl << geometry_file_name_;
    }
}

Updated on 2024-12-13 at 08:31:37 +0000