Program Listing for File NuVertexMaker.cxx

Return to documentation for file (larflow/Reco/NuVertexMaker.cxx)

#include "NuVertexMaker.h"

#include "geofuncs.h"
#include "DataFormat/track.h"

#include "NuVertexFitter.h"

namespace larflow {
namespace reco {


  NuVertexMaker::NuVertexMaker()
    : larcv::larcv_base("NuVertexMaker"),
    _ana_tree(nullptr)
  {
    _set_defaults();
  }

  void NuVertexMaker::process( larcv::IOManager& iolcv,
                               larlite::storage_manager& ioll )
  {

    // load keypoints
    LARCV_INFO() << "Number of keypoint producers: " << _keypoint_producers.size() << std::endl;
    for ( auto it=_keypoint_producers.begin(); it!=_keypoint_producers.end(); it++ ) {
      LARCV_INFO() << "Load keypoint data with tree name[" << it->first << "]" << std::endl;
      it->second = (larlite::event_larflow3dhit*)ioll.get_data( larlite::data::kLArFlow3DHit, it->first );
      auto it_pca = _keypoint_pca_producers.find( it->first );
      if ( it_pca==_keypoint_pca_producers.end() ) {
        _keypoint_pca_producers[it->first] = nullptr;
        it_pca = _keypoint_pca_producers.find( it->first );
      }
      it_pca->second = (larlite::event_pcaxis*)ioll.get_data( larlite::data::kPCAxis, it->first );
      LARCV_INFO() << "keypoints from [" << it->first << "]: " << it->second->size() << " keypoints" << std::endl;
    }

    // load clusters
    LARCV_INFO() << "Number of cluster producers: " << _cluster_producers.size() << std::endl;
    for ( auto it=_cluster_producers.begin(); it!=_cluster_producers.end(); it++ ) {
      LARCV_INFO() << "Load cluster data with tree name[" << it->first << "]" << std::endl;
      it->second = (larlite::event_larflowcluster*)ioll.get_data( larlite::data::kLArFlowCluster, it->first );
      auto it_pca = _cluster_pca_producers.find( it->first );
      if ( it_pca==_cluster_pca_producers.end() ) {
        _cluster_pca_producers[it->first] = nullptr;
        it_pca = _cluster_pca_producers.find( it->first );
      }
      it_pca->second = (larlite::event_pcaxis*)ioll.get_data( larlite::data::kPCAxis, it->first );
      LARCV_INFO() << "clusters from [" << it->first << "]: " << it->second->size() << " clusters" << std::endl;
    }

    _createCandidates();
    _merge_candidates();
    if ( _apply_cosmic_veto ) {
      _cosmic_veto_candidates( ioll );
    }
    LARCV_INFO() << "Num NuVertexCandidates: created=" << _vertex_v.size()
                 << "  after-merging=" << _merged_v.size()
                 << "  after-veto=" << _vetoed_v.size()
                 << std::endl;

    _refine_position( iolcv, ioll );

  }

  void NuVertexMaker::_createCandidates()
  {

    LARCV_DEBUG() << "Associate clusters to vertices via impact par and gap distance" << std::endl;

    // loop over vertices, calculate impact parameters to all clusters, keep if close enough.
    // limit pairings by gap distance (different for shower and track)

    // make vertex objects
    std::vector<NuVertexCandidate> seed_v;
    for ( auto it=_keypoint_producers.begin(); it!=_keypoint_producers.end(); it++ ) {
      if ( it->second==nullptr ) continue;

      for ( size_t vtxid=0; vtxid<it->second->size(); vtxid++ ) {

        auto const& lf_vertex = it->second->at(vtxid);

        NuVertexCandidate vertex;
        vertex.keypoint_producer = it->first;
        vertex.keypoint_index = vtxid;
        vertex.pos.resize(3,0);
        for (int i=0; i<3; i++)
          vertex.pos[i] = lf_vertex[i];
        vertex.score = 0.0;
        seed_v.emplace_back( std::move(vertex) );
      }
    }


    // associate to cluster objects
    for ( size_t vtxid=0; vtxid<seed_v.size(); vtxid++ ) {
      auto& vertex = seed_v[vtxid];

      for ( auto it=_cluster_producers.begin(); it!=_cluster_producers.end(); it++ ) {
        if ( it->second==nullptr ) continue;

        for ( size_t icluster=0; icluster<it->second->size(); icluster++ ) {

          auto const& lfcluster = it->second->at(icluster);
          auto const& lfpca     = _cluster_pca_producers[it->first]->at(icluster);
          NuVertexCandidate::ClusterType_t ctype   = _cluster_type[it->first];

          bool attached = _attachClusterToCandidate( vertex, lfcluster, lfpca,
                                                     ctype, it->first, icluster, true );


        }//end of cluster loop
      }//end of cluster container loop

      _score_vertex( vertex );

    }//end of vertex loop


    std::sort( seed_v.begin(), seed_v.end() );

    for ( auto& vertex : seed_v ) {

      if ( vertex.cluster_v.size()>0 ) {
        _vertex_v.emplace_back( std::move(vertex) );
        if ( logger().debug() ) {
          LARCV_DEBUG() << "Vertex[" << vertex.keypoint_producer << ", " << vertex.keypoint_index << "] " << std::endl;
          LARCV_DEBUG() << "  number of clusters: " << vertex.cluster_v.size() << std::endl;
          LARCV_DEBUG() << "  producer: " << vertex.keypoint_producer << std::endl;
          LARCV_DEBUG() << "  pos: (" << vertex.pos[0] << "," << vertex.pos[1] << "," << vertex.pos[2] << ")" << std::endl;
          LARCV_DEBUG() << "  score: " << vertex.score << std::endl;
          for (size_t ic=0; ic<vertex.cluster_v.size(); ic++) {
            LARCV_DEBUG() << "  cluster[" << ic << "] "
                          << " prod=" << vertex.cluster_v[ic].producer
                          << " idx=" << vertex.cluster_v[ic].index
                          << " impact=" << vertex.cluster_v[ic].impact << " cm"
                          << " gap=" << vertex.cluster_v[ic].gap << " cm"
                          << " npts=" << vertex.cluster_v[ic].npts
                          << std::endl;
          }
        }//end of if debug
      }//end of if has clusters
    }//end of vertex loop


  }

  void NuVertexMaker::clear()
  {
    _vertex_v.clear();
    _merged_v.clear();
    _keypoint_producers.clear();
    _keypoint_pca_producers.clear();
    _cluster_producers.clear();
    _cluster_pca_producers.clear();
  }

  void NuVertexMaker::_set_defaults()
  {
    // Track
    _cluster_type_max_impact_radius[ NuVertexCandidate::kTrack ] = 5.0;
    _cluster_type_max_gap[ NuVertexCandidate::kTrack ] = 5.0;

    // ShowerKP
    _cluster_type_max_impact_radius[ NuVertexCandidate::kShowerKP ] = 20.0;
    _cluster_type_max_gap[ NuVertexCandidate::kShowerKP ]           = 50.0;

    // Shower
    _cluster_type_max_impact_radius[ NuVertexCandidate::kShower ] = 50.0;
    _cluster_type_max_gap[ NuVertexCandidate::kShower ]           = 100.0;

    _apply_cosmic_veto = false;

  }

  void NuVertexMaker::_score_vertex( NuVertexCandidate& vtx )
  {
    vtx.score = 0.;
    const float tau_gap_shower    = 20.0;
    const float tau_impact_shower = 10.0;
    const float tau_ratio_shower = 1.0;

    const float tau_gap_track    = 3.0;
    const float tau_impact_track = 3.0;

    for ( auto& cluster : vtx.cluster_v ) {
      float clust_score = 1.0;
      if ( cluster.type==NuVertexCandidate::kTrack ) {
        if ( cluster.gap>3.0 )
          clust_score *= (1.0/tau_gap_track)*exp( -cluster.gap/tau_gap_track );
        if ( cluster.impact>3.0 )
          clust_score *= (1.0/tau_impact_track)*exp( -cluster.impact/tau_impact_track );
      }
      else {
        float ratio = cluster.impact/cluster.gap;
        clust_score *= (1.0/tau_ratio_shower)*exp( -ratio/tau_ratio_shower );
        if ( cluster.impact>3.0 )
          clust_score *= (1.0/tau_impact_shower)*exp( -cluster.impact/tau_impact_shower );
      }
      std::cout << "cluster[type=" << cluster.type << "] impact=" << cluster.impact << " gap=" << cluster.gap << " score=" << clust_score << std::endl;
      vtx.score += clust_score;
    }
  }

  void NuVertexMaker::add_nuvertex_branch( TTree* tree )
  {
    _ana_tree = tree;
    _own_tree = false;
    tree->Branch("nuvertex_v", &_vertex_v );
    tree->Branch("numerged_v", &_merged_v );
    tree->Branch("nuvetoed_v", &_vetoed_v );
    tree->Branch("nufitted_v", &_fitted_v );
  }


  void NuVertexMaker::make_ana_tree()
  {
    if ( !_ana_tree ) {
      _ana_tree = new TTree("NuVertexMakerTree","output of NuVertexMaker Class");

      // since we own this tree, we will add the run,subrun,event to it
      _ana_tree->Branch("run",&_ana_run,"run/I");
      _ana_tree->Branch("subrun",&_ana_run,"subrun/I");
      _ana_tree->Branch("event",&_ana_run,"event/I");
      // add the vertex container
      add_nuvertex_branch( _ana_tree );
      // mark that we own it, so we destroy it later
      _own_tree = true;
    }
  }

  void NuVertexMaker::_merge_candidates()
  {

    // sort by score
    // start with best score, absorb others, (so N^2 algorithm)

    // clear existing merged
    _merged_v.clear();

    if ( _vertex_v.size()==0 )
      return;

    _merged_v.reserve( _vertex_v.size() );

    // struct for us to sort by score
    struct NuScore_t {
      const NuVertexCandidate* nu;
      float score;
      NuScore_t( const NuVertexCandidate* the_nu, float s )
        : nu(the_nu),
          score(s)
      {};
      bool operator<( const NuScore_t& rhs ) const
      {
        if ( score>rhs.score ) return true;
        return false;
      }
    };

    std::vector< NuScore_t > nu_v;
    nu_v.reserve( _vertex_v.size() );
    for ( auto const& nucand : _vertex_v ) {
      nu_v.push_back( NuScore_t(&nucand, nucand.score) );
    }
    std::sort( nu_v.begin(), nu_v.end() );

    std::vector<int> used_v( nu_v.size(), 0 );

    // seed first merger candidate
    _merged_v.push_back( *(nu_v[0].nu) );
    used_v[0] = 1;

    if ( _vertex_v.size()==1 )
      return;

    int nused = 0;
    int current_cand_index = 0; // index of candidate in _merged_v, for whom we are currently looking for mergers
    while ( nused<(int)nu_v.size() && current_cand_index<_merged_v.size() ) {

      auto& cand = _merged_v.at(current_cand_index);

      for (int i=0; i<(int)nu_v.size(); i++) {
        if ( used_v[i]==1 ) continue;

        // test vertex
        auto const& test_vtx = *nu_v[i].nu;

        // test vertex distance
        float dist = 0.;
        for (int v=0; v<3; v++)
          dist += (test_vtx.pos[v]-cand.pos[v])*(test_vtx.pos[v]-cand.pos[v]);
        dist = sqrt(dist);

        if ( dist>5.0 )
          continue; // too far to merge (or the same)

        // if within distance, absorb clusters to make union
        // set the pos, keypoint producer based on best score
        used_v[i] = 1;

        // loop over clusters inside test vertex we are merging with
        int nclust_before = cand.cluster_v.size();
        for ( auto const& test_clust : test_vtx.cluster_v ) {

          // check the clusters against the current candidate's clusters
          bool is_found = false;
          for (auto const& curr_clust : cand.cluster_v ) {
            if ( curr_clust.index==test_clust.index && curr_clust.producer==test_clust.producer ) {
              is_found = true;
              break;
            }
          }

          if ( !is_found ) {
            // if not found, add it to the current candidate vertex
            auto it_clust = _cluster_producers.find( test_clust.producer );
            auto it_pca   = _cluster_pca_producers.find( test_clust.producer );
            auto it_ctype = _cluster_type.find( test_clust.producer );
            if ( it_clust==_cluster_producers.end() || it_pca==_cluster_pca_producers.end() ) {
              throw std::runtime_error("ERROR NuVertexMaker. could not find cluster/pca producer in dict");
            }
            auto const& lfcluster = it_clust->second->at(test_clust.index);
            auto const& lfpca     = it_pca->second->at(test_clust.index);
            auto const& clust_t   = it_ctype->second;
            _attachClusterToCandidate( cand, lfcluster, lfpca, clust_t,
                                       test_clust.producer, test_clust.index, false );
          }

        }//after test cluster loop

        // score the current candidate
        _score_vertex( cand );

        // here we create a duplicate,
        // but with the vertex moved to the pos of the test_vtx
        // then we decide which one to keep based on highest score

      }//end of loop to absorb vertices

      // get the next unused absorbed vertex, to seed as merger
      for (int i=0; i<(int)nu_v.size(); i++) {
        if ( used_v[i]==1 ) continue;
        _merged_v.push_back( *nu_v[i].nu );
        used_v[i] = 1;
        break;
      }

      current_cand_index++;
    }


  }

  bool NuVertexMaker::_attachClusterToCandidate( NuVertexCandidate& vertex,
                                                 const larlite::larflowcluster& lfcluster,
                                                 const larlite::pcaxis& lfpca,
                                                 NuVertexCandidate::ClusterType_t ctype,
                                                 std::string producer,
                                                 int icluster,
                                                 bool apply_cut )
  {

    std::vector<float> pcadir(3,0);
    std::vector<float> start(3,0);
    std::vector<float> end(3,0);
    float dist[2] = {0,0};
    float pcalen = 0.;
    for (int v=0; v<3; v++) {
      pcadir[v] = lfpca.getEigenVectors()[0][v];
      start[v]  = lfpca.getEigenVectors()[3][v];
      end[v]    = lfpca.getEigenVectors()[4][v];
      dist[0] += ( start[v]-vertex.pos[v] )*( start[v]-vertex.pos[v] );
      dist[1] += ( end[v]-vertex.pos[v] )*( end[v]-vertex.pos[v] );
      pcalen += pcadir[v]*pcadir[v];
    }
    pcalen = sqrt(pcalen);
    if (pcalen<0.1 || std::isnan(pcalen) ) {
      return false;
    }
    dist[0] = sqrt(dist[0]);
    dist[1] = sqrt(dist[1]);
    int closestend = (dist[0]<dist[1]) ? 0 : 1;
    float gapdist = dist[closestend];
    float r = pointLineDistance( start, end, vertex.pos );

    LARCV_DEBUG() << "pcadir-len=" << pcalen << std::endl;
    float projs = pointRayProjection<float>( start, pcadir, vertex.pos );
    float ends  = pointRayProjection<float>( start, pcadir, end );

    if ( apply_cut ) {
      // wide association for now
      if ( gapdist>_cluster_type_max_gap[ctype] )
        return false;

      if ( r>_cluster_type_max_impact_radius[ctype] )
        return false;

      if ( ctype==NuVertexCandidate::kShowerKP || ctype==NuVertexCandidate::kShower ) {
        if ( projs>2.0 && projs < (ends-2.0) )
          return false;
      }
    }

    // else attach
    NuVertexCandidate::VtxCluster_t cluster;
    cluster.producer = producer;
    cluster.index = icluster;
    cluster.dir.resize(3,0);
    cluster.pos.resize(3,0);
    for ( int i=0; i<3; i++) {
      if ( closestend==0 ) {
        cluster.dir[i] = pcadir[i];
        cluster.pos[i] = start[i];
      }
      else {
        cluster.dir[i] = -pcadir[i];
        cluster.pos[i] = end[i];
      }
    }
    cluster.gap = gapdist;
    cluster.impact = r;
    cluster.type = ctype;
    cluster.npts = (int)lfcluster.size();
    vertex.cluster_v.emplace_back( std::move(cluster) );

    return true;
  }

  void NuVertexMaker::_cosmic_veto_candidates( larlite::storage_manager& ioll )
  {

    // get cosmic tracks
    larlite::event_track* ev_cosmic
      = (larlite::event_track*)ioll.get_data( larlite::data::kTrack, "boundarycosmicnoshift" );

    // loop over vertices
    _vetoed_v.clear();
    for ( auto& vtx : _merged_v ) {

      bool close2cosmic = false;

      for (int itrack=0; itrack<(int)ev_cosmic->size(); itrack++) {
        const larlite::track& cosmictrack = ev_cosmic->at(itrack);

        float mindist_segment = 1.0e9;
        float mindist_point   = 1.0e9;
        for (int ipt=0; ipt<(int)cosmictrack.NumberTrajectoryPoints()-1; ipt++) {

          const TVector3& pos  = cosmictrack.LocationAtPoint(ipt);
          const TVector3& next = cosmictrack.LocationAtPoint(ipt+1);

          std::vector<float> fpos  = { (float)pos(0),  (float)pos(1),  (float)pos(2) };
          std::vector<float> fnext = { (float)next(0), (float)next(1), (float)next(2) };

          std::vector<float> fdir(3,0);
          float flen = 0.;
          for (int i=0; i<3; i++) {
            fdir[i] = fnext[i]-fpos[i];
            flen += fdir[i]*fdir[i];
          }
          if ( flen>0 ) {
            flen = sqrt(flen);
            for (int i=0; i<3; i++)
              fdir[i] /= flen;
          }
          else {
            continue;
          }


          float r = larflow::reco::pointLineDistance3f( fpos, fnext, vtx.pos );
          float s = larflow::reco::pointRayProjection3f( fpos, fdir, vtx.pos );

          if ( s>=0 && s<=flen && mindist_segment>r) {
            mindist_segment = r;
          }

          float pt1dist = 0.;
          for (int i=0; i<3; i++) {
            pt1dist += (fpos[i]-vtx.pos[i])*(fpos[i]-vtx.pos[i]);
          }
          pt1dist = sqrt(pt1dist);
          if ( pt1dist<mindist_point ) {
            mindist_point = pt1dist;
          }

          if ( ipt+1==(int)cosmictrack.NumberTrajectoryPoints() ) {
            pt1dist = 0;
            for (int i=0; i<3; i++) {
              pt1dist += (fnext[i]-vtx.pos[i])*(fnext[i]-vtx.pos[i]);
            }
            pt1dist = sqrt(pt1dist);
            if ( pt1dist<mindist_point ) {
              mindist_point = pt1dist;
            }
          }
        }//end of loop over cosmic points


        if ( mindist_segment<10.0 || mindist_point<10.0 ) {
          close2cosmic = true;
        }


        if ( close2cosmic )
          break;
      }//end of loop over tracks


      if ( !close2cosmic ) {
        _vetoed_v.push_back( vtx );
      }

    }//end of vertex loop

    LARCV_INFO() << "Vertices after cosmic veto: " << _vetoed_v.size() << " (from " << _merged_v.size() << ")" << std::endl;

  }

  void NuVertexMaker::_refine_position( larcv::IOManager& iolcv,
                                        larlite::storage_manager& ioll )
  {

    larflow::reco::NuVertexFitter fitter;
    if ( _apply_cosmic_veto )
      fitter.process( iolcv, ioll, get_vetoed_candidates() );
    else
      fitter.process( iolcv, ioll, get_merged_candidates() );

    const std::vector< std::vector<float> >& fitted_pos_v
      = fitter.get_fitted_pos();

    _fitted_v.clear();
    if ( _apply_cosmic_veto ) {

      int ivtx=0;
      for ( auto const& vtx : _vetoed_v ) {
        NuVertexCandidate fitcand = vtx;
        fitcand.pos = fitted_pos_v[ivtx];
        ivtx++;
        _fitted_v.emplace_back( std::move(fitcand) );
      }

    }

  }


}
}