src/tools/line_graphs.h
Utility to plot line graph diagrams of charge carrier drift paths. More…
Namespaces
Name |
---|
allpix Helper class to hold support layers for a detector model. |
Classes
Name | |
---|---|
class | allpix::LineGraph |
Detailed Description
Utility to plot line graph diagrams of charge carrier drift paths.
Copyright: Copyright (c) 2022-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
Source code
#ifndef ALLPIX_LINE_GRAPHS_H
#define ALLPIX_LINE_GRAPHS_H
#include "core/module/Module.hpp"
#include "objects/PropagatedCharge.hpp"
#include <Math/Point3D.h>
#include <TCanvas.h>
#include <TFile.h>
#include <TH2F.h>
#include <TH3F.h>
#include <TPaveText.h>
#include <TPolyLine3D.h>
#include <TPolyMarker3D.h>
#include <TStyle.h>
namespace allpix {
class LineGraph {
public:
using OutputPlotPoints = std::vector<
std::pair<std::tuple<double, unsigned int, CarrierType, CarrierState>, std::vector<ROOT::Math::XYZPoint>>>;
static void Create(uint64_t event_num, // NOLINT
Module* module,
const Configuration& config,
const OutputPlotPoints& output_plot_points,
CarrierState plotting_state) {
auto model = module->getDetector()->getModel();
auto title = (plotting_state == CarrierState::UNKNOWN ? "all" : allpix::to_string(plotting_state));
LOG(TRACE) << "Writing line graph for " << title << " charge carriers";
auto [minX, maxX, minY, maxY, scale_x, scale_y, max_charge, total_charge, tot_point_cnt, start_time] =
get_plot_settings(model, config, output_plot_points);
// Use a histogram to create the underlying frame
auto* histogram_frame =
new TH3F(("frame_" + module->getUniqueName() + "_" + std::to_string(event_num) + "_" + title).c_str(),
"",
10,
minX,
maxX,
10,
minY,
maxY,
10,
model->getSensorCenter().z() - model->getSensorSize().z() / 2.0,
model->getSensorCenter().z() + model->getSensorSize().z() / 2.0);
histogram_frame->SetDirectory(module->getROOTDirectory());
// Create the canvas for the line plot and set orientation
auto canvas = std::make_unique<TCanvas>(("line_plot_" + std::to_string(event_num) + "_" + title).c_str(),
("Propagation of charge for event " + std::to_string(event_num)).c_str(),
1280,
1024);
canvas->cd();
canvas->SetTheta(config.get<float>("output_plots_theta") * 180.0f / ROOT::Math::Pi());
canvas->SetPhi(config.get<float>("output_plots_phi") * 180.0f / ROOT::Math::Pi());
// Draw the frame on the canvas
histogram_frame->GetXaxis()->SetTitle(
(std::string("x ") + (config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
histogram_frame->GetYaxis()->SetTitle(
(std::string("y ") + (config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
histogram_frame->GetZaxis()->SetTitle("z (mm)");
histogram_frame->Draw();
// Loop over all point sets created during propagation
// The vector of unique_pointers is required in order not to delete the objects before the canvas is drawn.
std::vector<std::unique_ptr<TPolyLine3D>> lines;
short current_color = 1;
for(const auto& [deposit, points] : output_plot_points) {
// Check if we should plot this point:
if(plotting_state != CarrierState::UNKNOWN && plotting_state != std::get<3>(deposit)) {
continue;
}
auto line = std::make_unique<TPolyLine3D>();
for(const auto& point : points) {
line->SetNextPoint(point.x() / scale_x, point.y() / scale_y, point.z());
}
// Plot all lines with at least three points with different color
if(line->GetN() >= 2) {
EColor plot_color = (std::get<2>(deposit) == CarrierType::ELECTRON ? EColor::kAzure : EColor::kOrange);
current_color = static_cast<short int>(plot_color - 9 + (static_cast<int>(current_color) + 1) % 19);
line->SetLineColor(current_color);
line->Draw("same");
}
lines.push_back(std::move(line));
}
// Draw and write canvas to module output file, then clear the stored lines
canvas->Draw();
module->getROOTDirectory()->WriteTObject(canvas.get());
}
static void Animate(uint64_t event_num, // NOLINT
Module* module,
const Configuration& config,
const OutputPlotPoints& output_plot_points) {
LOG(TRACE) << "Writing animation for all charge carriers";
auto model = module->getDetector()->getModel();
auto [minX, maxX, minY, maxY, scale_x, scale_y, max_charge, total_charge, tot_point_cnt, start_time] =
get_plot_settings(model, config, output_plot_points);
// Use a histogram to create the underlying frame
auto* histogram_frame =
new TH3F(("frame_" + module->getUniqueName() + "_" + std::to_string(event_num) + "_all").c_str(),
"",
10,
minX,
maxX,
10,
minY,
maxY,
10,
model->getSensorCenter().z() - model->getSensorSize().z() / 2.0,
model->getSensorCenter().z() + model->getSensorSize().z() / 2.0);
histogram_frame->SetDirectory(module->getROOTDirectory());
// Create canvas for GIF animition of process
auto canvas = std::make_unique<TCanvas>(("animation_" + std::to_string(event_num) + "_all").c_str(),
("Propagation of charge for event " + std::to_string(event_num)).c_str(),
1280,
1024);
canvas->cd();
// Change axis labels if close to zero or PI as they behave different here
if(std::fabs(config.get<double>("output_plots_theta") / (ROOT::Math::Pi() / 2.0) -
std::round(config.get<double>("output_plots_theta") / (ROOT::Math::Pi() / 2.0))) < 1e-6 ||
std::fabs(config.get<double>("output_plots_phi") / (ROOT::Math::Pi() / 2.0) -
std::round(config.get<double>("output_plots_phi") / (ROOT::Math::Pi() / 2.0))) < 1e-6) {
histogram_frame->GetXaxis()->SetLabelOffset(-0.1f);
histogram_frame->GetYaxis()->SetLabelOffset(-0.075f);
} else {
histogram_frame->GetXaxis()->SetTitleOffset(2.0f);
histogram_frame->GetYaxis()->SetTitleOffset(2.0f);
}
// Draw frame on canvas
histogram_frame->Draw();
// Create the contour histogram
std::vector<std::string> file_name_contour{};
std::vector<TH2F*> histogram_contour{};
file_name_contour.push_back(module->createOutputFile("contourX" + std::to_string(event_num) + "_all.gif"));
histogram_contour.push_back(
new TH2F(("contourX_" + module->getUniqueName() + "_" + std::to_string(event_num)).c_str(),
"",
100,
minY,
maxY,
100,
model->getSensorCenter().z() - model->getSensorSize().z() / 2.0,
model->getSensorCenter().z() + model->getSensorSize().z() / 2.0));
histogram_contour.back()->SetDirectory(module->getROOTDirectory());
file_name_contour.push_back(module->createOutputFile("contourY" + std::to_string(event_num) + ".gif"));
histogram_contour.push_back(
new TH2F(("contourY_" + module->getUniqueName() + "_" + std::to_string(event_num)).c_str(),
"",
100,
minX,
maxX,
100,
model->getSensorCenter().z() - model->getSensorSize().z() / 2.0,
model->getSensorCenter().z() + model->getSensorSize().z() / 2.0));
histogram_contour.back()->SetDirectory(module->getROOTDirectory());
file_name_contour.push_back(module->createOutputFile("contourZ" + std::to_string(event_num) + ".gif"));
histogram_contour.push_back(
new TH2F(("contourZ_" + module->getUniqueName() + "_" + std::to_string(event_num)).c_str(),
"",
100,
minX,
maxX,
100,
minY,
maxY));
histogram_contour.back()->SetDirectory(module->getROOTDirectory());
// Create file and disable statistics for histogram
std::string file_name_anim = module->createOutputFile("animation" + std::to_string(event_num) + ".gif");
for(size_t i = 0; i < 3; ++i) {
histogram_contour[i]->SetStats(false);
}
// Remove temporary created files
auto ret = remove(file_name_anim.c_str());
for(size_t i = 0; i < 3; ++i) {
ret |= remove(file_name_contour[i].c_str());
}
if(ret != 0) {
throw ModuleError("Could not delete temporary animation files.");
}
// Create color table
TColor* colors[80]; // NOLINT
for(int i = 20; i < 100; ++i) {
auto color_idx = TColor::GetFreeColorIndex();
colors[i - 20] = new TColor(color_idx,
static_cast<float>(i) / 100.0f - 0.2f,
static_cast<float>(i) / 100.0f - 0.2f,
static_cast<float>(i) / 100.0f - 0.2f);
}
// Create animation of moving charges
auto animation_time = static_cast<unsigned int>(
std::lround((Units::convert(config.get<long double>("output_plots_step"), "ms") / 10.0) *
config.get<long double>("output_animations_time_scaling", 1e9)));
unsigned long plot_idx = 0;
unsigned int point_cnt = 0;
LOG_PROGRESS(INFO, module->getUniqueName() + "_OUTPUT_PLOTS")
<< "Written 0 of " << tot_point_cnt << " points for animation";
while(point_cnt < tot_point_cnt) {
std::vector<std::unique_ptr<TPolyMarker3D>> markers{};
unsigned long min_idx_diff = std::numeric_limits<unsigned long>::max();
// Reset the canvas
canvas->Clear();
canvas->SetTheta(config.get<float>("output_plots_theta") * 180.0f / ROOT::Math::Pi());
canvas->SetPhi(config.get<float>("output_plots_phi") * 180.0f / ROOT::Math::Pi());
canvas->Draw();
// Reset the histogram frame
histogram_frame->SetTitle("Charge propagation in sensor");
histogram_frame->GetXaxis()->SetTitle(
(std::string("x ") + (config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
histogram_frame->GetYaxis()->SetTitle(
(std::string("y ") + (config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
histogram_frame->GetZaxis()->SetTitle("z (mm)");
histogram_frame->Draw();
auto text = std::make_unique<TPaveText>(-0.75, -0.75, -0.60, -0.65);
auto time_ns = Units::convert(plot_idx * config.get<long double>("output_plots_step"), "ns");
std::stringstream sstr;
sstr << std::fixed << std::setprecision(2) << time_ns << "ns";
auto time_str = std::string(8 - sstr.str().size(), ' ');
time_str += sstr.str();
text->AddText(time_str.c_str());
text->Draw();
// Plot all the required points
for(const auto& [deposit, points] : output_plot_points) {
const auto& [time, charge, type, state] = deposit;
auto diff = static_cast<unsigned long>(
std::lround((time - start_time) / config.get<long double>("output_plots_step")));
if(plot_idx < diff) {
min_idx_diff = std::min(min_idx_diff, diff - plot_idx);
continue;
}
auto idx = plot_idx - diff;
if(idx >= points.size()) {
continue;
}
min_idx_diff = 0;
auto marker = std::make_unique<TPolyMarker3D>();
marker->SetMarkerStyle(kFullCircle);
marker->SetMarkerSize(
static_cast<float>(charge * config.get<double>("output_animations_marker_size", 1)) /
static_cast<float>(max_charge));
auto initial_z_perc = static_cast<int>(
((points[0].z() + model->getSensorSize().z() / 2.0) / model->getSensorSize().z()) * 80);
initial_z_perc = std::max(std::min(79, initial_z_perc), 0);
if(config.get<bool>("output_animations_color_markers")) {
marker->SetMarkerColor(static_cast<Color_t>(colors[initial_z_perc]->GetNumber()));
}
marker->SetNextPoint(points[idx].x() / scale_x, points[idx].y() / scale_y, points[idx].z());
marker->Draw();
markers.push_back(std::move(marker));
histogram_contour[0]->Fill(points[idx].y() / scale_y, points[idx].z(), charge);
histogram_contour[1]->Fill(points[idx].x() / scale_x, points[idx].z(), charge);
histogram_contour[2]->Fill(points[idx].x() / scale_x, points[idx].y() / scale_y, charge);
++point_cnt;
}
// Create a step in the animation
if(min_idx_diff != 0) {
canvas->Print((file_name_anim + "+100").c_str());
plot_idx += min_idx_diff;
} else {
// print animation
if(point_cnt < tot_point_cnt - 1) {
canvas->Print((file_name_anim + "+" + std::to_string(animation_time)).c_str());
} else {
canvas->Print((file_name_anim + "++100").c_str());
}
// Draw and print contour histograms
for(size_t i = 0; i < 3; ++i) {
canvas->Clear();
canvas->SetTitle((std::string("Contour of charge propagation projected on the ") +
static_cast<char>('X' + i) + "-axis")
.c_str());
switch(i) {
case 0 /* x */:
histogram_contour[i]->GetXaxis()->SetTitle(
(std::string("y ") +
(config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
.c_str());
histogram_contour[i]->GetYaxis()->SetTitle("z (mm)");
break;
case 1 /* y */:
histogram_contour[i]->GetXaxis()->SetTitle(
(std::string("x ") +
(config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
.c_str());
histogram_contour[i]->GetYaxis()->SetTitle("z (mm)");
break;
case 2 /* z */:
histogram_contour[i]->GetXaxis()->SetTitle(
(std::string("x ") +
(config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
.c_str());
histogram_contour[i]->GetYaxis()->SetTitle(
(std::string("y ") +
(config.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
.c_str());
break;
default:;
}
histogram_contour[i]->SetMinimum(1);
histogram_contour[i]->SetMaximum(total_charge /
config.get<double>("output_animations_contour_max_scaling", 10));
histogram_contour[i]->Draw("CONTZ 0");
if(point_cnt < tot_point_cnt - 1) {
canvas->Print((file_name_contour[i] + "+" + std::to_string(animation_time)).c_str());
} else {
canvas->Print((file_name_contour[i] + "++100").c_str());
}
histogram_contour[i]->Reset();
}
++plot_idx;
}
markers.clear();
LOG_PROGRESS(INFO, module->getUniqueName() + "_OUTPUT_PLOTS")
<< "Written " << point_cnt << " of " << tot_point_cnt << " points for animation";
}
}
private:
static std::tuple<double, double, double, double, double, double, double, double, unsigned long, double>
get_plot_settings(const std::shared_ptr<DetectorModel>& model,
const Configuration& config,
const OutputPlotPoints& output_plot_points) {
// Convert to pixel units if necessary
double scale_x = (config.get<bool>("output_plots_use_pixel_units") ? model->getPixelSize().x() : 1);
double scale_y = (config.get<bool>("output_plots_use_pixel_units") ? model->getPixelSize().y() : 1);
// Calculate the axis limits
double minX = FLT_MAX, maxX = FLT_MIN;
double minY = FLT_MAX, maxY = FLT_MIN;
unsigned long tot_point_cnt = 0;
double start_time = std::numeric_limits<double>::max();
unsigned int total_charge = 0;
unsigned int max_charge = 0;
for(const auto& [deposit, points] : output_plot_points) {
for(const auto& point : points) {
minX = std::min(minX, point.x() / scale_x);
maxX = std::max(maxX, point.x() / scale_x);
minY = std::min(minY, point.y() / scale_y);
maxY = std::max(maxY, point.y() / scale_y);
}
const auto& [time, charge, type, state] = deposit;
start_time = std::min(start_time, time);
total_charge += charge;
max_charge = std::max(max_charge, charge);
tot_point_cnt += points.size();
}
// Compute frame axis sizes if equal scaling is requested
if(config.get<bool>("output_plots_use_equal_scaling", true)) {
double centerX = (minX + maxX) / 2.0;
double centerY = (minY + maxY) / 2.0;
if(config.get<bool>("output_plots_use_pixel_units")) {
minX = centerX - model->getSensorSize().z() / model->getPixelSize().x() / 2.0;
maxX = centerX + model->getSensorSize().z() / model->getPixelSize().x() / 2.0;
minY = centerY - model->getSensorSize().z() / model->getPixelSize().y() / 2.0;
maxY = centerY + model->getSensorSize().z() / model->getPixelSize().y() / 2.0;
} else {
minX = centerX - model->getSensorSize().z() / 2.0;
maxX = centerX + model->getSensorSize().z() / 2.0;
minY = centerY - model->getSensorSize().z() / 2.0;
maxY = centerY + model->getSensorSize().z() / 2.0;
}
}
// Align on pixels if requested
if(config.get<bool>("output_plots_align_pixels")) {
if(config.get<bool>("output_plots_use_pixel_units")) {
minX = std::floor(minX - 0.5) + 0.5;
minY = std::floor(minY + 0.5) - 0.5;
maxX = std::ceil(maxX - 0.5) + 0.5;
maxY = std::ceil(maxY + 0.5) - 0.5;
} else {
double div = minX / model->getPixelSize().x();
minX = (std::floor(div - 0.5) + 0.5) * model->getPixelSize().x();
div = minY / model->getPixelSize().y();
minY = (std::floor(div - 0.5) + 0.5) * model->getPixelSize().y();
div = maxX / model->getPixelSize().x();
maxX = (std::ceil(div + 0.5) - 0.5) * model->getPixelSize().x();
div = maxY / model->getPixelSize().y();
maxY = (std::ceil(div + 0.5) - 0.5) * model->getPixelSize().y();
}
}
return {minX, maxX, minY, maxY, scale_x, scale_y, max_charge, total_charge, tot_point_cnt, start_time};
};
};
} // namespace allpix
#endif /* ALLPIX_LINE_GRAPHS_H */
Updated on 2024-12-13 at 08:31:37 +0000