Program Listing for File cluster_functions.cxx

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

#include "cluster_functions.h"

#include <fstream>

#include "ublarcvapp/dbscan/DBScan.h"
#include "ublarcvapp/dbscan/sDBScan.h"
#include "ublarcvapp/ContourTools/ContourClusterAlgo.h"
#include <cilantro/principal_component_analysis.hpp>

namespace larflow {
namespace reco {

  ClusterFunctions::ClusterFunctions() {}

  void cluster_larflow3dhits( const std::vector<larlite::larflow3dhit>& hit_v,
                              std::vector< cluster_t >& cluster_v,
                              const float maxdist, const int minsize, const int maxkd )
  {

    // convert points into list of floats
    std::vector< std::vector<float> > points_v;
    points_v.reserve( hit_v.size() );

    for ( auto const& lfhit : hit_v ) {
      std::vector<float > hit = { (float)lfhit[0], (float)lfhit[1], (float)lfhit[2] };
      points_v.push_back( hit );
    }


    clock_t start = clock();
    std::vector< ublarcvapp::dbscan::dbCluster > dbcluster_v = ublarcvapp::dbscan::DBScan::makeCluster3f( maxdist, minsize, maxkd, points_v );


    for (int ic=0; ic<(int)dbcluster_v.size()-1;ic++) {
      // skip the last cluster, which are noise points
      auto const& cluster = dbcluster_v[ic];
      cluster_t c;
      c.points_v.reserve(cluster.size());
      c.imgcoord_v.reserve(cluster.size());
      c.hitidx_v.reserve(cluster.size());
      for ( auto const& hitidx : cluster ) {
        // store 3d position and 2D image coordinates
        c.points_v.push_back( points_v.at(hitidx) );
        std::vector<int> coord(4,0);
        coord[3] = hit_v[hitidx].tick;
        coord[0] = (int)hit_v[hitidx].targetwire[0]; // U-plane
        coord[1] = (int)hit_v[hitidx].targetwire[1]; // V-plane
        coord[2] = (int)hit_v[hitidx].srcwire;
        c.imgcoord_v.push_back( coord );
        c.hitidx_v.push_back(hitidx);
      }
      cluster_v.emplace_back(std::move(c));
    }
    clock_t end = clock();
    double elapsed = double(end-start)/CLOCKS_PER_SEC;

    std::cout << "[cluster_larflow3dhit] made clusters: " << dbcluster_v.size() << " elpased=" << elapsed << " secs" << std::endl;
  }

  void cluster_spacepoint_v( const std::vector< std::vector<float> >& points_v,
                             std::vector< cluster_t >& cluster_v,
                             const float maxdist, const int minsize, const int maxkd )
  {

    clock_t start = clock();
    std::vector< ublarcvapp::dbscan::dbCluster > dbcluster_v
      = ublarcvapp::dbscan::DBScan::makeCluster3f( maxdist, minsize, maxkd, points_v );


    for (int ic=0; ic<(int)dbcluster_v.size()-1;ic++) {
      // skip the last cluster, which are noise points
      auto const& cluster = dbcluster_v[ic];
      cluster_t c;
      c.points_v.reserve(cluster.size());
      //c.imgcoord_v.reserve(cluster.size());
      c.hitidx_v.reserve(cluster.size());
      for ( auto const& hitidx : cluster ) {
        // store 3d position and 2D image coordinates
        c.points_v.push_back( points_v.at(hitidx) );
        c.hitidx_v.push_back(hitidx);
      }
      cluster_v.emplace_back(std::move(c));
    }
    clock_t end = clock();
    double elapsed = double(end-start)/CLOCKS_PER_SEC;

    std::cout << "[cluster_spacepoint] made clusters: " << dbcluster_v.size() << " elpased=" << elapsed << " secs" << std::endl;

  }


  void cluster_sdbscan_larflow3dhits( const std::vector<larlite::larflow3dhit>& hit_v,
                                      std::vector< cluster_t >& cluster_v,
                                      const float maxdist, const int minsize, const int maxkd ) {

    clock_t start = clock();

    // convert points into list of floats
    std::vector< std::vector<float> > points_v;
    points_v.reserve( hit_v.size() );

    for ( auto const& lfhit : hit_v ) {
      std::vector<float> hit = { lfhit[0], lfhit[1], lfhit[2] };
      points_v.push_back( hit );
    }

    auto sdbscan = ublarcvapp::dbscan::SDBSCAN< std::vector<float>, float >();
    sdbscan.Run( &points_v, 3, maxdist, minsize );

    auto noise = sdbscan.Noise;
    auto dbcluster_v = sdbscan.Clusters;

    for (int ic=0; ic<(int)dbcluster_v.size();ic++) {
      // skip the last cluster, which are noise points
      auto const& cluster = dbcluster_v[ic];
      cluster_t c;
      c.points_v.reserve(cluster.size());
      c.imgcoord_v.reserve(cluster.size());
      c.hitidx_v.reserve(cluster.size());
      for ( auto const& hitidx : cluster ) {
        // store 3d position and 2D image coordinates
        c.points_v.push_back( points_v.at(hitidx) );
        std::vector<int> coord(4,0);
        coord[3] = hit_v[hitidx].tick;
        coord[0] = (int)hit_v[hitidx].targetwire[0]; // U-plane
        coord[1] = (int)hit_v[hitidx].targetwire[1]; // V-plane
        coord[2] = (int)hit_v[hitidx].targetwire[2]; // Y-plane
        c.imgcoord_v.push_back( coord );
        c.hitidx_v.push_back(hitidx);
      }
      cluster_v.emplace_back(std::move(c));
    }
    clock_t end = clock();
    double elapsed = double(end-start)/CLOCKS_PER_SEC;

    std::cout << "[cluster_simple_larflow3dhits] made clusters: " << dbcluster_v.size() << " elpased=" << elapsed << " secs" << std::endl;
  }

  void cluster_sdbscan_spacepoints( const std::vector< std::vector<float> >& hit_v,
                                    std::vector< cluster_t >& cluster_v,
                                    const float maxdist, const int minsize, const int maxkd )
  {

    clock_t start = clock();

    // convert points into list of floats
    std::vector< std::vector<float> > points_v;
    points_v.reserve( hit_v.size() );

    for ( auto const& lfhit : hit_v ) {
      std::vector<float> hit = { lfhit[0], lfhit[1], lfhit[2] };
      points_v.push_back( hit );
    }

    auto sdbscan = ublarcvapp::dbscan::SDBSCAN< std::vector<float>, float >();
    sdbscan.Run( &points_v, 3, maxdist, minsize );

    auto noise = sdbscan.Noise;
    auto dbcluster_v = sdbscan.Clusters;

    for (int ic=0; ic<(int)dbcluster_v.size();ic++) {
      // skip the last cluster, which are noise points
      auto const& cluster = dbcluster_v[ic];
      cluster_t c;
      c.points_v.reserve(cluster.size());
      c.imgcoord_v.reserve(cluster.size());
      c.hitidx_v.reserve(cluster.size());
      for ( auto const& hitidx : cluster ) {
        // store 3d position and 2D image coordinates
        c.points_v.push_back( points_v.at(hitidx) );
        c.hitidx_v.push_back(hitidx);
      }
      cluster_v.emplace_back(std::move(c));
    }
    clock_t end = clock();
    double elapsed = double(end-start)/CLOCKS_PER_SEC;

    std::cout << "[cluster_sdbscan_spacepoints] made clusters: " << dbcluster_v.size() << " elpased=" << elapsed << " secs" << std::endl;
  }

  void cluster_pca( cluster_t& cluster ) {

    //std::cout << "[cluster_pca]" << std::endl;
    cluster.bbox_v.resize(3);
    for (size_t i=0; i<3; i++ ) {
      cluster.bbox_v[i].resize(2,0);
      cluster.bbox_v[i][0] = 1.0e9;  // initial min value
      cluster.bbox_v[i][1] = -1.0e9; // initial max value
    }

    std::vector< Eigen::Vector3f > eigen_v;
    eigen_v.reserve( cluster.points_v.size() );
    for ( auto const& hit : cluster.points_v ) {
      // store values for PCA
      eigen_v.push_back( Eigen::Vector3f( hit[0], hit[1], hit[2] ) );
      // also determine axis-aligned bounding box values
      for (int i=0; i<3; i++ ) {
        if ( hit[i]<cluster.bbox_v[i][0] ) cluster.bbox_v[i][0] = hit[i];
        if ( hit[i]>cluster.bbox_v[i][1] ) cluster.bbox_v[i][1] = hit[i];
      }
    }


    cilantro::PrincipalComponentAnalysis3f pca( eigen_v );
    cluster.pca_center.resize(3,0);
    cluster.pca_eigenvalues.resize(3,0);
    cluster.pca_axis_v.clear();
    for (int i=0; i<3; i++) {
      cluster.pca_center[i] = pca.getDataMean()(i);
      cluster.pca_eigenvalues[i] = pca.getEigenValues()(i);
      std::vector<float> e_v = { pca.getEigenVectors()(0,i),
                                 pca.getEigenVectors()(1,i),
                                 pca.getEigenVectors()(2,i) };
      cluster.pca_axis_v.push_back( e_v );
    }
    // we provide a way to order the points
    struct pca_coord {
      int idx;
      float s;
      bool operator<( const pca_coord& rhs ) const {
        if ( s<rhs.s) return true;
        return false;
      };
    };

    // normalize first axis (should be though)
    for ( int ipca=0; ipca<3; ipca++) {
      float lenpc = 0.;
      for (int i=0; i<3; i++ ) {
        lenpc += cluster.pca_axis_v[ipca][i]*cluster.pca_axis_v[ipca][i];
      }
      if ( lenpc>0 ) {
        lenpc = sqrt(lenpc);
        for ( int i=0; i<3; i++ )
          cluster.pca_axis_v[ipca][i] /= lenpc;
      }
    }

    std::vector< pca_coord > ordered_v;
    ordered_v.reserve( cluster.points_v.size() );
    for ( size_t idx=0; idx<cluster.points_v.size(); idx++ ) {
      float s = 0.;
      for (int i=0; i<3; i++ ) {
        s += (cluster.points_v[idx][i]-cluster.pca_center[i])*cluster.pca_axis_v[0][i];
      }
      pca_coord coord;
      coord.idx = idx;
      coord.s   = s;
      ordered_v.push_back( coord );
    }
    std::sort( ordered_v.begin(), ordered_v.end() );
    cluster.ordered_idx_v.resize( ordered_v.size() );
    cluster.pca_proj_v.resize( ordered_v.size() );
    for ( size_t i=0; i<ordered_v.size(); i++ ) {
      cluster.ordered_idx_v[i] = ordered_v[i].idx;
      cluster.pca_proj_v[i]    = ordered_v[i].s;
    }

    // define ends
    cluster.pca_ends_v.resize(2);
    cluster.pca_ends_v[0].resize(3,0);
    cluster.pca_ends_v[1].resize(3,0);
    for (int i=0; i<3; i++) {
      cluster.pca_ends_v[0][i] = cluster.pca_center[i] + cluster.pca_proj_v.front()*cluster.pca_axis_v[0][i];
      cluster.pca_ends_v[1][i] = cluster.pca_center[i] + cluster.pca_proj_v.back()*cluster.pca_axis_v[0][i];
    }

    float len3 = 0;
    std::vector<float> d3(3);
    for (int i=0; i<3; i++ ) {
      d3[i] = cluster.pca_ends_v[1][i] - cluster.pca_ends_v[0][i];
      len3 += d3[i]*d3[i];
    }
    len3 = sqrt(len3);
    cluster.pca_len = len3;

    // define radius of each point to the pca-axis
    float max_r = 0.;
    float ave_r2 = 0.;
    cluster.pca_radius_v.resize( ordered_v.size(), 0 );
    for ( size_t i=0; i<ordered_v.size(); i++ ) {
      // get distance of point from pca-axis
      // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
      std::vector<float>& pt = cluster.points_v[ ordered_v[i].idx ];

      std::vector<float> d1(3);
      std::vector<float> d2(3);

      float len1 = 0.;
      for (int i=0; i<3; i++ ) {
        d1[i] = pt[i] - cluster.pca_ends_v[0][i];
        d2[i] = pt[i] - cluster.pca_ends_v[1][i];
        len1 += d1[i]*d1[i];
      }
      len1 = sqrt(len1);

      if ( len3<1.0e-4 ) {
        cluster.pca_radius_v[i] = len1;
      }
      else {

        // cross-product
        std::vector<float> d1xd2(3);
        d1xd2[0] =  d1[1]*d2[2] - d1[2]*d2[1];
        d1xd2[1] = -d1[0]*d2[2] + d1[2]*d2[0];
        d1xd2[2] =  d1[0]*d2[1] - d1[1]*d2[0];
        float len1x2 = 0.;
        for ( int i=0; i<3; i++ ) {
          len1x2 += d1xd2[i]*d1xd2[i];
        }
        len1x2 = sqrt(len1x2);
        cluster.pca_radius_v[i]  = len1x2/len3; // distance of point from PCA-axis
      }
      ave_r2 += cluster.pca_radius_v[i]*cluster.pca_radius_v[i];
      if ( max_r < cluster.pca_radius_v[i] )
        max_r = cluster.pca_radius_v[i];

    }//end of loop over ordered points

    ave_r2 = sqrt( ave_r2/cluster.pca_radius_v.size() );

    cluster.pca_max_r  = max_r;
    cluster.pca_ave_r2 = ave_r2;


  }

  void cluster_runpca( std::vector<cluster_t>& cluster_v ) {
    clock_t start = clock();
    for ( auto& cluster : cluster_v ) {
      cluster_pca( cluster );
    }
    clock_t end = clock();
    double elapsed = double(end-start)/CLOCKS_PER_SEC;
    std::cout << "[ cluster_runpca ] elapsed=" << elapsed << " secs" << std::endl;
  }

  void cluster_dump2jsonfile( const std::vector<cluster_t>& cluster_v, std::string outfilename ) {
    nlohmann::json j;
    std::vector< nlohmann::json > jcluster_v;
    j["clusters"] = jcluster_v;

    for ( auto const& cluster : cluster_v ) {
      //std::cout << "cluster: nhits=" << cluster.points_v.size() << std::endl;
      nlohmann::json jcluster = cluster_json(cluster);
      j["clusters"].emplace_back( std::move(jcluster) );
    }

    std::ofstream o(outfilename.c_str());
    j >> o;
    o.close();
  }

  nlohmann::json cluster_json( const cluster_t& cluster ) {

    nlohmann::json jcluster;
    jcluster["hits"] = cluster.points_v;

    // save start, center, end of pca to make line
    std::vector< std::vector<float> > pca_points(3);
    for (int i=0; i<3; i++ )
      pca_points[i].resize(3,0);

    for (int i=0; i<3; i++ ) {
      pca_points[0][i] = cluster.pca_ends_v[0][i];// - 5.0*cluster.pca_axis_v[0][i];
      pca_points[1][i] = cluster.pca_center[i];
      pca_points[2][i] = cluster.pca_ends_v[1][i];// + 5.0*cluster.pca_axis_v[0][i];
    }
    jcluster["pca"] = pca_points;
    return jcluster;
  }


  void cluster_splitbytrackshower( const std::vector<larlite::larflow3dhit>& hit_v,
                                   const std::vector<larcv::Image2D>& ssnettrack_image_v,
                                   std::vector<larlite::larflow3dhit>& track_hit_v,
                                   std::vector<larlite::larflow3dhit>& shower_hit_v,
                                   float min_larmatch_score ) {

    clock_t begin = clock();

    track_hit_v.clear();
    shower_hit_v.clear();
    track_hit_v.reserve(  hit_v.size() );
    shower_hit_v.reserve( hit_v.size() );

    std::vector< const larcv::ImageMeta* > meta_v( ssnettrack_image_v.size(),0);
    for ( size_t p=0; p<ssnettrack_image_v.size(); p++ )
      meta_v[p] = &(ssnettrack_image_v[p].meta());

    int below_threshold = 0.;

    for ( auto const & hit : hit_v ) {

      //std::cout << "hit[9]=" << hit[9] << std::endl;
      if ( min_larmatch_score>0 && hit.size()>=10 && hit[9]<min_larmatch_score ) {
        below_threshold++;
        continue;
      }

      std::vector<float> scores(3,0);
      scores[0] = ssnettrack_image_v[0].pixel( meta_v[0]->row( hit.tick, __FILE__, __LINE__ ), hit.targetwire[0], __FILE__, __LINE__ );
      scores[1] = ssnettrack_image_v[1].pixel( meta_v[1]->row( hit.tick, __FILE__, __LINE__ ), hit.targetwire[1], __FILE__, __LINE__ );
      scores[2] = ssnettrack_image_v[2].pixel( meta_v[2]->row( hit.tick, __FILE__, __LINE__ ), hit.srcwire,       __FILE__, __LINE__ );

      // condition ... gather metrics
      int n_w_score = 0;
      float tot_score = 0.;
      float max_score = 0.;
      float min_non_zero = 1.;
      for ( auto s : scores ) {
        if ( s>0 ) n_w_score++;
        tot_score += s;
        if ( max_score<s )
          max_score = s;
        if ( s>1 && s<min_non_zero )
          min_non_zero = 0;
      }

      if ( n_w_score>0 && tot_score/float(n_w_score)>0.5 ) {
        track_hit_v.push_back( hit );
      }
      else
        shower_hit_v.push_back( hit );
    }

    clock_t end = clock();
    double elapsed = double(end-begin)/CLOCKS_PER_SEC;

    std::cout << "[cluster_split_by_trackshower]: "
              << "original=" << hit_v.size()
              << " into track=" << track_hit_v.size()
              << " and shower=" << shower_hit_v.size()
              << " below-threshold=" << below_threshold
              << " elasped=" << elapsed << " secs"
              << std::endl;
  }

  void cluster_imageprojection( const cluster_t& cluster,
                                std::vector<larcv::Image2D>& clust2d_images_v ) {

    // zero the images
    for ( auto& img : clust2d_images_v )
      img.paint(0.0);

    for ( auto const& imgcoord : cluster.imgcoord_v ) {
      for ( auto& img : clust2d_images_v ) {
        img.set_pixel( img.meta().row( imgcoord[3], __FILE__, __LINE__ ),
                       img.meta().col( imgcoord[img.meta().plane()], __FILE__, __LINE__ ),
                       50.0 /* dummy value */ );
      }
    }

  }

  void cluster_getcontours( std::vector<larcv::Image2D>& clust2d_images_v ) {
    ublarcvapp::ContourClusterAlgo contour_algo;
    contour_algo.analyzeImages( clust2d_images_v, 10.0, 2, 5, 10, 10, 2 );

    // contours made. now what.
    for ( size_t p=0; p<3; p++ ) {
      int ncontours = contour_algo.m_plane_atomics_v[p].size();
      std::cout << "[cluster_getcontours] ncontours plane[" << p << "] = " << ncontours << std::endl;
      for (int ictr=0; ictr<ncontours; ictr++ ) {

        if ( !contour_algo.m_plane_atomicmeta_v[p][ictr].hasValidPCA() ) {
          std::cout << "  icontour[" << ictr << "] pca not valid" << std::endl;
          continue;
        }

        // dist from start-end of pca-axis
        float pca_dist = 0;
        float dx = 0;
        for ( size_t i=0; i<2; i++ ) {
          dx = ( contour_algo.m_plane_atomicmeta_v[p][ictr].getPCAEndPos()[i] -
                 contour_algo.m_plane_atomicmeta_v[p][ictr].getPCAStartPos()[i] );
          pca_dist += dx*dx;
        }
        pca_dist = sqrt(pca_dist);
        std::cout << "  icontour[" << ictr << "] "
                  << " pca-dist=" << pca_dist
                  << " eigenvals [0]=" << contour_algo.m_plane_atomicmeta_v[p][ictr].getPCAeigenvalue(0)
                  << " [1]=" << contour_algo.m_plane_atomicmeta_v[p][ictr].getPCAeigenvalue(1)
                  << std::endl;
      }
    }

  }

  float cluster_closest_endpt_dist( const cluster_t& clust_a, const cluster_t& clust_b,
                                    std::vector< std::vector<float> >& endpts ) {
    // 2x2 calculation

    float dist[4] = {0};
    for (int i=0; i<2; i++) {
      for (int j=0; j<2; j++) {
        for (int k=0; k<3; k++) {
          dist[ 2*i+j ] += (clust_a.pca_ends_v[i][k] - clust_b.pca_ends_v[j][k])*(clust_a.pca_ends_v[i][k] - clust_b.pca_ends_v[j][k]);
        }
      }
    }

    float mindist = -1.0;
    int mini = 0;
    int minj = 0;
    for (int k=0; k<4; k++ ) {
      if ( dist[k]<mindist || mindist<0 ) {
        mindist = dist[k];
        mini = k/2;
        minj = k%2;
      }
    }

    endpts.resize(2);
    endpts[0].resize(3,0);
    endpts[1].resize(3,0);
    for (int k=0; k<3; k++ ) {
      endpts[0][k] = clust_a.pca_ends_v[mini][k];
      endpts[1][k] = clust_b.pca_ends_v[minj][k];
    }

    if ( mindist>0 )
      mindist = sqrt(mindist);

    return mindist;
  }

  bool cluster_endpt_in_bbox( const cluster_t& clust_a, const cluster_t& clust_b ) {

    bool a_in_b = false;
    bool b_in_a = false;
    for (int iend=0; iend<2; iend++) {
      int dims_inside = 0;
      for (int i=0; i<3; i++ ) {
        if ( clust_a.pca_ends_v[iend][i]>=clust_b.bbox_v[i][0] && clust_a.pca_ends_v[iend][i]<=clust_b.bbox_v[i][1] )
          dims_inside++;
      }
      if ( dims_inside==3 )
        a_in_b = true;

      dims_inside = 0;
      for (int i=0; i<3; i++ ) {
        if ( clust_b.pca_ends_v[iend][i]>=clust_a.bbox_v[i][0] && clust_b.pca_ends_v[iend][i]<=clust_a.bbox_v[i][1] )
          dims_inside++;
      }
      if ( dims_inside==3 )
        b_in_a = true;
    }

    return a_in_b || b_in_a;
  }

  float cluster_cospca( const cluster_t& clust_a, const cluster_t& clust_b ) {
    float cospca = 0.;
    float lena = 0.;
    float lenb = 0.;
    for ( int k=0; k<3; k++ ) {
      cospca += clust_a.pca_axis_v[0][k]*clust_b.pca_axis_v[0][k];
    }
    return cospca;
  }

  cluster_t cluster_merge( const cluster_t& clust_a, const cluster_t& clust_b ) {
    // copy first
    cluster_t merge = clust_a;

    merge.points_v.reserve(   merge.points_v.size()+clust_b.points_v.size() );
    merge.imgcoord_v.reserve( merge.imgcoord_v.size()+clust_b.imgcoord_v.size() );
    merge.hitidx_v.reserve( merge.imgcoord_v.size()+clust_b.imgcoord_v.size() );
    for ( size_t i=0; i<clust_b.points_v.size(); i++ ) {
      merge.points_v.push_back(   clust_b.points_v[i] );
      merge.imgcoord_v.push_back( clust_b.imgcoord_v[i] );
      merge.hitidx_v.push_back( clust_b.hitidx_v[i] );
    }

    cluster_pca( merge );
    return merge;
  }

  larlite::pcaxis cluster_make_pcaxis( const cluster_t& c, int cidx ) {

    // pca-axis
    larlite::pcaxis::EigenVectors e_v;
    // just std::vector< std::vector<double> >
    // we store axes (3) and then the 1st axis end points. So five vectors.
    for ( auto const& a_v : c.pca_axis_v ) {
      std::vector<double> da_v = { (double)a_v[0], (double)a_v[1], (double) a_v[2] };
        e_v.push_back( da_v );
    }
    // start and end points
    for ( auto const& p_v : c.pca_ends_v ) {
      std::vector<double> dp_v = { (double)p_v[0], (double)p_v[1], (double)p_v[2] };
      e_v.push_back( dp_v );
    }
    double eigenval[3] = { c.pca_eigenvalues[0], c.pca_eigenvalues[1], c.pca_eigenvalues[2] };
    double centroid[3] = { c.pca_center[0], c.pca_center[1], c.pca_center[2] };
    larlite::pcaxis llpca( true, c.points_v.size(), eigenval, e_v, centroid, 0, cidx );

    return llpca;
  }


  cluster_t cluster_from_larflowcluster( const larlite::larflowcluster& lfcluster ) {

    std::cout << "[cluster_from_larflowcluster] input cluster size=" << lfcluster.size() << std::endl;
    cluster_t c;
    c.points_v.reserve( 2*lfcluster.size() );
    c.imgcoord_v.reserve( 2*lfcluster.size() );
    c.hitidx_v.reserve( 2*lfcluster.size() );
    for ( auto const& lfhit : lfcluster ) {
      //if ( lfhit.size()<3 ) continue;
      //if ( lfhit.targetwire.size()<3 ) continue;
      // std::cout << "lfhit: " << lfhit[0] << "," << lfhit[1] << "," << lfhit[2] << std::endl;
      // std::cout << "   coord=(" << lfhit.targetwire[0] << "," << lfhit.targetwire[1] << "," << lfhit.targetwire[2] << ")" << std::endl;
      // std::cout << "   tick=" << lfhit.tick << std::endl;
      std::vector<float> pos   = { lfhit[0], lfhit[1], lfhit[2] };
      std::vector<int>   coord = { lfhit.targetwire[0],
                                   lfhit.targetwire[1],
                                   lfhit.targetwire[2],
                                   lfhit.tick };
      c.points_v.push_back( pos );
      c.imgcoord_v.push_back( coord );
      c.hitidx_v.push_back( (int)c.points_v.size()-1 );
    }

    cluster_pca( c );

    return c;
  }

  /*
   * @brief project hits into the ADC image and get the total pixel sum for each plane
   *
   * @param[in] cluster Cluster whose hits we project. (Note we use cluster_t::imgcoord).
   * @param[in] img_v   Wire plane image to get the charge sum from.
   * @return charge sum on each plane.
   *
   */
  std::vector<float> cluster_pixelsum( const cluster_t& cluster,
                                       const std::vector<larcv::Image2D>& img_v  ) {

    std::vector<float> cluster_adc(3,0.0);

    std::vector<larcv::Image2D> blank_v;
    for (auto const& img : img_v ) {
      larcv::Image2D blank(img.meta());
      blank.paint(0);
      blank_v.emplace_back( std::move(blank) );
    }

    for ( size_t ihit=0; ihit<cluster.imgcoord_v.size(); ihit++ ) {
      int row = img_v[0].meta().row( cluster.imgcoord_v[ihit][3], __FILE__, __LINE__ );
      for ( size_t p=0; p<img_v.size(); p++ ) {
        int col = img_v[p].meta().col( cluster.imgcoord_v[ihit][p], __FILE__, __LINE__ );
        float used   = blank_v[p].pixel(row,col);
        if ( used<1.0 ) {
          float pixval = img_v[p].pixel(row,col);
          cluster_adc[p] += pixval;
          // mark it to not be reused
          blank_v[p].set_pixel(row,col,10.0);
        }
      }
    }
    return cluster_adc;
  }

  float cluster_dist_from_pcaline( const cluster_t& cluster, const std::vector<float>& pt ) {

    //
    // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html

    std::vector<float> d1(3);
    std::vector<float> d2(3);

    float len1 = 0.;
    for (int i=0; i<3; i++ ) {
      d1[i] = pt[i] - cluster.pca_ends_v[0][i];
      d2[i] = pt[i] - cluster.pca_ends_v[1][i];
      len1 += d1[i]*d1[i];
    }
    len1 = sqrt(len1);

    if ( cluster.pca_len<1.0e-4 ) {
      // short cluster, use distance to end point
      return len1;
    }

    // cross-product
    std::vector<float> d1xd2(3);
    d1xd2[0] =  d1[1]*d2[2] - d1[2]*d2[1];
    d1xd2[1] = -d1[0]*d2[2] + d1[2]*d2[0];
    d1xd2[2] =  d1[0]*d2[1] - d1[1]*d2[0];
    float len1x2 = 0.;
    for ( int i=0; i<3; i++ ) {
      len1x2 += d1xd2[i]*d1xd2[i];
    }
    len1x2 = sqrt(len1x2);
    float r = len1x2/cluster.pca_len;
    return r;
  }

}
}