1#include "Tracking/Reco/LinearSeedFinder.h"
13 truth_matching_tool_ = std::make_shared<tracking::sim::TruthMatchingTool>();
19 "out_seed_collection",
getName() +
"LinearRecoilSeedTracks");
23 parameters.
get<std::string>(
"input_hits_collection",
"DigiRecoilSimHits");
25 parameters.
get<std::string>(
"input_rec_hits_collection",
"EcalRecHits");
27 input_pass_name_ = parameters.
get<std::string>(
"input_pass_name",
"");
29 sim_particles_passname_ =
30 parameters.
get<std::string>(
"sim_particles_passname");
32 sim_particles_events_passname_ =
33 parameters.
get<std::string>(
"sim_particles_events_passname");
37 parameters.
get<std::vector<double>>(
"recoil_uncertainty", {0.006, 0.085});
38 ecal_uncertainty_ = parameters.
get<
double>(
"ecal_uncertainty", {3.87});
39 ecal_distance_threshold_ = parameters.
get<
double>(
"ecal_distance_threshold");
40 ecal_first_layer_z_threshold_ =
41 parameters.
get<
double>(
"ecal_first_layer_z_threshold");
43 layer12_midpoint_ = parameters.
get<
double>(
"layer12_midpoint");
44 layer23_midpoint_ = parameters.
get<
double>(
"layer23_midpoint");
45 layer34_midpoint_ = parameters.
get<
double>(
"layer34_midpoint");
49 auto start = std::chrono::high_resolution_clock::now();
50 std::vector<ldmx::StraightTrack> straight_seed_tracks;
56 const auto& ecal_rec_hit =
event.getCollection<
ldmx::EcalHit>(
59 std::vector<std::array<double, 3>> first_layer_ecal_rec_hits;
62 for (
const auto& x_ecal : ecal_rec_hit) {
63 if (x_ecal.getZPos() < ecal_first_layer_z_threshold_) {
64 first_layer_ecal_rec_hits.push_back(
65 {x_ecal.getZPos(), x_ecal.getXPos(), x_ecal.getYPos()});
70 if ((recoil_hits.size() < 2) || (first_layer_ecal_rec_hits.empty()) ||
71 (uniqueLayersHit(recoil_hits) < 2)) {
73 n_seeds_ += straight_seed_tracks.size();
79 std::map<int, ldmx::SimParticle> particle_map;
80 if (event.
exists(
"SimParticles", sim_particles_events_passname_)) {
82 "SimParticles", sim_particles_passname_);
83 truth_matching_tool_->setup(particle_map, recoil_hits);
87 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
90 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
94 "RecoilSimHits", input_pass_name_);
96 "TargetScoringPlaneHits", input_pass_name_);
99 std::unordered_map<int, std::vector<const ldmx::SimTrackerHit*>>
100 sim_hits_by_track_id;
101 for (
const auto& hit : recoil_sim_hits) {
102 sim_hits_by_track_id[hit.getTrackID()].push_back(&hit);
106 std::unordered_map<int, const ldmx::SimTrackerHit*> scoring_hit_map;
107 for (
const auto& sp_hit : scoring_hits) {
108 if (sp_hit.getPosition()[2] > 0)
109 scoring_hit_map[sp_hit.getTrackID()] = &sp_hit;
112 for (
const auto& point : recoil_hits) {
114 float x = point.getGlobalPosition()[0];
117 auto track_ids = point.getTrackIds();
118 int track_id = (track_ids.size() > 0) ? track_ids.at(0) : -1;
121 auto sim_range_it = sim_hits_by_track_id.find(track_id);
122 if (sim_range_it == sim_hits_by_track_id.end())
continue;
125 const auto& sim_hits = sim_range_it->second;
127 for (
const auto* sim_hit : sim_hits) {
128 float z = sim_hit->getPosition()[2];
130 if (x < layer12_midpoint_) {
131 if (z < layer12_midpoint_) {
132 auto sp_it = scoring_hit_map.find(track_id);
133 if (sp_it != scoring_hit_map.end()) {
134 first_two_layers.emplace_back(point, *sim_hit, *sp_it->second);
140 else if (x < layer23_midpoint_) {
141 if (z > layer12_midpoint_ && z < layer23_midpoint_) {
146 else if (x < layer34_midpoint_) {
147 if (z > layer23_midpoint_ && z < layer34_midpoint_) {
148 auto sp_it = scoring_hit_map.find(track_id);
149 if (sp_it != scoring_hit_map.end()) {
150 second_two_layers.emplace_back(point, *sim_hit, *sp_it->second);
157 if (z > layer34_midpoint_) {
158 second_two_layers.emplace_back(point, *sim_hit,
168 auto first_sensor_combos = processMeasurements(first_two_layers, tg);
169 auto second_sensor_combos = processMeasurements(second_two_layers, tg);
171 for (
const auto& [first_combo_3d_point, first_layer_one, first_layer_two] :
172 first_sensor_combos) {
174 std::optional<ldmx::Measurement>>
177 if (first_layer_two.has_value()) {
178 first_sensor_point = {first_combo_3d_point,
179 std::get<ldmx::Measurement>(first_layer_one),
180 std::get<ldmx::Measurement>(*first_layer_two)};
183 first_sensor_point = {first_combo_3d_point,
184 std::get<ldmx::Measurement>(first_layer_one),
188 for (
const auto& [second_combo_3d_point, second_layer_one,
189 second_layer_two] : second_sensor_combos) {
191 std::optional<ldmx::Measurement>>
193 if (second_layer_two.has_value()) {
194 second_sensor_point = {second_combo_3d_point,
195 std::get<ldmx::Measurement>(second_layer_one),
196 std::get<ldmx::Measurement>(*second_layer_two)};
199 second_sensor_point = {second_combo_3d_point,
200 std::get<ldmx::Measurement>(second_layer_one),
204 for (
const auto& rec_hit : first_layer_ecal_rec_hits) {
208 seedTracker(first_sensor_point, second_sensor_point, rec_hit);
211 if (seed_track.getChi2() > 0.0) {
212 straight_seed_tracks.push_back(seed_track);
218 n_seeds_ += straight_seed_tracks.size();
221 auto end = std::chrono::high_resolution_clock::now();
223 auto diff = end - start;
224 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();
226 first_layer_ecal_rec_hits.clear();
227 straight_seed_tracks.clear();
233 std::optional<ldmx::Measurement>>
236 std::optional<ldmx::Measurement>>
238 const std::array<double, 3> ecal_one) {
239 auto [sensor1, layer1, layer2] = recoil_one;
240 auto [sensor2, layer3, layer4] = recoil_two;
241 std::vector<ldmx::Measurement> all_points;
250 all_points.push_back(layer1);
254 if (layer2.has_value()) {
255 all_points.push_back(*layer2);
258 all_points.push_back(layer3);
262 if (layer4.has_value()) {
263 all_points.push_back(*layer4);
269 auto [m_x, b_x, m_y, b_y, seed_cov] = fit3DLine(sensor1, sensor2, ecal_one);
270 std::array<double, 3> temp_extrapolated_point = {
271 ecal_one[0], m_x * ecal_one[0] + b_x, m_y * ecal_one[0] + b_y};
272 double temp_distance = calculateDistance(temp_extrapolated_point, ecal_one);
276 if (temp_distance < ecal_distance_threshold_) {
278 trk.setInterceptX(b_x);
280 trk.setInterceptY(b_y);
281 trk.setTheta(std::atan2(m_y, std::sqrt(1 + m_x * m_x)));
282 trk.setPhi(std::atan2(m_x, 1.0));
284 trk.setAllSensorPoints(all_points);
285 trk.setFirstSensorPosition(sensor1);
286 trk.setSecondSensorPosition(sensor2);
287 trk.setFirstLayerEcalRecHit(ecal_one);
288 trk.setDistancetoRecHit(temp_distance);
290 trk.setTargetLocation(0.0, b_x, b_y);
291 trk.setEcalLayer1Location(temp_extrapolated_point);
293 globalChiSquare(sensor1, sensor2, ecal_one, m_x, m_y, b_x, b_y));
297 trk.setCov(seed_cov);
300 if (truth_matching_tool_->configured()) {
301 auto truth_info = truth_matching_tool_->truthMatch(all_points);
302 trk.setTrackID(truth_info.track_id_);
303 trk.setPdgID(truth_info.pdg_id_);
304 trk.setTruthProb(truth_info.truth_prob_);
317 ldmx_log(info) <<
"AVG Time/Event: " << std::fixed << std::setprecision(1)
318 << processing_time_ / n_events_ <<
" ms";
319 ldmx_log(info) <<
"Total Seeds/Events: " << n_seeds_ <<
"/" << n_events_;
320 ldmx_log(info) <<
"not enough seed points " << n_missing_;
324std::array<double, 3> LinearSeedFinder::getPointAtZ(
325 std::array<double, 3> target, std::array<double, 3> measurement,
327 double slope_x = (measurement[1] - target[0]) / (measurement[0] - target[2]);
328 double slope_y = (measurement[2] - target[1]) / (measurement[0] - target[2]);
330 double intercept_x = target[0] - slope_x * target[2];
331 double intercept_y = target[1] - slope_y * target[2];
333 double x_target = slope_x * z_target + intercept_x;
334 double y_target = slope_y * z_target + intercept_y;
336 return {z_target, x_target, y_target};
339std::vector<std::tuple<
340 std::array<double, 3>,
341 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>,
344LinearSeedFinder::processMeasurements(
347 const geo::TrackersTrackingGeometry& tg) {
349 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
352 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
354 std::vector<std::tuple<
355 std::array<double, 3>,
356 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>,
359 points_with_measurement;
360 Acts::Vector3 dummy{0., 0., 0.};
363 for (
const auto& [measurement, sim_hit, scoring_hit] : measurements) {
364 if (measurement.getLayerID() % 2 == 0) {
365 axial_measurements.emplace_back(measurement, sim_hit, scoring_hit);
367 stereo_measurements.emplace_back(measurement, sim_hit, scoring_hit);
373 if (!axial_measurements.empty() && !stereo_measurements.empty()) {
374 for (
const auto& axial : axial_measurements) {
375 const auto& [axial_meas, axial_hit, axial_sp] = axial;
377 for (
const auto& stereo : stereo_measurements) {
378 const auto& [stereo_meas, stereo_hit, stereo_sp] = stereo;
380 const Acts::Surface* axial_surface =
381 tg.getSurface(axial_meas.getLayerID());
382 const Acts::Surface* stereo_surface =
383 tg.getSurface(stereo_meas.getLayerID());
385 if (!axial_surface || !stereo_surface)
continue;
387 std::vector<ldmx::SimTrackerHit> sim_hits = {axial_hit, stereo_hit};
388 Acts::Vector3 space_point =
389 simple3DHitV2(axial_meas, *axial_surface, stereo_meas,
390 *stereo_surface, axial_sp, sim_hits);
392 points_with_measurement.push_back(
393 {convertToLdmxStdArray(space_point), axial, stereo});
396 }
else if (!axial_measurements.empty()) {
398 for (
const auto& axial : axial_measurements) {
399 const auto& [axial_meas, axial_hit, axial_sp] = axial;
400 const Acts::Surface* axial_surface =
401 tg.getSurface(axial_meas.getLayerID());
403 Acts::Vector3 axial_meas_hit = axial_surface->localToGlobal(
405 Acts::Vector2(axial_meas.getLocalPosition()[0], 0.0), dummy);
407 points_with_measurement.push_back(
408 {convertToLdmxStdArray(axial_meas_hit), axial, std::nullopt});
410 }
else if (!stereo_measurements.empty()) {
411 for (
const auto& stereo : stereo_measurements) {
412 const auto& [stereo_meas, stereo_hit, stereo_sp] = stereo;
413 const Acts::Surface* stereo_surface =
414 tg.getSurface(stereo_meas.getLayerID());
416 Acts::Vector3 stereo_meas_hit = stereo_surface->localToGlobal(
418 Acts::Vector2(stereo_meas.getLocalPosition()[0], 0.0), dummy);
420 points_with_measurement.push_back(
421 {convertToLdmxStdArray(stereo_meas_hit), stereo, std::nullopt});
424 return points_with_measurement;
428std::array<double, 3> LinearSeedFinder::convertToLdmxStdArray(
429 const Acts::Vector3& vec) {
430 return {vec.x(), vec.y(), vec.z()};
435std::tuple<Acts::Vector3, Acts::Vector3, Acts::Vector3>
436LinearSeedFinder::getSurfaceVectors(
const Acts::Surface& surface) {
437 Acts::Vector3 dummy{0., 0., 0.};
439 surface.localToGlobal(geometryContext(), Acts::Vector2(1, 0), dummy) -
440 surface.center(geometryContext());
442 surface.localToGlobal(geometryContext(), Acts::Vector2(0, 1), dummy) -
443 surface.center(geometryContext());
444 Acts::Vector3 w = u.cross(v).normalized();
445 return {u.normalized(), v.normalized(), w};
457Acts::Vector3 LinearSeedFinder::simple3DHitV2(
461 std::vector<ldmx::SimTrackerHit> pair_sim_hits) {
462 Acts::Vector3 dummy{0., 0., 0.};
463 Acts::Vector3 hit_on_target{target_sp.
getPosition()[0],
467 Acts::Vector3 axial_true_global{pair_sim_hits[0].getPosition()[0],
468 pair_sim_hits[0].getPosition()[1],
469 pair_sim_hits[0].getPosition()[2]};
471 Acts::Vector3 stereo_true_global{pair_sim_hits[1].getPosition()[0],
472 pair_sim_hits[1].getPosition()[1],
473 pair_sim_hits[1].getPosition()[2]};
475 Acts::Vector3 simpart_path = axial_true_global - hit_on_target;
476 Acts::Vector3 simpart_unit = simpart_path.normalized();
480 Acts::Vector3 axial_origin = axial_surface.center(geometryContext());
481 Acts::Vector3 stereo_origin = stereo_surface.center(geometryContext());
485 Acts::Vector3 delta_sensors = stereo_origin - axial_origin;
489 double dx_proj = (simpart_unit[0] / simpart_unit[2]) *
495 auto [axial_u, axial_v, axial_w] = getSurfaceVectors(axial_surface);
496 auto [stereo_u, stereo_v, stereo_w] = getSurfaceVectors(stereo_surface);
497 double salpha = dotProduct(axial_v, stereo_u);
498 double cosalpha = dotProduct(axial_u, stereo_u);
507 stereo_v_value = 0.0;
512 double v_intercept_useproj =
513 (stereo_u_value - (axial_u_value - dx_proj) * cosalpha) / salpha;
514 double u_intercept_useproj = axial_u_value - dx_proj;
517 Acts::Vector3 axst_global_useproj = axial_surface.localToGlobal(
519 Acts::Vector2(u_intercept_useproj, v_intercept_useproj), dummy);
520 Acts::Vector3 dummy_stereo_proj = stereo_surface.localToGlobal(
522 Acts::Vector2(u_intercept_useproj, v_intercept_useproj), dummy);
525 Acts::Vector3 reconstructed_hit{dummy_stereo_proj[0], axst_global_useproj[1],
526 axst_global_useproj[2]};
528 ldmx_log(debug) <<
"The particle projected axst measured position is "
529 "(compare with stereo sim position): "
530 << reconstructed_hit[0] <<
", " << reconstructed_hit[1]
531 <<
", " << reconstructed_hit[2] <<
"\n";
533 return reconstructed_hit;
536double LinearSeedFinder::dotProduct(
const Acts::Vector3& v1,
537 const Acts::Vector3& v2) {
541std::tuple<double, double, double, double, std::vector<double>>
542LinearSeedFinder::fit3DLine(
const std::array<double, 3>& first_recoil,
543 const std::array<double, 3>& second_recoil,
544 const std::array<double, 3>& ecal) {
545 double z_pos1 = first_recoil[0], x_pos1 = first_recoil[1],
546 y_pos1 = first_recoil[2];
547 double z_pos2 = second_recoil[0], x_pos2 = second_recoil[1],
548 y_pos2 = second_recoil[2];
549 double z_pos3 = ecal[0], x_pos3 = ecal[1], y_pos3 = ecal[2];
551 std::array<double, 6> weights = {
552 1 / pow(recoil_uncertainty_[0], 2), 1 / pow(recoil_uncertainty_[1], 2),
553 1 / pow(recoil_uncertainty_[0], 2), 1 / pow(recoil_uncertainty_[1], 2),
554 1 / pow(ecal_uncertainty_, 2), 1 / pow(ecal_uncertainty_, 2)};
556 Eigen::Matrix<double, 6, 4> a_mat;
557 Eigen::Matrix<double, 6, 1> d_vec, w_vec;
560 a_mat << z_pos1, 1, 0, 0, 0, 0, z_pos1, 1, z_pos2, 1, 0, 0, 0, 0, z_pos2, 1,
561 z_pos3, 1, 0, 0, 0, 0, z_pos3, 1;
564 d_vec << x_pos1, y_pos1, x_pos2, y_pos2, x_pos3, y_pos3;
567 w_vec = Eigen::Matrix<double, 6, 1>(weights.data());
570 Eigen::MatrixXd at_w_a = a_mat.transpose() * w_vec.asDiagonal() * a_mat;
571 Eigen::MatrixXd at_w_d = a_mat.transpose() * w_vec.asDiagonal() * d_vec;
572 Eigen::VectorXd param_vec = at_w_a.ldlt().solve(at_w_d);
574 Eigen::Matrix4d covariance_matrix = at_w_a.inverse();
578 std::vector<double> covariance_vector = {
579 covariance_matrix(0, 0), covariance_matrix(0, 1), covariance_matrix(0, 2),
580 covariance_matrix(0, 3), covariance_matrix(1, 1), covariance_matrix(1, 2),
581 covariance_matrix(1, 3), covariance_matrix(2, 2), covariance_matrix(2, 3),
582 covariance_matrix(3, 3)};
585 return {param_vec(0), param_vec(1), param_vec(2), param_vec(3),
589double LinearSeedFinder::calculateDistance(
590 const std::array<double, 3>& point1,
const std::array<double, 3>& point2) {
591 return sqrt(pow(point1[1] - point2[1], 2) + pow(point1[2] - point2[2], 2));
594double LinearSeedFinder::globalChiSquare(
595 const std::array<double, 3>& first_sensor,
596 const std::array<double, 3>& second_sensor,
597 const std::array<double, 3>& ecal_hit,
double m_x,
double m_y,
double b_x,
599 double chi2_x = 0, chi2_y = 0;
601 (m_x * first_sensor[0] + b_x - first_sensor[1]) / recoil_uncertainty_[0],
604 (m_y * first_sensor[0] + b_y - first_sensor[2]) / recoil_uncertainty_[1],
607 chi2_x += pow((m_x * second_sensor[0] + b_x - second_sensor[1]) /
608 recoil_uncertainty_[0],
610 chi2_y += pow((m_y * second_sensor[0] + b_y - second_sensor[2]) /
611 recoil_uncertainty_[1],
614 chi2_x += pow((m_x * ecal_hit[0] + b_x - ecal_hit[1]) / ecal_uncertainty_, 2);
615 chi2_y += pow((m_y * ecal_hit[0] + b_y - ecal_hit[2]) / ecal_uncertainty_, 2);
617 return chi2_x + chi2_y;
620int LinearSeedFinder::uniqueLayersHit(
621 const std::vector<ldmx::Measurement>& digi_points) {
622 std::vector<ldmx::Measurement> sorted_points = digi_points;
625 std::sort(sorted_points.begin(), sorted_points.end(),
627 return meas1.getGlobalPosition()[0] <
628 meas2.getGlobalPosition()[0];
632 auto last = std::unique(
633 sorted_points.begin(), sorted_points.end(),
635 return meas1.getGlobalPosition()[0] == meas2.getGlobalPosition()[0];
639 return std::distance(sorted_points.begin(), last);
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
std::string getName() const
Get the processor name.
Implements an event buffer system for storing event data.
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Class which represents the process under execution.
Class encapsulating parameters for configuring a processor.
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Stores reconstructed hit information from the ECAL.
std::array< float, 2 > getLocalPosition() const
Class representing a simulated particle.
Represents a simulated tracker hit in the simulation.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
std::string out_seed_collection_
The name of the output collection of seeds to be stored.
LinearSeedFinder(const std::string &name, framework::Process &process)
Constructor.
void configure(framework::config::Parameters ¶meters) override
Configure the processor using the given user specified parameters.
void produce(framework::Event &event) override
Run the processor and create a collection of results which indicate if a charge particle can be found...
void onProcessEnd() override
Output event statistics.
std::string input_hits_collection_
The name of the input hits collection to use in finding seeds..
void onProcessStart() override
Setup the truth matching.
std::string input_rec_hits_collection_
The name of the tagger Tracks (only for Recoil Seeding)
a helper base class providing some methods to shorten access to common conditions used within the tra...
The measurement calibrator can be a function or a class/struct able to retrieve the sim hits containe...