Program Listing for File CRTHitMatch.cxx¶
↰ Return to documentation for file (larflow/CRTMatch/CRTHitMatch.cxx
)
#include "CRTHitMatch.h"
#include "LArUtil/LArProperties.h"
#include "larflow/Reco/cluster_functions.h"
#include "TRandom2.h"
namespace larflow {
namespace crtmatch {
void CRTHitMatch::clear() {
// input containers
_intime_opflash_v.clear();
_outtime_opflash_v.clear();
_crthit_v.clear();
_crttrack_v.clear();
_lfcluster_v.clear();
_pcaxis_v.clear();
_hit2track_rank_v.clear();
_all_rank_v.clear();
// output containers
_matched_hitidx.clear();
_flash_v.clear();
_match_hit_v.clear();
_matched_cluster.clear();
_matched_cluster_t.clear();
_matched_opflash_v.clear();
_used_tracks_v.clear();
}
void CRTHitMatch::addIntimeOpFlashes( const larlite::event_opflash& opflash_v ) {
for ( auto const& opf : opflash_v )
_intime_opflash_v.push_back( opf );
}
void CRTHitMatch::addCosmicOpFlashes( const larlite::event_opflash& opflash_v ) {
for ( auto const& opf : opflash_v )
_outtime_opflash_v.push_back( opf );
}
void CRTHitMatch::addCRThits( const larlite::event_crthit& crthit_v ) {
for ( auto const& hit : crthit_v )
_crthit_v.push_back( hit );
}
void CRTHitMatch::addCRTtracks( const larlite::event_crttrack& crttrack_v ) {
for ( auto const& track : _crttrack_v )
_crttrack_v.push_back( track );
}
void CRTHitMatch::addLArFlowClusters( const larlite::event_larflowcluster& lfcluster_v,
const larlite::event_pcaxis& pcaxis_v ) {
for ( auto const& cluster : lfcluster_v ) {
_lfcluster_v.push_back( &cluster );
}
for ( auto const& pca : pcaxis_v ) {
_pcaxis_v.push_back( &pca );
}
}
bool CRTHitMatch::process( larcv::IOManager& iolcv, larlite::storage_manager& llio ) {
clear();
larlite::event_opflash* beamflash
= (larlite::event_opflash*)llio.get_data( larlite::data::kOpFlash, "simpleFlashBeam" );
larlite::event_opflash* cosmicflash
= (larlite::event_opflash*)llio.get_data( larlite::data::kOpFlash, "simpleFlashCosmic" );
addIntimeOpFlashes( *beamflash );
addCosmicOpFlashes( *cosmicflash );
// get crt hits
larlite::event_crthit* crthit_v
= (larlite::event_crthit*)llio.get_data( larlite::data::kCRTHit, "crthitcorr" );
addCRThits( *crthit_v );
// get clusters
larlite::event_larflowcluster* lfclusters_v
= (larlite::event_larflowcluster*)llio.get_data( larlite::data::kLArFlowCluster,
_input_cluster_treename );
larlite::event_pcaxis* pcaxis_v
= (larlite::event_pcaxis*)llio.get_data( larlite::data::kPCAxis,
_input_pcaxis_treename );
addLArFlowClusters( *lfclusters_v, *pcaxis_v );
std::vector< larflow::reco::cluster_t > test_v;
int icluster = 0;
for ( auto const& plfcluster : _lfcluster_v ) {
std::cout << "[convert cluster " << icluster << "]" << std::endl;
larflow::reco::cluster_t c = larflow::reco::cluster_from_larflowcluster( *plfcluster );
test_v.emplace_back( std::move(c) );
icluster++;
}
LARCV_INFO() << "passed conversion test" << std::endl;
return makeMatches();
}
bool CRTHitMatch::makeMatches() {
// compile matches
compilematches();
_used_tracks_v.resize( _lfcluster_v.size(), 0 );
// process matches in greedy fashion
for ( auto& m : _all_rank_v ) {
// get index of crt hit of this match
int crthitidx = m.hitidx;
auto& hit_matches_v = _hit2track_rank_v[ crthitidx ];
if ( hit_matches_v.size()==0 ) continue; // shouldn't happen
if ( hit_matches_v.size()==1 ) {
// if track already used. skip
if ( _used_tracks_v[ m.trackidx ]==1 )
continue;
// if not, make the match
LARCV_INFO() << " store single crt-hit to cluster match products" << std::endl;
_matched_hitidx.push_back( crthitidx );
// create a copy
larlite::larflowcluster lfc = *_lfcluster_v[ m.trackidx ]; // create a copy
_matched_cluster.emplace_back( std::move(lfc) );
_used_tracks_v[ m.trackidx ] = 1;
}
else {
// more than 1, attempt a merge
// set the usage vector for these matches
bool has_unused = false;
std::vector<int> used_track_v( hit_matches_v.size(), 0 );
for ( size_t i=0; i<hit_matches_v.size(); i++ ) {
used_track_v[i] = _used_tracks_v[ hit_matches_v[i].trackidx ];
if ( used_track_v[i]==0 )
has_unused = true;
}
if ( has_unused ) {
bool performed_merge = false;
larlite::larflowcluster merge = _merge_matched_cluster( hit_matches_v,
used_track_v,
performed_merge );
// make match based on merge result
if (performed_merge) {
larflow::reco::cluster_t clout = larflow::reco::cluster_from_larflowcluster( merge );
_matched_hitidx.push_back( crthitidx );
_matched_cluster.push_back( merge );
_used_tracks_v[ m.trackidx ] = 1;
LARCV_INFO() << "store merged clusters" << std::endl;
}
}
}
}//end of loop over all rank matches
LARCV_NORMAL() << "number of crt-hit to track matches: " << _matched_cluster.size() << std::endl;
// apply t0 offset to clusters
for ( size_t imatch=0; imatch<_matched_hitidx.size(); imatch++ ) {
int hitidx = _matched_hitidx[ imatch ];
auto const& crthit = _crthit_v[ hitidx ];
auto& lfc = _matched_cluster[ imatch ];
float xoffset = larutil::LArProperties::GetME()->DriftVelocity()*( crthit.ts2_ns*0.001 );
// we need to t0 subtract the points
for ( auto& lfhit : lfc ) {
lfhit[0] -= xoffset;
}
// store cluster_t for modified larflow cluster
larflow::reco::cluster_t clout = larflow::reco::cluster_from_larflowcluster( lfc );
_matched_cluster_t.emplace_back( std::move(clout) );
}
// now match CRT hits with tracks to opflashes
// make list of flashes
for ( auto const& flash : _intime_opflash_v )
_flash_v.push_back( &flash );
for ( auto const& flash : _outtime_opflash_v )
_flash_v.push_back( &flash );
// make list of crt hits with matches
for ( auto const& hitidx : _matched_hitidx ) {
_match_hit_v.push_back( &_crthit_v[ hitidx ] );
}
_matchOpflashes( _flash_v, _match_hit_v, _matched_cluster, _matched_opflash_v );
return true;
}
void CRTHitMatch::compilematches() {
_hit2track_rank_v.resize( _crthit_v.size() );
// plane positions
float crt_plane_pos[4] = { -261.606, -142.484, 393.016, 618.25 }; // bottom, side, side, top
//larlite::event_pcaxis* ev_pcaout = (larlite::event_pcaxis*)outio.get_data( larlite::data::kPCAxis, "crtmatch" );
// loop over larflow tracks, assign it to the best hit in each CRT plane
// we are filling out _hit2track_rank_v[ hit index ] -> list of track indices
LARCV_INFO() << "==============================" << std::endl;
LARCV_INFO() << " Start match function" << std::endl;
if ( _pcaxis_v.size()!=_lfcluster_v.size() ) {
throw std::runtime_error( "number of clusters and pc axis do not match" );
}
for (size_t itrack=0; itrack<_pcaxis_v.size(); itrack++ ) {
// represent track and its direction with its pc axis
auto const& pca = *_pcaxis_v[itrack];
// enforce minimum length
float len = getLength(pca);
if ( len<5.0 ) continue;
// allocate vars for best intersection for each CRT plane
float min_dist[4] = { 1.0e9, 1.0e9, 1.0e9, 1.0e9 };
int best_hitidx[4] = {-1, -1, -1, -1};
std::vector<float> best_panel_pos[4];
for (int i=0; i<4; i++) best_panel_pos[i].resize(3,0);
// loop over crt hits
for (size_t jhit=0; jhit<_crthit_v.size(); jhit++) {
auto const& crthit = _crthit_v[jhit];
int crtplane = crthit.plane;
// calculate distance to crt hit and position of intersection on the panel
std::vector<float> panel_pos;
float dist = makeOneMatch( pca, crthit, panel_pos );
// update closest hit on the plane
if ( dist>0 && dist<min_dist[crtplane] ) {
min_dist[crtplane] = dist;
best_hitidx[crtplane] = jhit;
best_panel_pos[crtplane] = panel_pos;
}
}//end of hit loop
LARCV_DEBUG() << " [" << itrack << "] closest dist per plane = "
<< "[ " << min_dist[0] << ", " << min_dist[1] << ", " << min_dist[2] << ", " << min_dist[3] << "]"
<< std::endl;
// loop over plane
for (int p=0; p<4; p++ ) {
// if a good hit found in the plane
if ( best_hitidx[p]>=0 ) {
auto const& besthit = _crthit_v[best_hitidx[p]];
LARCV_DEBUG() << " crt_hit=(" << besthit.x_pos << ", " << besthit.y_pos << "," << besthit.z_pos << ") "
<< " panel_pos=(" << best_panel_pos[p][0] << "," << best_panel_pos[p][1] << "," << best_panel_pos[p][2] << ") "
<< std::endl;
if ( min_dist[p]>=0 && min_dist[p]<50.0 ) {
float line[3] = {0};
float dist = 0.;
for (int i=0; i<3; i++ ) {
line[i] = best_panel_pos[p][i]-pca.getAvePosition()[i];
dist += line[i]*line[i];
}
dist = sqrt(dist);
for (int i=0; i<3; i++ ) line[i] /= dist;
// store a match
match_t m;
m.hitidx = best_hitidx[p];
m.trackidx = itrack;
m.tracklen = len;
m.dist2hit = min_dist[p];
_hit2track_rank_v[ best_hitidx[p] ].push_back( m );
_all_rank_v.push_back( m );
// store for visualization purposes
// larlite::pcaxis::EigenVectors e_v; // 3-axis + 2-endpoints
// for ( size_t p=0; p<3; p++ ) {
// std::vector<double> da_v = { line[0], line[1], line[2] };
// e_v.push_back( da_v );
// }
// std::vector<double> centroid_v = { pca.getAvePosition()[0], pca.getAvePosition()[1], pca.getAvePosition()[2] };
// std::vector<double> panel_v = { best_panel_pos[p][0], best_panel_pos[p][1], best_panel_pos[p][2] };
// e_v.push_back( centroid_v );
// e_v.push_back( panel_v );
// double eigenval[3] = { min_dist[p], 0, 0 };
// larlite::pcaxis llpca( true, 1, eigenval, e_v, centroid_v.data(), 0, itrack );
// //ev_pcaout->emplace_back( std::move(llpca) );
} // close enough to a hit to register a match
}//if valid hit found for plane
}//end of loop over CRT planes
}//end of loop over tracks
// sort the all rank list
std::sort( _all_rank_v.begin(), _all_rank_v.end() );
// sort each hit list
for ( auto& hitmatches : _hit2track_rank_v ) {
std::sort( hitmatches.begin(), hitmatches.end() );
}
printHitInfo();
return;
}
void CRTHitMatch::printHitInfo() {
LARCV_NORMAL() << "===============================================" << std::endl;
LARCV_NORMAL() << "[ CRT HIT INFO ]" << std::endl;
for ( size_t idx=0; idx<_crthit_v.size(); idx++ ) {
auto const& hit = _crthit_v.at(idx);
std::stringstream ss;
ss << " [hit " << idx
<< " p=" << hit.plane
<< " pos=(" << hit.x_pos << ", " << hit.y_pos << ", " << hit.z_pos << ") "
<< " t=" << hit.ts2_ns*1.0e-3 << " usec ]";
ss << " matches[";
for ( auto const& hidx : _hit2track_rank_v[idx] )
ss << " (" << hidx.dist2hit << "," << hidx.tracklen << ")";
ss << " ] ";
LARCV_NORMAL() << ss.str() << std::endl;
}
LARCV_NORMAL() << "===============================================" << std::endl;
}
float CRTHitMatch::makeOneMatch( const larlite::pcaxis& lfcluster_axis,
const larlite::crthit& hit,
std::vector<float>& panel_pos ) {
int crt_plane_dim[4] = { 1, 0, 0, 1 }; // Y-axis, X-axis, X-axis, Y-axis
float endpts[2][3];
float center[3];
float dir[3];
float crtpos[3] = { hit.x_pos, hit.y_pos, hit.z_pos };
float len = 0;
for ( int i=0; i<3; i++ ) {
endpts[0][i] = lfcluster_axis.getEigenVectors()[3][i];
endpts[1][i] = lfcluster_axis.getEigenVectors()[4][i];
center[i] = lfcluster_axis.getAvePosition()[i];
dir[i] = endpts[0][i]-center[i];
len += dir[i]*dir[i];
}
if (len>0) {
len = sqrt(len);
for (int i=0; i<3; i++) dir[i] /= len;
}
float max_x = 0.;
float min_x = 1.0e9;
for (int i=0; i<2; i++ ) {
if ( endpts[i][0] > max_x )
max_x = endpts[i][0];
if ( endpts[i][0] < min_x )
min_x = endpts[i][0];
}
// check if parallel to the plane
if ( dir[ crt_plane_dim[ hit.plane ] ]==0 ) {
return -1;
}
// check if in time
float x_offset = hit.ts2_ns*0.001*larutil::LArProperties::GetME()->DriftVelocity();
if ( max_x-x_offset >= 260.0 || max_x-x_offset<-10.0 ) {
return -1;
}
if ( min_x-x_offset >= 260.0 || min_x-x_offset<-10.0 ) {
return -1;
}
// in time, so let's calculte the position onto the plane
// remove the time offset
center[0] -= x_offset;
// project onto crt plane
float s = (crtpos[ crt_plane_dim[ hit.plane ] ] - center[ crt_plane_dim[hit.plane] ])/dir[ crt_plane_dim[hit.plane] ];
panel_pos.resize(3,0);
for ( int i=0; i<3; i++ ) {
panel_pos[i] = center[i] + s*dir[i];
}
// calculate distance to the hit
float dist = 0.;
for (int i=0; i<3; i++) {
dist += ( crtpos[i]-panel_pos[i] )*( crtpos[i]-panel_pos[i] );
}
dist = sqrt( dist );
panel_pos[0] += x_offset;
return dist;
}
float CRTHitMatch::getLength( const larlite::pcaxis& pca ) {
float dist = 0.;
for (int i=0; i<3; i++ ) {
float dx = ( pca.getEigenVectors()[3][i]-pca.getEigenVectors()[4][i] );
dist += dx*dx;
}
return sqrt(dist);
}
void CRTHitMatch::_matchOpflashes( const std::vector< const larlite::opflash* >& flash_v,
const std::vector< const larlite::crthit* >& hit_v,
const std::vector< larlite::larflowcluster >& cluster_v,
std::vector< larlite::opflash >& matched_opflash_v ) {
std::vector< int > used_flash_v( flash_v.size(), 0 );
const float _max_dt_flash_crt = 2.0;
for ( size_t ihit=0; ihit<hit_v.size(); ihit++ ) {
auto const& crt = *hit_v[ihit];
std::vector< const larlite::opflash* > matched_in_time_v;
std::vector<float> dt_usec_v;
std::vector< int > matched_index_v;
float closest_time = 10e9;
for ( size_t i=0; i<flash_v.size(); i++ ) {
if ( used_flash_v[i]>0 ) continue;
float dt_usec = crt.ts2_ns*1.0e-3 - flash_v[i]->Time();
if ( fabs(dt_usec) < closest_time )
closest_time = fabs(dt_usec);
if ( fabs(dt_usec)<_max_dt_flash_crt ) {
matched_in_time_v.push_back( flash_v[i] );
dt_usec_v.push_back( dt_usec );
matched_index_v.push_back( i );
LARCV_INFO() << " ... crt-track and opflash matched. dt_usec=" << dt_usec << std::endl;
}
}//end of flash loop
LARCV_NORMAL() << "crt-hit has " << matched_index_v.size() << " flash matches. closest time=" << closest_time << std::endl;
if ( matched_in_time_v.size()==0 ) {
// make empty opflash at the time of the crttrack
std::vector< double > PEperOpDet(32,0.0);
larlite::opflash blank_flash( crt.ts2_ns*1.0e-3,
0.0,
crt.ts2_ns*1.0e-3,
0,
PEperOpDet );
matched_opflash_v.emplace_back( std::move(blank_flash) );
}
else if ( matched_in_time_v.size()==1 ) {
// store a copy of the single matched opflash
matched_opflash_v.push_back( *matched_in_time_v[0] );
used_flash_v[ matched_index_v[0] ] = 1;
}
else {
// choose closest using distance from center of flash and track
// need mean of cluster
std::vector<float> cluster_mean(3,0);
for ( auto const& hit : cluster_v[ ihit ] ) {
for (int i=0; i<3; i++)
cluster_mean[i] += hit[i];
}
for (int i=0; i<3; i++ )
cluster_mean[i] /= (float)cluster_v[ihit].size();
float smallest_dist = 1.0e9;
const larlite::opflash* closest_opflash = nullptr;
// we have a loose initial standard. if more than one, we use a tighter standard
bool have_tight_match = false;
for ( auto& dt_usec : dt_usec_v ) {
if ( fabs(dt_usec)<1.5 ) have_tight_match = true;
}
for ( size_t i=0; i<matched_in_time_v.size(); i++ ) {
// ignore loose match if we know we have at least one good one
if ( have_tight_match && dt_usec_v[i]>1.5 ) continue;
// get mean of opflash
std::vector<float> flashcenter = { 0.0,
(float)matched_in_time_v[i]->YCenter(),
(float)matched_in_time_v[i]->ZCenter() };
float dist = 0.;
for (int v=1; v<3; v++ ) {
dist += ( flashcenter[v]-cluster_mean[v] )*( flashcenter[v]-cluster_mean[v] );
}
dist = sqrt(dist);
if ( dist<smallest_dist ) {
smallest_dist = dist;
closest_opflash = matched_in_time_v[i];
}
LARCV_INFO() << " ... distance between opflash and track center: " << dist << " cm" << std::endl;
}//end of candidate opflash match
// store best match
if ( closest_opflash ) {
matched_opflash_v.push_back( *closest_opflash );
}
else {
// shouldnt get here
throw std::runtime_error( "should not get here" );
}
}//else more than 1 match
}//end of CRT HIT loop
// check we have the right amount of flashes
if ( matched_opflash_v.size()!=cluster_v.size() || matched_opflash_v.size()!=hit_v.size() ) {
throw std::runtime_error( "different amount of input crt-hit, cluster, and opflashes");
}
}
larlite::larflowcluster CRTHitMatch::_merge_matched_cluster( const std::vector< CRTHitMatch::match_t >& hit_match_v,
std::vector<int>& used_in_merge,
bool& merged ) {
merged = false;
larlite::larflowcluster lfcluster;
// if one or zero matches, nothing to do here.
if ( hit_match_v.size()<=1 )
return lfcluster;
// we use cluster tools, so start by converting best track
larflow::reco::cluster_t mergecluster;
int nmerged = 0;
for ( size_t idx=0; idx<hit_match_v.size(); idx++ ) {
LARCV_INFO() << "merger: make cluster for larflowcluster index=" << hit_match_v[idx].trackidx << " nhits=" << _lfcluster_v[ hit_match_v[idx].trackidx ]->size() << std::endl;
larflow::reco::cluster_t c = larflow::reco::cluster_from_larflowcluster( *_lfcluster_v[ hit_match_v[idx].trackidx ] );
if ( nmerged==0 ) {
// first cluster
LARCV_INFO() << "merger: set first cluster" << std::endl;
std::swap(mergecluster,c);
used_in_merge[idx] = 1;
merged = true;
nmerged++;
continue;
}
// determine a good match
LARCV_INFO() << "merger: test for merger" << std::endl;
std::vector< std::vector<float> > closestpts_vv;
float endptdist = larflow::reco::cluster_closest_endpt_dist( mergecluster, c, closestpts_vv );
float cospca = cluster_cospca( mergecluster, c );
cospca = 1.0 - fabs(cospca);
if ( float(cospca)*180.0/3.14159 < 20.0 && endptdist < 30.0 ) {
LARCV_INFO() << "merge test passed" << std::endl;
larflow::reco::cluster_t testmerge = larflow::reco::cluster_merge( mergecluster, c );
if ( testmerge.pca_eigenvalues[1] < 30.0 ) {
merged = true;
used_in_merge[idx] = 1;
nmerged++;
LARCV_INFO() << "swap for merged cluster" << std::endl;
std::swap( mergecluster, testmerge );
}
}
}
for ( size_t idx=0; idx<hit_match_v.size(); idx++ ) {
if ( used_in_merge[idx]==1 ) {
for ( auto const& hit : *_lfcluster_v[ hit_match_v[idx].trackidx ] )
lfcluster.push_back( hit );
}
}
return lfcluster;
}
void CRTHitMatch::save_to_file( larlite::storage_manager& outll, bool remove_if_no_flash ) {
// larlite::event_crthit* out_crthit
// = (larlite::event_crthit*)outll.get_data( larlite::data::kCRTHit, "matchcrthit" );
larlite::event_crttrack* out_crttrack
= (larlite::event_crttrack*)outll.get_data( larlite::data::kCRTTrack, "matchcrthit" );
larlite::event_opflash* out_opflash
= (larlite::event_opflash*)outll.get_data( larlite::data::kOpFlash, "matchcrthit" );
larlite::event_larflowcluster* out_lfcluster
= (larlite::event_larflowcluster*)outll.get_data( larlite::data::kLArFlowCluster, "matchcrthit" );
for (size_t i=0; i<_matched_cluster.size(); i++ ) {
auto& cluster = _matched_cluster[i];
auto& opflash = _matched_opflash_v[i];
auto const& crthit = *_match_hit_v[i];
auto const& clustert = _matched_cluster_t[i];
if ( remove_if_no_flash && opflash.TotalPE()==0.0 ) {
LARCV_INFO() << "no matching flash for matched CRT hit[" << i << "], not saving" << std::endl;
continue;
}
float petot = opflash.TotalPE();
LARCV_NORMAL() << "saving track with opflash pe=" << petot << " nopdets=" << opflash.nOpDets() << std::endl;
// form a crt-track
larlite::crttrack outtrack;
// outtrack.feb_id = crthit.feb_id;
// outtrack.pesmap = crthit.pesmap;
// outtrack.peshit = crthit.peshit;
// outtrack.ts0_s = crthit.ts0_s;
// outtrack.ts0_ns = crthit.ts0_ns;
// outtrack.ts0_ns_corr = crthit.ts0_ns_corr;
// first crt hit
outtrack.ts2_ns_h1 = crthit.ts2_ns;
outtrack.plane1 = crthit.plane;
outtrack.x1_pos = crthit.x_pos;
outtrack.y1_pos = crthit.y_pos;
outtrack.z1_pos = crthit.z_pos;
// second crt hit: use the farthest point on the cluster
outtrack.plane2 = -1;
outtrack.ts2_ns_h2 = crthit.ts2_ns;
std::vector<float> crtpos = { outtrack.x1_pos, outtrack.y1_pos, outtrack.z1_pos };
int farend = 0;
float fardist = 0.;
for ( int iend=0; iend<2; iend++ ) {
float dist = 0;
for (int i=0; i<3; i++ ) {
dist += ( crtpos[i]- clustert.pca_ends_v[iend][i] )*( crtpos[i]- clustert.pca_ends_v[iend][i] );
}
dist = sqrt(dist);
if ( dist>fardist ) {
fardist = dist;
farend = iend;
}
}
outtrack.x2_pos = clustert.pca_ends_v[farend][0];
outtrack.y2_pos = clustert.pca_ends_v[farend][1];
outtrack.z2_pos = clustert.pca_ends_v[farend][2];
out_crttrack->push_back(outtrack);
out_opflash->push_back(opflash);
out_lfcluster->push_back( cluster );
}
}
bool CRTHitMatch::was_cluster_used( int idx ) {
if (idx<0 || idx>=_used_tracks_v.size() )
return false;
if ( _used_tracks_v[idx]==1 )
return true;
return false;
}
}
}