Program Listing for File FlowContourMatch.cxx¶
↰ Return to documentation for file (larflow/FlowContourMatch/arxiv/FlowContourMatch.cxx
)
#include "FlowContourMatch.h"
#include <exception>
#include <cmath>
// larlite
#include "LArUtil/Geometry.h"
#include "LArUtil/LArProperties.h"
#include "LArUtil/DetectorProperties.h"
// larcv
#include "larcv/core/DataFormat/ImageMeta.h"
#include "larcv/core/ROOTUtil/ROOTUtils.h"
// ROOT
#include "TStyle.h"
#include "TCanvas.h"
#include "TH2D.h"
namespace larflow {
FlowMatchData_t::FlowMatchData_t( int srcid, int tarid )
: src_ctr_id(srcid), tar_ctr_id(tarid), score(0)
{}
FlowMatchData_t::FlowMatchData_t( const FlowMatchData_t& x )
: src_ctr_id(x.src_ctr_id),
tar_ctr_id(x.tar_ctr_id),
score(x.score)
{
matchingflow_v = x.matchingflow_v;
}
// ==========================================
// FlowContourMatch Algo
// ---------------------
const int FlowContourMatch::kSourcePlane[2] = { 2, 2 };
const int FlowContourMatch::kTargetPlane[2] = { 0, 1 };
FlowContourMatch::FlowContourMatch() {
for (int i=0; i<2; i++) {
m_score_matrix[i] = NULL;
m_plot_scorematrix[i] = NULL;
m_tar_img2ctrindex[i] = NULL;
}
m_src_img2ctrindex = NULL;
// parameters: see header for descriptions
kTargetChargeRadius = 2;
// larutil
m_psce = new ::larutil::SpaceChargeMicroBooNE;
m_ptsv = ::larutil::TimeService::GetME();
}
FlowContourMatch::~FlowContourMatch() {
clear();
}
void FlowContourMatch::clear( bool clear2d, bool clear3d, int flowdir ) {
if ( clear2d ) {
// needs to be cleared for each subimage (entry)
for (int i=0; i<2; i++) {
if ( flowdir>=0 && i!=flowdir ) continue;
delete m_score_matrix[i];
delete m_plot_scorematrix[i];
m_score_matrix[i] = nullptr;
m_plot_scorematrix[i] = nullptr;
m_flowdata[i].clear();
m_flowdata_indexmap[i].clear();
m_src_targets[i].clear();
delete m_tar_img2ctrindex[i];
m_tar_img2ctrindex[i] = nullptr;
}
delete m_src_img2ctrindex;
m_src_img2ctrindex = nullptr;
}//end of clear 2nd
if (clear3d) {
// needs to be cleared after every event
m_plhit2flowdata.clear();
}
}
// ==================================================================================
// Main function
// -----------------------
std::vector<larlite::larflow3dhit> FlowContourMatch::makeFlowPixels( std::vector<larcv::Image2D>& adc_crop_v,
std::vector<larcv::SparseImage>& flowy2u,
std::vector<larcv::SparseImage>& flowy2v,
const float threshold )
{
std::vector<larlite::larflow3dhit> flowhit_v;
return flowhit_v;
}
// ==================================================================================
// Primary Interfaces
// -----------------------
void FlowContourMatch::fillPlaneHitFlow( const ublarcvapp::ContourClusterAlgo& contour_data,
const larcv::Image2D& src_adc,
const std::vector<larcv::Image2D>& tar_adc,
const std::vector<larcv::Image2D>& flow_img,
const larlite::event_hit& hit_v,
const float threshold,
bool runY2U,
bool runY2V) {
// Primary call.
// We expect this to be called many time per event, for each subimage
// goal is to fill m_plhit2flowdata
// we clear 2d info only (first). 3d info gets accumulated over many subimages
// this is wonky and needs to get fixed with eventual interface where whole-image gets provided
// (or vector of subimages? -- lots of mem to hold that simultaneously?)
clear( true, false );
if(runY2U && runY2V && tar_adc.size()<2){
throw std::runtime_error("FlowContourMatch::fillPlaneHitFlow: requested both planes but single target");
}
if(runY2U && runY2V && flow_img.size()<2){
throw std::runtime_error("FlowContourMatch::fillPlaneHitFlow: requested both planes but single flow image");
}
if(runY2U){
//std::cout << "Run Y2U Match" << std::endl;
_match( FlowContourMatch::kY2U,
contour_data,
src_adc,
tar_adc[0],
flow_img[0],
hit_v,
threshold );
m_plhit2flowdata.ranY2U = true;
}
if(runY2V){
//std::cout << "Run Y2V Match" << std::endl;
_match( FlowContourMatch::kY2V,
contour_data,
src_adc,
tar_adc[1],
flow_img[1],
hit_v,
threshold );
m_plhit2flowdata.ranY2V = true;
}
//check for size mismatch: should not happen if both run
if(runY2U && runY2V){
if ( m_plhit2flowdata.Y2U.size()!=m_plhit2flowdata.Y2V.size() )
throw std::runtime_error("FlowContourMatch::fillPlaneHitFlowData -- Y2U and Y2V do not match");
}
_fill_consistency3d(m_plhit2flowdata.Y2U, m_plhit2flowdata.Y2V, m_plhit2flowdata.consistency3d, m_plhit2flowdata.dy, m_plhit2flowdata.dz);
}
// =====================================================================
// SSNet+Endpoint Integration
// ---------------------------
void FlowContourMatch::integrateSSNetEndpointOutput( const std::vector<larcv::Image2D>& track_scoreimgs,
const std::vector<larcv::Image2D>& shower_scoreimgs,
const std::vector<larcv::Image2D>& endpt_scoreimgs )
{
// store ssnet+endpoint information into hit2flow data
// inputs
// ------
// track_scoreimgs: assuming vector is (u,v,y)
// shower_scoreimgs: assuming vector is (u,v,y)
// endpt_scoreimgs: assuming vector is (u,v,y)
// ** we assume the above cover the same subimage region
//
// output
// -------
// updates m_plhit2flowdata[Y2U and Y2V] (if exists)
// can be whole view or subimage
// we loop over hits, and check if image set is applicable
bool filled[2] = { m_plhit2flowdata.ranY2U, m_plhit2flowdata.ranY2V };
std::vector<HitFlowData_t>* phitdata_v[2] = { &(m_plhit2flowdata.Y2U), &(m_plhit2flowdata.Y2V) };
for ( int iflow=kY2U; iflow<(int)kNumFlowDirs; iflow++ ) {
if ( !filled[iflow] ) continue;
std::vector<HitFlowData_t>& hitdata_v = *(phitdata_v[iflow]);
// we check if hit is contained
const larcv::Image2D& track_img = track_scoreimgs[ kSourcePlane[iflow] ];
const larcv::Image2D& shower_img = shower_scoreimgs[ kSourcePlane[iflow] ];
const larcv::Image2D& endpt_img = endpt_scoreimgs[ kSourcePlane[iflow] ];
const larcv::ImageMeta& track_meta = track_img.meta();
for ( auto& hitdata : hitdata_v ) {
if ( track_meta.min_x()<=hitdata.srcwire && hitdata.srcwire<track_meta.max_x()
&& track_meta.min_y()<=hitdata.pixtick && hitdata.pixtick<track_meta.max_y() ) {
// inside image
int col = track_meta.col( hitdata.srcwire );
int row = track_meta.row( hitdata.pixtick );
hitdata.track_score = track_img.pixel(row,col);
hitdata.shower_score = shower_img.pixel(row,col);
hitdata.endpt_score = endpt_img.pixel(row,col);
// renormed scores: remove endpt and recalc shower/track
float renorm = 1.0 - hitdata.endpt_score; // bg+track+shower
hitdata.renormed_track_score = hitdata.track_score/renorm;
hitdata.renormed_shower_score = hitdata.shower_score/renorm;
}
}
}
}
// =====================================================================
// INFILL INTEGRATION
// --------------------
void FlowContourMatch::stitchInfill( const larcv::Image2D& infill_crop,
larcv::Image2D& trusted,
larcv::Image2D& infill_whole,
const larcv::EventChStatus& ev_chstatus){
// fills an infill subimage in a whole image.
// Note: this is set to prefer prediction in center of image.
const larcv::ImageMeta& out_meta = infill_whole.meta();
int src_min_c = out_meta.col( infill_crop.meta().min_x() );
int src_min_r = out_meta.row( infill_crop.meta().min_y() );
for ( int r=0; r<(int)infill_crop.meta().rows(); r++ ) {
for ( int c=0; c<(int)infill_crop.meta().cols(); c++ ) {
int src_c = src_min_c+c;
int src_r = src_min_r+r;
if ( trusted.pixel(src_r,src_c)>0 )
continue; // already filled by trusted region
infill_whole.set_pixel( src_r, src_c, infill_crop.pixel(r,c) );
if ( c>=261 || c<=571 )
trusted.set_pixel( src_r, src_c, 1.0 );
}
}
}
void FlowContourMatch::maskInfill( const larcv::Image2D& infill,
const larcv::EventChStatus& ev_chstatus,
const float score_thresh,
larcv::Image2D& masked_infill){
// masks infill prediction outside of dead regions.
// creates activation image (pix = 0 or 1) w.r.t. infill score threshold
// inputs
//------
// original infill image
//
// outputs
//-------
// masked & thresholded infill activation
//
const larcv::ChStatus& status = ev_chstatus.Status(infill.meta().plane());
const std::vector<short> st_v = status.as_vector();
for(int col=0; col<infill.meta().cols(); col++){
if ( (col+(int)infill.meta().min_x())>=(int)st_v.size() ) continue; // bounds check
if(st_v[col+infill.meta().min_x()]==4) continue; //if good ch skip
for(int row=0; row<infill.meta().rows(); row++){
if(infill.pixel(row,col)<score_thresh) continue; //infill score threshold
masked_infill.set_pixel(row,col,1);
}//end of row
}//end of col
}
void FlowContourMatch::addInfill( const larcv::Image2D& masked_infill,
const larcv::EventChStatus& ev_chstatus,
const float threshold,
larcv::Image2D& img_fill_v){
// adds infill activation to existing adc image
// adc is set w.r.t. some charge threshold
// inputs
//-------
// infill activation
// original adc image
//
// outputs
//---------
// edited adc image
//
// int maxcol = img_fill_v.meta().cols();
// int maxcol_plane = maxcol;
// if ( img_fill_v.meta().plane()<2 ) {
// maxcol_plane = img_fill_v.meta().col(2399)+1; // maxwire
// }
// else {
// maxcol_plane = img_fill_v.meta().col(3455)+1; // maxwire
// }
// maxcol = ( maxcol>maxcol_plane) ? maxcol_plane : maxcol;
const larcv::ChStatus& status = ev_chstatus.Status(masked_infill.meta().plane());
const std::vector<short> st_v = status.as_vector();
for(int col=0; col<masked_infill.meta().cols(); col++){
if ( (col+(int)masked_infill.meta().min_x())>=(int)st_v.size() ) continue; // bounds check
if(st_v[col+masked_infill.meta().min_x()]==4) continue; //if good ch skip
for(int row=0; row<masked_infill.meta().rows(); row++){
if(masked_infill.pixel(row,col)<1) continue; //masked infill
img_fill_v.set_pixel(row,col,threshold+5.); //set adc to above threshold
}//end of row
}//end of col
}
void FlowContourMatch::labelInfillHits( const std::vector<larcv::Image2D>& masked_infill) {
// store Infill above thresh information into hit2flow data
// inputs
// ------
// masked_infill: assuming vector is (u,v,y)
// ** we assume the above cover the same subimage region
//
// output
// -------
// updates m_plhit2flowdata[Y2U and Y2V] (if exists)
// can be whole view or subimage
// we loop over hits, and check if image set is applicable
bool filled[2] = { m_plhit2flowdata.ranY2U, m_plhit2flowdata.ranY2V };
std::vector<HitFlowData_t>* phitdata_v[2] = { &(m_plhit2flowdata.Y2U), &(m_plhit2flowdata.Y2V) };
for ( int iflow=kY2U; iflow<(int)kNumFlowDirs; iflow++ ) {
if ( !filled[iflow] ) continue;
std::vector<HitFlowData_t>& hitdata_v = *(phitdata_v[iflow]);
// we check if hit is contained
const larcv::Image2D& infill_src = masked_infill[ kSourcePlane[iflow] ];
const larcv::Image2D& infill_tar = masked_infill[ kTargetPlane[iflow] ];
const larcv::ImageMeta& src_meta = infill_src.meta();
const larcv::ImageMeta& tar_meta = infill_tar.meta();
for ( auto& hitdata : hitdata_v ) {
if ( src_meta.min_x()<=hitdata.srcwire && hitdata.srcwire<src_meta.max_x()
&& src_meta.min_y()<=hitdata.pixtick && hitdata.pixtick<src_meta.max_y() ) {
// inside image
int col = src_meta.col( hitdata.srcwire );
int row = src_meta.row( hitdata.pixtick );
hitdata.src_infill = (infill_src.pixel(row,col)>0 ) ? true : false;
// check if target pix is inside image
if ( tar_meta.min_x()<=hitdata.targetwire && hitdata.targetwire<tar_meta.max_x()
&& tar_meta.min_y()<=hitdata.pixtick && hitdata.pixtick<tar_meta.max_y() ) {
int colt= tar_meta.col( hitdata.targetwire);
hitdata.tar_infill = (infill_tar.pixel(row,colt)>0) ? true : false;
}
else{
//not in crop
hitdata.tar_infill = false;
}
}
}
}
}
// =====================================================================
// MCTRACK MATCH
// --------------------
void FlowContourMatch::mctrack_match( const larlite::event_mctrack& evtrack,
const std::vector<larcv::Image2D>& img_v){
return mctrack_match( m_plhit2flowdata, evtrack, img_v, m_psce, m_ptsv);
}
void FlowContourMatch::mctrack_match(PlaneHitFlowData_t& plhit2flowdata,
const larlite::event_mctrack& evtrack,
const std::vector<larcv::Image2D>& img_v,
::larutil::SpaceChargeMicroBooNE* psce,
const ::larutil::TimeService* ptsv){
// This function updates internal std::vector<HitFlowData_t> to add mctruth.
// It is intended to be run once per event,
// when we have collected all hits in the whole image.
// First all mctracks in the event are projected onto the whole Y image.
// Then we match via hit source pixel and tick
//space charge and time service; ideally initialized in algo constructor
::larutil::SpaceChargeMicroBooNE* sce = psce;
if ( psce==NULL ){
sce = new ::larutil::SpaceChargeMicroBooNE;
}
// note on TimeService: if not initialized from root file via GetME(true)
// needs trig_offset hack in tufts_larflow branch head
const ::larutil::TimeService* tsv = ptsv;
if( ptsv==NULL){
tsv = ::larutil::TimeService::GetME();
}
// blank track images: trackid, x, y, z, E, flag, dWall with Y meta
std::vector<larcv::Image2D> trackimg_v;
for(int i=0; i<7; i++){
larcv::Image2D trackimg(img_v[2].meta());
trackimg.paint(-1.0);
trackimg_v.emplace_back(std::move(trackimg));
}
for(const auto& truthtrack : evtrack){
//initialize internal vectors
int nstep = truthtrack.size();
std::vector<unsigned int> trackid;//(nstep,0);
std::vector<double> E;//(nstep,0);
std::vector<std::vector<float>> tyz;//(nstep,std::vector<double>(3,0));
std::vector<float> dWall;
_mctrack_to_tyz(truthtrack,tyz,trackid,E,dWall,sce,tsv);
_tyz_to_pixels(tyz,trackid,E,dWall,img_v[2],trackimg_v);
}
std::vector<HitFlowData_t>* hit2flowdata = &plhit2flowdata.Y2U; // assign Y2U first
std::vector<HitFlowData_t>* hit2flowdata2 = NULL;
if(plhit2flowdata.ranY2U && plhit2flowdata.ranY2V){
hit2flowdata2 = &plhit2flowdata.Y2V; // also assign Y2V
}
else if(!plhit2flowdata.ranY2U && plhit2flowdata.ranY2V){
hit2flowdata = &plhit2flowdata.Y2V; //assign Y2V only
}
else if(!plhit2flowdata.ranY2U && !plhit2flowdata.ranY2V){
throw std::runtime_error("FlowContourMatch::mctrack_match -- no hits filled");
}
else{
//do nothing
}
// now we loop over HitFlowData_t
// note: this copies the same mctruth to Y2U and Y2V, b/c we don't know
// at this point which one will be selected
//
// to check: are srcwire, pixtick in global (whole img) scope??
// if not, I need the crop image meta
for(auto& hit : *(hit2flowdata)){
hit.X_truth.resize(3,-1.);
if(hit.pixtick >= trackimg_v[0].meta().min_y() && hit.pixtick < trackimg_v[0].meta().max_y()
&& hit.srcwire >= trackimg_v[0].meta().min_x() && hit.srcwire < trackimg_v[0].meta().max_x() ){
int col = trackimg_v[0].meta().col( hit.srcwire );
int row = trackimg_v[0].meta().row( hit.pixtick );
hit.trackid = (int)trackimg_v[0].pixel(row,col);
hit.truthflag = (int)trackimg_v[5].pixel(row,col)+1; //nomatch is -1 in blank image
hit.X_truth[0] = trackimg_v[1].pixel(row,col);
hit.X_truth[1] = trackimg_v[2].pixel(row,col);
hit.X_truth[2] = trackimg_v[3].pixel(row,col);
hit.dWall = trackimg_v[6].pixel(row,col);
//std::cout <<"dWall= "<< hit.dWall << std::endl;
}
}
if(hit2flowdata2 != NULL){
for(auto& hit : *(hit2flowdata2)){
hit.X_truth.resize(3,-1.);
if(hit.pixtick >= trackimg_v[0].meta().min_y() && hit.pixtick < trackimg_v[0].meta().max_y()
&& hit.srcwire >= trackimg_v[0].meta().min_x() && hit.srcwire < trackimg_v[0].meta().max_x() ){
int col = trackimg_v[0].meta().col( hit.srcwire );
int row = trackimg_v[0].meta().row( hit.pixtick );
hit.trackid = (int)trackimg_v[0].pixel(row,col);
hit.truthflag = (int)trackimg_v[5].pixel(row,col)+1;
hit.X_truth[0] = trackimg_v[1].pixel(row,col);
hit.X_truth[1] = trackimg_v[2].pixel(row,col);
hit.X_truth[2] = trackimg_v[3].pixel(row,col);
hit.dWall = trackimg_v[6].pixel(row,col);
}
}
}
}
void FlowContourMatch::_mctrack_to_tyz(const larlite::mctrack& truthtrack,
std::vector<std::vector<float>>& tyz,
std::vector<unsigned int>& trackid,
std::vector<double>& E,
std::vector<float>& dWall,
::larutil::SpaceChargeMicroBooNE* sce,
const ::larutil::TimeService* tsv){
const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*(::larutil::DetectorProperties::GetME()->SamplingRate()*1.0e-3);
// detector boundaries
const ::larutil::Geometry* geo = ::larutil::Geometry::GetME();
const float xmin = 0.;
const float xmax = geo->DetHalfWidth()*2.;
const float ymin = -1.*geo->DetHalfHeight();
const float ymax = geo->DetHalfHeight();
const float zmin = 0.;
const float zmax = geo->DetLength();
for(int step=1; step<truthtrack.size(); step++){
std::vector<float> pos(3); //x,y,z
std::vector<float> dr(4); // x,y,z,t
dr[0] = -truthtrack[step-1].X()+truthtrack[step].X();
dr[1] = -truthtrack[step-1].Y()+truthtrack[step].Y();
dr[2] = -truthtrack[step-1].Z()+truthtrack[step].Z();
dr[3] = -truthtrack[step-1].T()+truthtrack[step].T();
float dR = sqrt(pow(dr[0],2)+pow(dr[1],2)+pow(dr[2],2));
for (int i=0; i<3; i++)
dr[i] /= dR;
int N = (dR>0.15) ? dR/0.15+1 : 1;
float dstep = dR/float(N);
for(int i=0; i<N; i++){
float mindist = 1.0e4;
pos[0] = truthtrack[step-1].X()+dstep*i*dr[0];
pos[1] = truthtrack[step-1].Y()+dstep*i*dr[1];
pos[2] = truthtrack[step-1].Z()+dstep*i*dr[2];
// for time use linear approximation
float t = truthtrack[step-1].T()+i*(dstep*::larutil::LArProperties::GetME()->DriftVelocity()*1.0e3); // cm * cm/usec * usec/ns
std::vector<double> pos_offset = sce->GetPosOffsets( pos[0], pos[1], pos[2] );
pos[0] = pos[0]-pos_offset[0]+0.7;
pos[1] += pos_offset[1];
pos[2] += pos_offset[2];
//time tick
float tick = tsv->TPCG4Time2Tick(t) + pos[0]/cm_per_tick;
pos[0] = (tick + tsv->TriggerOffsetTPC()/(::larutil::DetectorProperties::GetME()->SamplingRate()*1.0e-3))*cm_per_tick; // x in cm
// SCE shifted min dist to wall
//if(std::abs(pos[0] - xmin)< mindist) mindist = std::abs(pos[0] - xmin);
//if(std::abs(pos[0] - xmax)< mindist) mindist = std::abs(pos[0] - xmax);
if(std::abs(pos[1] - ymin)< mindist) mindist = std::abs(pos[1] - ymin);
if(std::abs(pos[1] - ymax)< mindist) mindist = std::abs(pos[1] - ymax);
if(std::abs(pos[2] - zmin)< mindist) mindist = std::abs(pos[2] - zmin);
if(std::abs(pos[2] - zmax)< mindist) mindist = std::abs(pos[2] - zmax);
tyz.push_back(pos);
dWall.push_back(mindist);
trackid.push_back(truthtrack.TrackID());//this is the same for all steps in a track
E.push_back(truthtrack[step-1].E());
}
}
}
void FlowContourMatch::_tyz_to_pixels(const std::vector<std::vector<float>>& tyz,
const std::vector<unsigned int>& trackid,
const std::vector<double>& E,
const std::vector<float>& dWall,
const larcv::Image2D& adc,
std::vector<larcv::Image2D>& trackimg_v){
// this function updates the mctrack images for each mctrack
// if two tracks are crossing, the corresponding pixel will be filled by the track with higher E at that point
std::vector<std::vector<int>> imgpath;
imgpath.reserve(tyz.size());
for (auto const& pos : tyz ) {
std::vector<int> crossing_imgcoords = getProjectedPixel( pos, adc.meta(), 3 );
imgpath.push_back( crossing_imgcoords );
}
int istep = 0;
float thresh = 10.0; // adc threshold
// note: pix are image row, col numbers
for(auto const& pix : imgpath ){
if(pix[0]==-1 || pix[3]==-1){istep++; continue;}
for(int b=pix[3]-2; b<pix[3]+3; b++){
if( b<0 || b>= trackimg_v[0].meta().cols()) continue; //border case
double prevE = trackimg_v[4].pixel(pix[0],b);
if( prevE>0 && prevE > E.at(istep) ) continue; //fill only highest E deposit
if( adc.pixel(pix[0],b)<thresh) continue;
trackimg_v[0].set_pixel(pix[0],b,(float)trackid.at(istep));// trackid
trackimg_v[1].set_pixel(pix[0],b,(float)tyz.at(istep)[0]); // x
trackimg_v[2].set_pixel(pix[0],b,(float)tyz.at(istep)[1]); // y
trackimg_v[3].set_pixel(pix[0],b,(float)tyz.at(istep)[2]); // z
trackimg_v[4].set_pixel(pix[0],b,(float)E.at(istep)); // E
int flag = (b==pix[3]) ? 0 : 1; // 1: on track, 2: on smear //+1 when filling hit
trackimg_v[5].set_pixel(pix[0],b,(float)flag); // flag
trackimg_v[6].set_pixel(pix[0],b,(float)dWall.at(istep)); //dWall
}
istep++;
}
}
// copy of UBWireTool::getProjectedImagePixel
std::vector<int> FlowContourMatch::getProjectedPixel( const std::vector<float>& pos3d,
const larcv::ImageMeta& meta,
const int nplanes,
const float fracpixborder ) {
std::vector<int> img_coords( nplanes+1, -1 );
float row_border = fabs(fracpixborder)*meta.pixel_height();
float col_border = fabs(fracpixborder)*meta.pixel_width();
// tick/row
float tick = pos3d[0]/(::larutil::LArProperties::GetME()->DriftVelocity()*::larutil::DetectorProperties::GetME()->SamplingRate()*1.0e-3) + 3200.0;
if ( tick<meta.min_y() ) {
if ( tick>meta.min_y()-row_border )
// below min_y-border, out of image
img_coords[0] = meta.rows()-1; // note that tick axis and row indicies are in inverse order (same order in larcv2)
else
// outside of image and border
img_coords[0] = -1;
}
else if ( tick>meta.max_y() ) {
if ( tick<meta.max_y()+row_border )
// within upper border
img_coords[0] = 0;
else
// outside of image and border
img_coords[0] = -1;
}
else {
// within the image
img_coords[0] = meta.row( tick );
}
// Columns
Double_t xyz[3] = { pos3d[0], pos3d[1], pos3d[2] };
// there is a corner where the V plane wire number causes an error
if ( (pos3d[1]>-117.0 && pos3d[1]<-116.0) && pos3d[2]<2.0 ) {
//std::cout << __PRETTY_FUNCTION__ << ": v-plane corner hack (" << xyz[0] << "," << xyz[1] << "," << xyz[2] << ")" << std::endl;
xyz[1] = -116.0;
}
for (int p=0; p<nplanes; p++) {
float wire = larutil::Geometry::GetME()->WireCoordinate( xyz, p );
// round wire
//wire = std::roundf(wire);
// get image coordinates
if ( wire<meta.min_x() ) {
if ( wire>meta.min_x()-col_border ) {
//std::cout << __PRETTY_FUNCTION__ << " plane=" << p << " wire=" << wire << "<" << meta.min_x()-col_border << std::endl;
// within lower border
img_coords[p+1] = 0;
}
else
img_coords[p+1] = -1;
}
else if ( wire>=meta.max_x() ) {
if ( wire<meta.max_x()+col_border ) {
//std::cout << __PRETTY_FUNCTION__ << " plane=" << p << " wire=" << wire << ">" << meta.max_x()+col_border << std::endl;
// within border
img_coords[p+1] = meta.cols()-1;
}
else
// outside border
img_coords[p+1] = -1;
}
else
// inside image
img_coords[p+1] = meta.col( wire );
}//end of plane loop
// there is a corner where the V plane wire number causes an error
if ( pos3d[1]<-116.3 && pos3d[2]<2.0 && img_coords[1+1]==-1 ) {
img_coords[1+1] = 0;
}
return img_coords;
}
// ----------------------------------
// TRUTH MATCH WITH ANCESTOR IMAGES
// ----------------------------------
void FlowContourMatch::label_mcid_w_ancestor_img( const std::vector<larcv::Image2D>& ancestor_v,
const std::vector<larcv::Image2D>& adcimg_v) {
std::vector<HitFlowData_t>* pflowdata[2] = { nullptr, nullptr };
if ( m_plhit2flowdata.ranY2U )
pflowdata[0] = &(m_plhit2flowdata.Y2U);
if ( m_plhit2flowdata.ranY2V )
pflowdata[1] = &(m_plhit2flowdata.Y2V);
if ( !pflowdata[0] && !pflowdata[1] ) {
throw std::runtime_error("[FlowContourMatch::mctrack_match_w_ancestor_img] No hit information filled yet");
}
int nlabeled[2] = {0,0};
for ( int iflow=0; iflow<2; iflow++) {
if ( !pflowdata[iflow] ) continue;
std::vector<HitFlowData_t>& flowdata = *(pflowdata[iflow]);
// ------------------------------------------------
// debug
// char hname[100];
// sprintf( hname, "ancestorlabel_flow%d", iflow );
// TH2D hancestor = larcv::as_th2d( adcimg_v[2], hname );
// hancestor.Reset();
// for ( size_t r=0; r<ancestor_v[2].meta().rows(); r++ ) {
// for ( size_t c=0; c<ancestor_v[2].meta().cols(); c++ ) {
// if ( ancestor_v[2].pixel(r,c)>=0 )
// hancestor.SetBinContent( c+1, r+1, 5 );
// }
// }
// ------------------------------------------------
int maxcol = ancestor_v[2].meta().cols();
int maxrow = ancestor_v[2].meta().rows();
for ( auto& hit : flowdata ) {
if ( hit.srcwire<0 || hit.pixtick<0 ) continue;
int col = ancestor_v[2].meta().col( hit.srcwire );
int row = ancestor_v[2].meta().row( hit.pixtick );
// look in a neighborhood
float maxadc = -1;
int mcid_at_max = -1;
for ( int dc=-3; dc<=3; dc++ ) {
int c=col+dc;
if ( c<0 || c>=maxcol ) continue;
for ( int dr=-10; dr<=10; dr++ ) {
int r=row+dr;
if ( r<0 || r>=maxrow ) continue;
float adc = adcimg_v[2].pixel(r,c);
int mcid = ancestor_v[2].pixel(r,c);
if ( adc>maxadc && mcid>=0 ) {
maxadc = adc;
mcid_at_max = mcid;
}
}
}
hit.trackid = mcid_at_max;
if ( mcid_at_max>=0 ) {
//hancestor.SetBinContent( col+1, row+1, 10 );
nlabeled[iflow]++;
}
else {
//hancestor.SetBinContent( col+1, row+1, -5 );
}
}
std::cout << "[FlowContourMatch::label_mcid_w_ancestor_img] "
<< "Flow path " << iflow << " labeled hits=" << nlabeled[iflow]
<< " of " << flowdata.size() << " all hits"
<< std::endl;
// ---------------------------------
// debug
// gStyle->SetOptStat(0);
// TCanvas c("c","c",800,600);
// hancestor.Draw("colz");
// std::string canvname = std::string(hname)+".png";
// c.SaveAs( canvname.c_str() );
// std::cout << "[FlowContourMatch::label_mcid_w_ancestor_img] [DEBUG] save " << hname << std::endl;
// ---------------------------------
}
}
// =====================================================================
// INTERNAL FUNCTIONS
// --------------------
void FlowContourMatch::_fill_consistency3d(std::vector<HitFlowData_t>& Y2U,
std::vector<HitFlowData_t>& Y2V,
std::vector<int>& consistency3d,
std::vector<float>& dy,
std::vector<float>& dz) {
float ddy =0;
float ddz =0;
if(Y2U.size()<=0 && Y2V.size()<=0) return; //nothing was filled
// if here, at least flow was filled. we allocate consistency vectors
if ( (Y2U.size()>0 && consistency3d.size()!=Y2U.size())
|| (Y2V.size()>0 && consistency3d.size()!=Y2V.size()) ) {
consistency3d.assign( Y2U.size(), -1);
dy.assign( Y2U.size(), -1 );
dz.assign( Y2U.size(), -1 );
}
//first fill 3D coord
if(Y2U.size()>0){
for(int i=0; i<Y2U.size(); i++){
Y2U[i].X.assign( 3, -1);
_calc_coord3d(Y2U[i],Y2U[i].X,FlowContourMatch::kY2U);
}
}
if(Y2V.size()>0){
for(int i=0; i<Y2V.size(); i++){
Y2V[i].X.assign( 3, -1);
_calc_coord3d(Y2V[i],Y2V[i].X,FlowContourMatch::kY2V);
}
}
//if both flow information is provided, we can run consistency measures
if(Y2U.size()>0 && Y2V.size()>0){
//both are same size (by construction)
for(int i=0; i<Y2U.size(); i++){
_calc_dist3d(Y2U[i].X,Y2V[i].X,ddy,ddz);
dy[i] = ddy;
dz[i] = ddz;
consistency3d[i] = _calc_consistency3d(ddy,ddz);
}
} //end of both
}
void FlowContourMatch::_calc_coord3d(HitFlowData_t& hit_y2u,
std::vector<float>& X,
FlowDirection_t flowdir) {
if ( hit_y2u.srcwire<0 || hit_y2u.srcwire>=3456) {
// this hit has no flow-match data
X[0] = -1;
X[1] = -1;
X[2] = -1;
return;
}
// for debug
//std::cout << __PRETTY_FUNCTION__ << "src wire=" << hit_y2u.srcwire << " tar(u)=" << hit_y2u.targetwire << std::endl;
if ( hit_y2u.targetwire<0 || hit_y2u.targetwire>=2400 ) {
// flow is out of the plane (track how this happend).
// for now, we do not have a vaule
X[0] = -1;
X[1] = -1;
X[2] = -1;
return;
}
// larlite geometry tool
const ::larutil::Geometry* geo = ::larutil::Geometry::GetME();
const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; // cm/usec * usec/tick
X[0] = (hit_y2u.pixtick-3200.0)*cm_per_tick;
double y=-1.;
double z=-1.;
//Get intersection
switch ( flowdir ) {
case kY2U:
geo->IntersectionPoint( hit_y2u.srcwire, hit_y2u.targetwire, 2, 0, y, z );
break;
case kY2V:
geo->IntersectionPoint( hit_y2u.srcwire, hit_y2u.targetwire, 2, 1, y, z );
break;
default:
throw std::runtime_error("FlowContourMatch::calc_coord3d: invalid FlowDirection_t option"); // shouldnt be possible
break;
}
X[1] = y;
X[2] = z;
}
void FlowContourMatch::_calc_dist3d(std::vector<float>& X0,
std::vector<float>& X1,
float& dy,
float& dz) {
if ( X0.size()<=0 || X1.size()<=0 || X0.size()!=X1.size() ) {
// one of the 3D positions was not properly initialized: should not happen
dy = -1;
dz = -1;
return;
}
if ( std::any_of(X0.cbegin(), X0.cend(), [](int i){ return i == -1; })
|| std::any_of(X1.cbegin(), X1.cend(), [](int i){ return i == -1; })) {
// one of the flows is out of the plane (track how this happend).
// for now, we do not have a vaule
dy = -1;
dy = -1;
return;
}
dy = sqrt(pow(X1[1]-X0[1],2));
dz = sqrt(pow(X1[2]-X0[2],2));
}
int FlowContourMatch::_calc_consistency3d(float& dy,
float& dz) {
if ( dy<0 || dz<0 )
return larlite::larflow3dhit::kNoValue; // no
float dR = sqrt(dy*dy + dz*dz);
//dR should be in cm?
if(dR<0.5) return larlite::larflow3dhit::kIn5mm; // kIn5mm
if(dR<1.0) return larlite::larflow3dhit::kIn10mm; // kIn10mm
if(dR<5.0) return larlite::larflow3dhit::kIn50mm; // kIn50mm
return larlite::larflow3dhit::kOut50mm; // kOut50mm
}
void FlowContourMatch::_match( FlowDirection_t flowdir,
const ublarcvapp::ContourClusterAlgo& contour_data,
const larcv::Image2D& src_adc,
const larcv::Image2D& tar_adc,
const larcv::Image2D& flow_img,
const larlite::event_hit& hit_v,
const float threshold ) {
// produces 3D hits from from one flow image
int src_planeid = -1;
int tar_planeid = -1;
switch ( flowdir ) {
case kY2U:
src_planeid = 2;
tar_planeid = 0;
break;
case kY2V:
src_planeid = 2;
tar_planeid = 1;
break;
default:
throw std::runtime_error("FlowContourMatch::match: invalid FlowDirection_t option"); // shouldnt be possible
break;
}
// we clear the 2d data, but keep the hit data (which we will update with _make3Dhits)
clear( true, false, (int)flowdir );
// first we create match data within the image
_createMatchData( contour_data, flow_img, src_adc, tar_adc, flowdir );
// use the match data to score contour-contour matching
_scoreMatches( contour_data, src_planeid, tar_planeid, flowdir );
// use score matrix to define matches
_greedyMatch(flowdir);
// make 3D hits and update hit2flowdata vector
if ( flowdir==kY2U )
_make3Dhits( hit_v, src_adc, tar_adc, src_planeid, tar_planeid, threshold, m_plhit2flowdata.Y2U, flowdir );
else if ( flowdir==kY2V )
_make3Dhits( hit_v, src_adc, tar_adc, src_planeid, tar_planeid, threshold, m_plhit2flowdata.Y2V, flowdir );
else
throw std::runtime_error("should never get here");
}
void FlowContourMatch::makeHitsFromWholeImagePixels( const larcv::Image2D& src_adc, larlite::event_hit& evhit_v, const float threshold ) {
// instead of hits, which can be too sparsely defined,
// we can try to match pixels (or maybe eventually groups of pixels).
// to use same machinery, we turn pixels into hits
//
// inputs
// ------
// src_adc: source ADC image
// threshold: ADC threshold
//
// outputs
// -------
// evhit_v (by address): vector of hits created from above threshold pixels
evhit_v.clear();
evhit_v.reserve(10000);
int maxcol = src_adc.meta().cols();
int maxcol_plane = maxcol;
if ( src_adc.meta().plane()<2 ) {
maxcol_plane = src_adc.meta().col(2399)+1; // maxwire
}
else {
maxcol_plane = src_adc.meta().col(3455)+1; // maxwire
}
maxcol = ( maxcol>maxcol_plane) ? maxcol_plane : maxcol;
// we loop over all source pixels and make "hits" for all pixels above threshold
int ihit = 0;
for (int irow=0; irow<(int)src_adc.meta().rows(); irow++) {
float hit_tick = src_adc.meta().pos_y( irow )-2400.0;
for (int icol=0; icol<maxcol; icol++) {
float pixval = src_adc.pixel( irow, icol );
if (pixval<threshold )
continue;
int wire = src_adc.meta().pos_x( icol );
// make fake hit from pixel
larlite::hit h;
h.set_rms( 1.0 );
h.set_time_range( hit_tick, hit_tick );
h.set_time_peak( hit_tick, 1.0 );
h.set_time_rms( 1.0 );
h.set_amplitude( pixval, sqrt(pixval) );
h.set_integral( pixval, sqrt(pixval) );
h.set_sumq( pixval );
h.set_multiplicity( 1 );
h.set_local_index( ihit );
h.set_goodness( 1.0 );
h.set_ndf( 1 );
larlite::geo::WireID wireid( 0, 0, src_adc.meta().plane(), wire );
int ch = larutil::Geometry::GetME()->PlaneWireToChannel( wireid.Plane, wireid.Wire );
h.set_channel( ch );
h.set_view( (larlite::geo::View_t)wireid.Plane );
h.set_wire( wireid );
h.set_signal_type( larutil::Geometry::GetME()->SignalType( ch ) );
evhit_v.emplace_back( std::move(h) );
ihit++;
}
}
}
// ==================================================================================
// Algorithm (internal) Methods
// -----------------------------
void FlowContourMatch::_scoreMatches( const ublarcvapp::ContourClusterAlgo& contour_data, int src_planeid, int tar_planeid, const FlowDirection_t kflowdir ) {
// takes src-target contour pairs and starts to calculate scores
// scores are based on what fraction of pixels get matched from source to the target contour
//
// results use m_flowdata information
//
// things we fill
// --------------
// m_score_matrix: (src,target) contour index are the pos. in the array/matrix. value is score
//
m_src_ncontours = contour_data.m_plane_atomics_v[src_planeid].size();
m_tar_ncontours[(int)kflowdir] = contour_data.m_plane_atomics_v[tar_planeid].size();
// std::cout << __PRETTY_FUNCTION__ << std::endl;
// std::cout << "scr ncontours: " << m_src_ncontours << std::endl;
// std::cout << "tar ncontours: " << m_tar_ncontours << std::endl;
if ( m_score_matrix[kflowdir]!=NULL )
delete m_score_matrix[kflowdir];
m_score_matrix[kflowdir] = new double[m_src_ncontours*m_tar_ncontours[kflowdir]]; // should probably its own class
memset(m_score_matrix[kflowdir], 0, sizeof(double)*m_src_ncontours*m_tar_ncontours[kflowdir] );
for ( auto& flowdata : m_flowdata[kflowdir] ) {
float score = _scoreMatch( flowdata );
flowdata.score = score;
m_score_matrix[kflowdir][ flowdata.src_ctr_id*m_tar_ncontours[kflowdir] + flowdata.tar_ctr_id ] = score;
}
// normalize it
for (int is=0; is<m_src_ncontours; is++) {
float norm_s = 0;
for (int it=0; it<m_tar_ncontours[kflowdir]; it++) {
norm_s += m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ];
}
if (norm_s>0 ) {
for (int it=0; it<m_tar_ncontours[kflowdir]; it++) {
m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ] /= norm_s;
}
}
}
}
float FlowContourMatch::_scoreMatch( const FlowMatchData_t& matchdata ) {
float score = 0.0;
int nscores = 0;
for ( auto const& flow : matchdata.matchingflow_v ) {
score += 1.0/flow.pred_miss;
nscores++;
}
return score;
}
void FlowContourMatch::_greedyMatch(const FlowDirection_t kflowdir) {
// goal is to assign a cluster on the
// source plane purely to one on the target
//
// this function modifies m_score_matrix[kflowdir]
//
for (int is=0; is<m_src_ncontours; is++) {
float max_s = -1.0;
int idx = 0;
for (int it=0; it<m_tar_ncontours[kflowdir]; it++) {
float score = m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ];
if ( score>max_s ) {
max_s = 0;
idx = it;
}
}
if (max_s>0 ) {
for (int it=0; it<m_tar_ncontours[kflowdir]; it++) {
if ( it!=idx )
m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ] = 0;
else
m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ] = 1.0;
}
}
}
}
void FlowContourMatch::dumpMatchData() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
for (int i=0; i<2; i++) {
std::cout << "===Match Data. Direction " << i << " =======" << std::endl;
for ( auto& flowdata : m_flowdata[i] ) {
//std::cout << "[CONTOURS: src(" << it.first[0] << ") -> tar(" << it.first[1] << ")]" << std::endl;
std::cout << "[CONTOURS: src(" << flowdata.src_ctr_id << ") -> tar(" << flowdata.tar_ctr_id << ")]" << std::endl;
std::cout << " Flow entries " << flowdata.matchingflow_v.size() << std::endl;
for ( auto const& flow : flowdata.matchingflow_v ) {
std::cout << " " << flow.row << ": " << flow.src_wire
<< " -> " << flow.tar_wire << " err=" << flow.pred_miss << std::endl;
}
}
}
}
TH2D& FlowContourMatch::plotScoreMatrix(const FlowDirection_t kflowdir) {
if ( m_plot_scorematrix!=NULL ) {
delete m_plot_scorematrix[kflowdir];
}
m_plot_scorematrix[kflowdir] = new TH2D( "h2d_flowmatch_scorematrix", ";Source Contour;Target Contour",
m_src_ncontours, 0, m_src_ncontours,
m_tar_ncontours[kflowdir], 0, m_tar_ncontours[kflowdir] );
for (int is=0; is<m_src_ncontours; is++) {
for (int it=0; it<m_tar_ncontours[kflowdir]; it++) {
m_plot_scorematrix[kflowdir]->SetBinContent( is+1, it+1, m_score_matrix[kflowdir][ is*m_tar_ncontours[kflowdir] + it ] );
}
}
m_plot_scorematrix[kflowdir]->SetMaximum(1.0);
m_plot_scorematrix[kflowdir]->SetMinimum(0.0);
return *(m_plot_scorematrix[kflowdir]);
}
void FlowContourMatch::_make3Dhits( const larlite::event_hit& hit_v,
const larcv::Image2D& srcimg_adc,
const larcv::Image2D& tar_adc,
const int src_plane,
const int tar_plane,
const float threshold,
std::vector<HitFlowData_t>& hit2flowdata,
const FlowDirection_t kflowdir ) {
// make3Dhits
// turn flow predictions and contour matches into 3D hits
//
// inputs
// ------
// hit_v: vector of hits found on the different planes
// srcimg_adc: source ADC image
// tar_adc: target ADC image
// src_plane: plane ID of source image
// tar_plane: plane ID of target image
// threshold: ADC threshold used to determine if landed on pixel w/ charge
//
// implicit input from class members
// ----------------------------------
// m_score_matrix: tells us which target contours most strongly associated to cluster (from scoreMatches)
// m_flowdata: data of what clusters associated to which (createMatchData)
// m_src_img2ctrindex: array mapping (row,col) to contour index for source image (createMatchData)
// m_tar_img2ctrindex: array mapping (row,col) to contour index for target image (createMatchData)
//
// outputs
// -------
// hit2flowdata: this vector is constant for a whole event image, and gets updated for each sub-image.
// it stores where we think we should put the target hit -- note this might be a modification of the
// the initial flow prediction
//
// description of method
// ---------------------
// 1) loop over hits
// 2) for each hit, determine if it is within a source contour using m_src_img2ctrindex
// 3) using flow image, determine target pixel
// 4) determine match quality
// 5) depending on match quality, determine matching target column
// 6) convert source and target columns into wires, row into ticks
// 7) turn that information, make 3D hit position (X-axis relative to trigger time)
int _verbosity = 1;
if ( hit2flowdata.size()!=hit_v.size() ) {
if (_verbosity>0 )
std::cout << "hit2flowdata vector is reset" << std::endl;
hit2flowdata.clear();
hit2flowdata.resize( hit_v.size() );
}
// for (int hitidx=0; hitidx<(int)hit_v.size(); hitidx++) {
// // get hit for this index
// const larlite::hit& ahit = hit_v[hitidx];
// std::cout << "hit[" << hitidx << "]: " << ahit.StartTick() << std::endl;
// }
// std::cin.get();
int numscoredhits = 0;
int not_on_src_plane = 0;
int not_in_bounds = 0;
int src_col_nomatch = 0;
int no_contour = 0;
for (int hitidx=0; hitidx<(int)hit_v.size(); hitidx++) {
// get hit for this index
const larlite::hit& ahit = hit_v[hitidx];
// is this on the source plane? if not, skip
if ( src_plane!=(int)ahit.WireID().planeID().Plane ) {
not_on_src_plane++;
continue;
}
// is it in the image (crop)
// time limits
int hit_tstart = 2400+(int)ahit.StartTick();
int hit_tend = 2400+(int)ahit.EndTick();
if ( hit_tend < m_srcimg_meta->min_y() || hit_tstart >= m_srcimg_meta->max_y() ) {
// if not within the tick bounds, skip
not_in_bounds++;
continue;
}
if ( hit_tend >= m_srcimg_meta->max_y() )
hit_tend = m_srcimg_meta->pos_y( m_srcimg_meta->rows()-1 );
if ( hit_tstart < m_srcimg_meta->min_y() )
hit_tstart = m_srcimg_meta->min_y();
// wire bounds
int wire = (int)ahit.WireID().Wire;
if ( wire < m_srcimg_meta->min_x() || wire >= m_srcimg_meta->max_x() ) {
// if not within wire bounds, skip
not_in_bounds++;
continue;
}
// translate wire and ticks into col and row
int wirecol = m_srcimg_meta->col( wire );
int rowstart = m_srcimg_meta->row( hit_tstart );
int rowend = m_srcimg_meta->row( hit_tend );
if ( _verbosity>1 )
std::cout << "---------------------------------------" << std::endl;
if ( _verbosity>1 )
std::cout << "valid hit: " << hitidx << " wire=" << wire << " wirecol=" << wirecol
<< " tickrange=[" << hit_tstart << "," << hit_tend << "]"
<< " rowrange=[" << rowstart << "," << rowend << "]"
<< std::endl;
// ok our hit is in the image. match the hit to a contour
// we loop through source image contours and check if any of their pixels are inside the hit tick range.
bool foundcontour = false;
int sourcecontourindex = -1;
for ( auto const& it_ctr : m_src_targets[kflowdir] ) {
int src_ctridx = it_ctr.first; // source image contour index
const ContourTargets_t& ctrtargets = it_ctr.second; // list of src and target pixels
// loop over pixels within this source contour, find those that fit within given hit above
for ( auto const& pixinfo : ctrtargets ) {
//std::cout << " srcctr[" << src_ctridx << "] (" << pixinfo.row << "," << pixinfo.srccol << ")" << std::endl;
if ( wirecol!=pixinfo.srccol ) {
//std::cout << "source contour pixel does not match, skip. wirecol=" << wirecol << " pixinfo.srccol=" << pixinfo.srccol << std::endl;
src_col_nomatch++;
continue;
}
if ( rowstart <= pixinfo.row && pixinfo.row <= rowend ) {
// found the overlap
sourcecontourindex = src_ctridx;
foundcontour = true;
if ( _verbosity>1 )
std::cout << "source contour pixel within hit. source contour=" << src_ctridx
<< " source(r,c)=(" << pixinfo.row << "," << pixinfo.srccol << ")"
<< " w/ flow to target(r,c)=(" << pixinfo.row << "," << pixinfo.col << ")"
<< std::endl;
// update the src/target pix from larflow based on
// 1) adc value of source pixel
// 2) match quality
float src_adc = srcimg_adc.pixel( pixinfo.row, pixinfo.srccol );
// match quality
// 1-best) target pixel inside (primary?) contour and on charge
// 2) target pixel inside contour -- find closest charge inside contour
// 3) target pixel outside contour -- find closest charge inside best-score contour
// x) can provide a null result too. when truth is in dead region, the resulting hit can often be poor.
// eventually we would use matchability here. alternatively, during tracking pass, we can infer if
// next step is inside dead region. if flow prediction at this point is wildly off, then we throw
// out hit. but there is nothing one can do for that approach here.
// get the data for this hit
HitFlowData_t& hitdata = hit2flowdata[hitidx];
// so we first calculate the hit quality, using the flow, src image, target image,
// and src-target contour matches (via score matrix)
int matchquality = -1; // << starting value
int pastquality = hitdata.matchquality;
int dist2center = std::abs( pixinfo.srccol - m_srcimg_meta->cols()/2 );
int dist2charge = -1;
if ( _verbosity>1 )
std::cout << " -- past hitquality=" << pastquality << std::endl;
// does target point to charge? look within a range of wires
int tarcolmin = pixinfo.col-kTargetChargeRadius;
int tarcolmax = pixinfo.col+kTargetChargeRadius;
if ( tarcolmin<0 )
tarcolmin = 0;
if ( tarcolmax>=(int)m_tarimg_meta[kflowdir]->cols() )
tarcolmax = (int)m_tarimg_meta[kflowdir]->cols() - 1;
// we look for the peak adc value between tarcolmin and tarcolmax
float target_adc = 0; // <<
int target_col = pixinfo.col; // << default, set to flow prediction
bool oncharge = false;
for (int tarcol=tarcolmin; tarcol<=tarcolmax; tarcol++) {
float tadc = tar_adc.pixel( pixinfo.row, tarcol );
if ( target_adc<tadc ) {
target_adc = tadc;
target_col = tarcol;
}
}
if ( target_adc>threshold ) {
oncharge = true;
}
// is this pixel in a (matched) contour
bool incontour = false;
int target_contour = m_tar_img2ctrindex[kflowdir][ int(pixinfo.row*m_tarimg_meta[kflowdir]->cols() + target_col) ];
if ( target_contour>0 ) {
incontour = true;
}
// CONDITIONS FOR QUALITY LEVEL
if ( incontour && oncharge ) {
// quality level 1: oncharge and incontour
matchquality = 1;
dist2charge = 0;
if ( _verbosity>1 )
std::cout << " -- hit quality=1 " << " past(" << pastquality << ") " << std::endl;
}//end of quality 1 condition
else if ( incontour && !oncharge && (pastquality<0 || pastquality>=2) ) {
// quality level 2: incontour but not on charge
// we calculate the matched pixel for this case if in the past we didnt get a better match
matchquality = 2;
if ( _verbosity>1 )
std::cout << " -- hit quality=2 " << " past(" << pastquality << ") " << std::endl;
// we look for the closest pixel inside the contour that has charge, and that is what we match to
int possearch_col = target_col+1;
if ( possearch_col<0 )
possearch_col = 0;
if ( possearch_col>=m_tarimg_meta[kflowdir]->cols() )
possearch_col = m_tarimg_meta[kflowdir]->cols()-1;
while ( possearch_col<(int)m_tarimg_meta[kflowdir]->cols() && possearch_col-target_col<30 && possearch_col>target_col) {
if ( m_tar_img2ctrindex[kflowdir][ int(pixinfo.row*m_tarimg_meta[kflowdir]->cols() + possearch_col) ]==target_contour ) {
// column in contour
float tadc = tar_adc.pixel( pixinfo.row, possearch_col );
if ( tadc>threshold ) {
break;
}
}
possearch_col++;
}
int negsearch_col = target_col-1;
if ( negsearch_col<0 )
negsearch_col = 0;
if ( negsearch_col>=m_tarimg_meta[kflowdir]->cols() )
negsearch_col = m_tarimg_meta[kflowdir]->cols()-1;
while ( negsearch_col>=0 && target_col-negsearch_col<30 && negsearch_col < target_col ) {
if ( m_tar_img2ctrindex[kflowdir][ int(pixinfo.row*m_tarimg_meta[kflowdir]->cols() + negsearch_col) ]==target_contour ) {
// column in contour
float tadc = tar_adc.pixel( pixinfo.row, negsearch_col );
if ( tadc>threshold ) {
break;
}
}
negsearch_col--;
}
// bound results
if ( negsearch_col<0 )
negsearch_col = 0;
if ( possearch_col>=m_tarimg_meta[kflowdir]->cols() )
possearch_col = m_tarimg_meta[kflowdir]->cols()-1;
int negdist = abs(negsearch_col-target_col);
int posdist = abs(possearch_col-target_col);
if ( negdist < posdist ) {
target_col = negsearch_col;
dist2charge = negdist;
}
else {
target_col = possearch_col;
dist2charge = posdist;
}
target_adc = tar_adc.pixel( pixinfo.row, target_col );
}// end of quality 2
else if ( !incontour && (pastquality<0 || pastquality>=3) ) {
// quality 3: out of contour and out of charge
// we calculate the matched pixel for this case if in the past we didnt get a better match
// doing the best we can
matchquality = 3;
if ( _verbosity>1 )
std::cout << " -- hit quality=3 " << " past(" << pastquality << ") starting search from target_col=" << target_col << std::endl;
// check the best matching contour first for charge to match
// we search in a neighborhood around the predicted point
// we track all the contours we cross into
// we use the contour with the best value from m_score_matrix
// take the pixel using the best match
std::vector<ClosestContourPix_t> matched_contour_list;
std::set<int> used_contours;
bool found_candidate_contour = false;
int possearch_col = target_col+1;
if ( possearch_col<0 )
possearch_col = 0;
if ( possearch_col>=m_tarimg_meta[kflowdir]->cols() )
possearch_col = m_tarimg_meta[kflowdir]->cols()-1;
while ( possearch_col<(int)m_tarimg_meta[kflowdir]->cols() && possearch_col>target_col && possearch_col-target_col<50 ) {
float tadc = tar_adc.pixel( pixinfo.row, possearch_col );
if ( tadc > threshold ) {
int target_contour_idx = m_tar_img2ctrindex[kflowdir][ int(pixinfo.row*m_tarimg_meta[kflowdir]->cols() + possearch_col) ];
if ( used_contours.find( target_contour_idx )==used_contours.end() ) {
// have not search this contour, provide a match candidate
ClosestContourPix_t close_ctr_info;
close_ctr_info.ctridx = target_contour_idx;
close_ctr_info.dist = abs(possearch_col - target_col);
close_ctr_info.col = possearch_col;
close_ctr_info.adc = tadc;
close_ctr_info.scorematch = m_score_matrix[kflowdir][ int(src_ctridx*m_tar_ncontours[kflowdir] + target_contour_idx) ];
matched_contour_list.push_back( close_ctr_info );
used_contours.insert( target_contour_idx );
found_candidate_contour = true;
}
}
possearch_col++;
}//end of pos loop
int negsearch_col = target_col-1;
if ( negsearch_col<0 )
negsearch_col = 0;
if ( negsearch_col>=m_tarimg_meta[kflowdir]->cols() )
negsearch_col = m_tarimg_meta[kflowdir]->cols()-1;
while ( negsearch_col>=0 && target_col-negsearch_col<50 && negsearch_col < target_col) {
float tadc = tar_adc.pixel( pixinfo.row, negsearch_col );
if ( tadc > threshold ) {
int target_contour_idx = m_tar_img2ctrindex[kflowdir][ int(pixinfo.row*m_tarimg_meta[kflowdir]->cols() + negsearch_col) ];
if ( used_contours.find( target_contour_idx )==used_contours.end() ) {
// have not search this contour, provide a match candidate
ClosestContourPix_t close_ctr_info;
close_ctr_info.ctridx = target_contour_idx;
close_ctr_info.dist = abs(negsearch_col - target_col);
close_ctr_info.col = negsearch_col;
close_ctr_info.adc = tadc;
close_ctr_info.scorematch = m_score_matrix[kflowdir][ src_ctridx*m_tar_ncontours[kflowdir] + target_contour_idx ];
matched_contour_list.push_back( close_ctr_info );
used_contours.insert( target_contour_idx );
found_candidate_contour = true;
}
}
negsearch_col--;
}//end of neg loop
// ok, now we pick the best one! if we found a candidate that is
float best_score = 0.0;
float best_adc = 0.0;
int best_col = -1;
int best_dist = -1;
if ( found_candidate_contour ) {
for ( auto& match : matched_contour_list ) {
if ( best_score < match.scorematch ) {
best_col = match.col;
best_score = match.scorematch;
best_dist = match.dist;
best_adc = match.adc;
}
}
target_col = best_col;
dist2charge = best_dist;
target_adc = best_adc;
}
else {
// didn't find a nearby contour
target_col = -1; // indicates that no good target found
matchquality = -1;
}
}
// did we do better?
bool update_hitdata = false;
if ( matchquality>0 && target_col>=0 ) {
// target_col<0 indicates no good target found (happens when true target pixel in dead region)
//we found a case where we did better or the same
if ( matchquality<pastquality || pastquality<0 ) {
// we did better. replace the hit
update_hitdata = true;
}
else {
if ( matchquality==1 && hitdata.dist2center>dist2center) {
// we decide on this by using the src pixel closest to the center of the y-image
update_hitdata = true;
}
else if ( matchquality==2 && hitdata.dist2charge>dist2charge ) {
// we decide on this by using which flow prediction was closest to the eventual charge match
update_hitdata = true;
}
else if ( matchquality==3 && hitdata.dist2charge>dist2charge ) {
// same criterion as 2
update_hitdata = true;
}
}
}
if ( matchquality<0 && pastquality<0 ) {
// matchquality is currenly not set still (didn't find a good match)
// we want to set the default to for the hitdata
hitdata.maxamp = 0;
hitdata.hitidx = hitidx;
hitdata.srcwire = m_srcimg_meta->pos_x( pixinfo.srccol );
hitdata.targetwire = m_tarimg_meta[kflowdir]->pos_x( pixinfo.col );
hitdata.pixtick = m_srcimg_meta->pos_y( pixinfo.row );
hitdata.matchquality = -1;
hitdata.dist2center = 10000; // large sentinal value
hitdata.dist2charge = 10000; // large sentinal value
hitdata.src_ctr_idx = sourcecontourindex;
hitdata.tar_ctr_idx = -1;
if ( _verbosity>1 )
std::cout << " -- set default for unmatched hit [srccol=" << pixinfo.srccol << "] [targetcol=" << pixinfo.col << "]" << std::endl;
}
if ( update_hitdata ) {
if ( _verbosity>1 )
std::cout << " -- update hit flow data" << std::endl;
hitdata.maxamp = target_adc;
hitdata.hitidx = hitidx;
hitdata.srcwire = m_srcimg_meta->pos_x( pixinfo.srccol );
hitdata.targetwire = m_tarimg_meta[kflowdir]->pos_x( target_col ); //< we used the column we found
hitdata.pixtick = m_srcimg_meta->pos_y( pixinfo.row );
hitdata.matchquality = matchquality;
hitdata.dist2center = dist2center;
hitdata.dist2charge = dist2charge;
hitdata.src_ctr_idx = sourcecontourindex;
hitdata.tar_ctr_idx = target_contour;
}
}//if row within hit row range
}//end of ctrtargets pixel loop
}//end of loop over list of src-target pairs
// did it find a source contour?
if ( !foundcontour ) {
if ( _verbosity>1 )
std::cout << "pixel src(r,c)=(" << (rowstart+rowend)/2 << "," << wirecol << ") does not have matching source contour." << std::endl;
no_contour++;
}
else {
numscoredhits++;
}
}//end of hit index loop
std::cout << "number of scored hits: " << numscoredhits
<< " (no_contour=" << no_contour << ","
<< " src_col_nomatch=" << src_col_nomatch << ","
<< " not_in_bounds=" << not_in_bounds << ","
<< " not_on_srcplane=" << not_on_src_plane << ")"
<< std::endl;
return;
}
std::vector<larlite::larflow3dhit> FlowContourMatch::get3Dhits_1pl( FlowDirection_t flowdir, bool makehits_for_nonmatches ) {
// we convert the information we've compiled in m_hit2flowdata, which provides our best guess
// as the correct source-column + target-column pair.
// the objects of this container also contain info about matchquality
if ( flowdir==kY2U )
return get3Dhits_1pl( m_plhit2flowdata.Y2U, makehits_for_nonmatches );
else if ( flowdir==kY2V )
return get3Dhits_1pl( m_plhit2flowdata.Y2U, makehits_for_nonmatches );
else
throw std::runtime_error("should not get here");
}
std::vector<larlite::larflow3dhit> FlowContourMatch::get3Dhits_1pl( const std::vector<HitFlowData_t>& hit2flowdata, bool makehits_for_nonmatches ) {
// now we have, in principle, the best/modified flow prediction for hits that land on flow predictions
// we can make 3D hits!
std::vector<larlite::larflow3dhit> output_hit3d_v;
for (int hitidx=0; hitidx<(int)hit2flowdata.size(); hitidx++) {
const HitFlowData_t& hitdata = hit2flowdata[ hitidx ];
if ( hitdata.matchquality<=0 && !makehits_for_nonmatches ) {
//std::cout << "no good match for hitidx=" << hitidx << ", skip this hit if we haven't set the makehits_for_nonmatches flag" << std::endl;
continue;
}
// otherwise make a hit
larlite::larflow3dhit flowhit;
flowhit.idxhit = hitidx;
flowhit.tick = hitdata.pixtick;
flowhit.srcwire = hitdata.srcwire;
flowhit.targetwire[0] = hitdata.targetwire;
flowhit[0] = hitdata.X[0];
flowhit[1] = hitdata.X[1];
flowhit[2] = hitdata.X[2];
flowhit.dy = -1.;
flowhit.dz = -1.;
flowhit.consistency3d=larlite::larflow3dhit::kNoValue;
flowhit.X_truth[0] = hitdata.X_truth[0];
flowhit.X_truth[1] = hitdata.X_truth[1];
flowhit.X_truth[2] = hitdata.X_truth[2];
flowhit.trackid = hitdata.trackid;
flowhit.dWall = hitdata.dWall;
switch ( hitdata.matchquality ) {
case 1:
flowhit.matchquality=larlite::larflow3dhit::kQandCmatch;
break;
case 2:
flowhit.matchquality=larlite::larflow3dhit::kCmatch;
break;
case 3:
flowhit.matchquality=larlite::larflow3dhit::kClosestC;
break;
default:
flowhit.matchquality=larlite::larflow3dhit::kNoMatch;
break;
}
switch ( hitdata.truthflag ) {
case 0:
flowhit.truthflag=larlite::larflow3dhit::kNoTruthMatch;
break;
case 1:
flowhit.truthflag=larlite::larflow3dhit::kOnTrack;
break;
case 2:
flowhit.truthflag=larlite::larflow3dhit::kOnSmear;
break;
default:
flowhit.truthflag=larlite::larflow3dhit::kUnknown;
break;
}
output_hit3d_v.emplace_back( flowhit );
}
return output_hit3d_v;
}
std::vector<larlite::larflow3dhit> FlowContourMatch::get3Dhits_2pl( bool makehits_for_nonmatches, bool require_3Dconsistency ) {
// we convert the information we've compiled in m_hit2flowdata, which provides our best guess
// as the correct source-column + target-column pair.
// the objects of this container also contain info about matchquality
return get3Dhits_2pl( m_plhit2flowdata, makehits_for_nonmatches, require_3Dconsistency );
}
std::vector<larlite::larflow3dhit> FlowContourMatch::get3Dhits_2pl( const PlaneHitFlowData_t& plhit2flowdata, bool makehits_for_nonmatches, bool require_3Dconsistency ) {
// now we have, in principle, the best/modified flow prediction for hits that land on flow predictions
// here we choose which of the two predictions to keep (for each hit)
std::vector<larlite::larflow3dhit> output_hit3d_v;
// case 1, we only ran one flow direction
if ( !plhit2flowdata.ranY2U || !plhit2flowdata.ranY2V ) {
// determine which one we ran
FlowDirection_t flowdir = ( plhit2flowdata.ranY2U ) ? kY2U : kY2V;
const std::vector<HitFlowData_t>& hit2flowdata = ( plhit2flowdata.ranY2U ) ? plhit2flowdata.Y2U : plhit2flowdata.Y2V;
for (int hitidx=0; hitidx<(int)hit2flowdata.size(); hitidx++) {
const HitFlowData_t& hitdata = hit2flowdata[ hitidx ];
if ( hitdata.matchquality<=0 && !makehits_for_nonmatches ) {
//std::cout << "no good match for hitidx=" << hitidx << ", skip this hit if we haven't set the makehits_for_nonmatches flag" << std::endl;
continue;
}
// otherwise make a hit
larlite::larflow3dhit flowhit;
flowhit.resize(3,-1);
flowhit.idxhit = hitidx;
flowhit.tick = hitdata.pixtick;
flowhit.srcwire = hitdata.srcwire;
flowhit.targetwire[0] = hitdata.targetwire;
flowhit[0] = hitdata.X[0];
flowhit[1] = hitdata.X[1];
flowhit[2] = hitdata.X[2];
flowhit.dy = -1; // no 3d consistency calc
flowhit.dz = -1; // no 3d consistency calc
flowhit.track_score = hitdata.track_score;
flowhit.shower_score = hitdata.shower_score;
flowhit.endpt_score = hitdata.endpt_score;
flowhit.renormed_track_score = hitdata.renormed_track_score;
flowhit.renormed_shower_score = hitdata.renormed_shower_score;
flowhit.src_infill = (unsigned short)(hitdata.src_infill);
flowhit.tar_infill[0] = (unsigned short)(hitdata.tar_infill);
flowhit.X_truth[0] = hitdata.X_truth[0];
flowhit.X_truth[1] = hitdata.X_truth[1];
flowhit.X_truth[2] = hitdata.X_truth[2];
flowhit.trackid = hitdata.trackid;
flowhit.dWall = hitdata.dWall;
switch ( hitdata.matchquality ) {
case 1:
flowhit.matchquality=larlite::larflow3dhit::kQandCmatch;
break;
case 2:
flowhit.matchquality=larlite::larflow3dhit::kCmatch;
break;
case 3:
flowhit.matchquality=larlite::larflow3dhit::kClosestC;
break;
default:
flowhit.matchquality=larlite::larflow3dhit::kNoMatch;
break;
}
flowhit.consistency3d=larlite::larflow3dhit::kNoValue;
switch ( hitdata.truthflag ) {
case 0:
flowhit.truthflag=larlite::larflow3dhit::kNoTruthMatch;
break;
case 1:
flowhit.truthflag=larlite::larflow3dhit::kOnTrack;
break;
case 2:
flowhit.truthflag=larlite::larflow3dhit::kOnSmear;
break;
default:
flowhit.truthflag=larlite::larflow3dhit::kUnknown;
break;
}
output_hit3d_v.emplace_back( flowhit );
}
}// end of only 1 ran
//case 2: we ran Y2U and Y2V
else if(plhit2flowdata.ranY2U && plhit2flowdata.ranY2V){
std::cout << "Picking hits using 2-flow information" << std::endl;
const std::vector<HitFlowData_t>& hit2flowdata_y2u = plhit2flowdata.Y2U;
const std::vector<HitFlowData_t>& hit2flowdata_y2v = plhit2flowdata.Y2V;
//we only loop over length of one, they should be same anyway
for (int hitidx=0; hitidx<(int)hit2flowdata_y2u.size(); hitidx++) {
const HitFlowData_t& hitdata0 = hit2flowdata_y2u[ hitidx ];
const HitFlowData_t& hitdata1 = hit2flowdata_y2v[ hitidx ];
HitFlowData_t hitdata;
//select here
larlite::larflow3dhit::FlowDirection_t used_dir = larlite::larflow3dhit::kY2U;
if(require_3Dconsistency && plhit2flowdata.consistency3d[ hitidx ]>3) continue; //if require_3Dconsistency, skip if no consistency
if(hitdata0.matchquality<0 && hitdata1.matchquality<0 && !makehits_for_nonmatches ) {
//std::cout << "no good match for hitidx=" << hitidx << ", skip this hit if we haven't set the makehits_for_nonmatches flag" << std::endl;
continue;
}
else if(hitdata0.matchquality<0 && hitdata1.matchquality<0 && makehits_for_nonmatches) {
//neither has a match. pick the one that does not land on infill
if(hitdata0.tar_infill && !hitdata1.tar_infill){
hitdata = hitdata1;
used_dir = larlite::larflow3dhit::kY2V;
}
else if(!hitdata0.tar_infill && hitdata1.tar_infill){
hitdata = hitdata0;
}
else{
hitdata = hitdata0; //neither has a match, same infill, pick y2u
}
}
else if(hitdata0.matchquality>=0 && hitdata1.matchquality>=0 && hitdata0.matchquality==hitdata1.matchquality) {
//same quality, pick the one that does not land on infill
if(hitdata0.tar_infill && !hitdata1.tar_infill){
hitdata = hitdata1;
used_dir = larlite::larflow3dhit::kY2V;
}
else if(hitdata0.tar_infill && !hitdata1.tar_infill){
hitdata = hitdata0;
}
else{
hitdata = hitdata0; //same infill, pick y2u
}
}
else if((hitdata0.matchquality<0 && hitdata1.matchquality>=0)
|| (hitdata0.matchquality>=0 && hitdata1.matchquality>=0 && hitdata0.matchquality < hitdata1.matchquality)) {
hitdata = hitdata1; //y2v better matchqual
used_dir = larlite::larflow3dhit::kY2V;
}
else if((hitdata1.matchquality<0 && hitdata0.matchquality>=0)
|| (hitdata1.matchquality>=0 && hitdata0.matchquality>=0 && hitdata1.matchquality < hitdata0.matchquality)) {
hitdata = hitdata0; //y2u better matchqual
}
else{
hitdata = hitdata0;
}
//make a hit
larlite::larflow3dhit flowhit;
flowhit.resize(3,-1);
flowhit.idxhit = hitidx;
flowhit.tick = hitdata.pixtick;
flowhit.srcwire = hitdata.srcwire;
flowhit.flowdir = used_dir;
flowhit.targetwire[0] = hitdata0.targetwire;
flowhit.targetwire[1] = hitdata1.targetwire;
flowhit[0] = hitdata.X[0];
flowhit[1] = hitdata.X[1];
flowhit[2] = hitdata.X[2];
flowhit.dy = plhit2flowdata.dy[ hitidx ];
flowhit.dz = plhit2flowdata.dz[ hitidx ];
flowhit.track_score = hitdata.track_score;
flowhit.shower_score = hitdata.shower_score;
flowhit.endpt_score = hitdata.endpt_score;
flowhit.renormed_track_score = hitdata.renormed_track_score;
flowhit.renormed_shower_score = hitdata.renormed_shower_score;
flowhit.src_infill = (unsigned short)(hitdata.src_infill);
flowhit.tar_infill[0] = (unsigned short)(hitdata0.tar_infill);
flowhit.tar_infill[1] = (unsigned short)(hitdata1.tar_infill);
flowhit.X_truth[0] = hitdata.X_truth[0];
flowhit.X_truth[1] = hitdata.X_truth[1];
flowhit.X_truth[2] = hitdata.X_truth[2];
flowhit.trackid = hitdata.trackid;
flowhit.dWall = hitdata.dWall;
//std::cout <<"hitdata dWall "<< hitdata.dWall <<" flowhit dWall " << flowhit.dWall << std::endl;
switch ( hitdata.matchquality ) {
case 1:
flowhit.matchquality=larlite::larflow3dhit::kQandCmatch;
break;
case 2:
flowhit.matchquality=larlite::larflow3dhit::kCmatch;
break;
case 3:
flowhit.matchquality=larlite::larflow3dhit::kClosestC;
break;
default:
flowhit.matchquality=larlite::larflow3dhit::kNoMatch;
break;
}
switch ( plhit2flowdata.consistency3d[ hitidx ] ) {
case 0:
flowhit.consistency3d=larlite::larflow3dhit::kIn5mm;
break;
case 1:
flowhit.consistency3d=larlite::larflow3dhit::kIn10mm;
break;
case 2:
flowhit.consistency3d=larlite::larflow3dhit::kIn50mm;
break;
case 3:
flowhit.consistency3d=larlite::larflow3dhit::kOut50mm;
break;
default:
flowhit.consistency3d=larlite::larflow3dhit::kNoValue;
break;
}
switch ( hitdata.truthflag ) {
case 0:
flowhit.truthflag=larlite::larflow3dhit::kNoTruthMatch;
break;
case 1:
flowhit.truthflag=larlite::larflow3dhit::kOnTrack;
break;
case 2:
flowhit.truthflag=larlite::larflow3dhit::kOnSmear;
break;
default:
flowhit.truthflag=larlite::larflow3dhit::kUnknown;
break;
}
output_hit3d_v.emplace_back( flowhit );
}
}// end of both ran
return output_hit3d_v;
}
std::vector<larcv::Image2D> FlowContourMatch::makeStitchedFlowImages( const std::vector<larcv::Image2D>& img_v ) {
std::vector< larlite::larflow3dhit > hit_v = get3Dhits_2pl( false, false );
std::vector<larcv::Image2D> outimg_v;
larcv::Image2D y2uimg( img_v[0].meta() );
larcv::Image2D y2vimg( img_v[1].meta() );
larcv::Image2D srcimg( img_v[2].meta() );
y2uimg.paint(0.0);
y2vimg.paint(0.0);
srcimg.paint(0.0);
std::cout << "[FlowContourMatch::makeStichedFlowImages] start" << std::endl;
for (int i=0; i<3; i++)
std::cout << " " <<img_v[i].meta().dump() << std::endl;
for ( auto& hit : hit_v ) {
if ( hit[0]==hit[2] && hit[0]==hit[1] && hit[0]==-1 ) continue; // bad hit
if ( hit.tick >=img_v[2].meta().min_y() && hit.tick<img_v[2].meta().max_y()
&& hit.srcwire>=img_v[2].meta().min_x() && hit.srcwire<img_v[2].meta().max_x() ) {
int row = img_v[2].meta().row( hit.tick );
int col = img_v[2].meta().col( hit.srcwire );
//std::cout << "hit: src(" << row << "," << col << ")";
if ( hit.flowdir==larlite::larflow3dhit::kY2U ) {
srcimg.set_pixel( row, col, hit.targetwire[0]-hit.srcwire );
if ( img_v[0].meta().min_x()<=hit.targetwire[kY2U] && img_v[0].meta().max_x()>hit.targetwire[kY2U] ) {
y2uimg.set_pixel( row, img_v[0].meta().col(hit.targetwire[0]), img_v[2].pixel(row,col) );
//std::cout << " target(" << row << "," << img_v[0].meta().col(hit.targetwire[0]) << ")";
}
}
else {
srcimg.set_pixel( row, col, hit.targetwire[1]-hit.srcwire );
if ( img_v[1].meta().min_x()<=hit.targetwire[kY2V] && img_v[1].meta().max_x()>hit.targetwire[kY2V] ) {
y2vimg.set_pixel( row, img_v[1].meta().col(hit.targetwire[kY2V]), img_v[2].pixel(row,col) );
//std::cout << " target(" << row << "," << img_v[1].meta().col(hit.targetwire[1]) << ")";
}
}
//std::cout << std::endl;
}// if inside image
}// hit loop
outimg_v.emplace_back( std::move(y2uimg) );
outimg_v.emplace_back( std::move(y2vimg) );
outimg_v.emplace_back( std::move(srcimg) );
return outimg_v;
}
}