Program Listing for File CosmicTrackBuilder.cxx

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

#include "CosmicTrackBuilder.h"

#include "DataFormat/larflow3dhit.h"
#include "DataFormat/track.h"
#include "DataFormat/opflash.h"
#include "LArUtil/LArProperties.h"
#include "LArUtil/Geometry.h"

#include "larflow/SCBoundary/SCBoundary.h"

namespace larflow {
namespace reco {

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

    clear();

    // get clusters, pca-axis
    // std::vector<std::string> producer_v
    //   = { "trackprojsplit_full",
    //       "trackprojsplit_wcfilter" };
    std::vector<std::string> producer_v
      = { "trackprojsplit_full" };


    for ( auto const& producer : producer_v ) {
      larlite::event_larflowcluster* ev_cluster
        = (larlite::event_larflowcluster*)ioll.get_data(larlite::data::kLArFlowCluster, producer);
      larlite::event_pcaxis* ev_pcaxis
        = (larlite::event_pcaxis*)ioll.get_data(larlite::data::kPCAxis,producer);
      loadClusterLibrary( *ev_cluster, *ev_pcaxis );
    }

    buildNodeConnections();

    // make tracks using keypoints
    std::string producer_keypoint = "keypointcosmic";
    larlite::event_larflow3dhit* ev_keypoint
      = (larlite::event_larflow3dhit*)ioll.get_data(larlite::data::kLArFlow3DHit, producer_keypoint );


    for (size_t ikp=0; ikp<ev_keypoint->size(); ikp++) {
      auto const& kp = ev_keypoint->at(ikp);
      std::vector<float> startpt = { kp[0], kp[1], kp[2] };
      buildTracksFromPoint( startpt );
    }

    // make tracks using unused segments
    _buildTracksFromSegments();



    larlite::event_track* evout_track
      = (larlite::event_track*)ioll.get_data(larlite::data::kTrack, "cosmictrack");

    fillLarliteTrackContainer( *evout_track );

    if ( _do_boundary_analysis ) {
      _boundary_analysis_noflash( ioll );
    }

  }

  void CosmicTrackBuilder::_boundary_analysis_noflash( larlite::storage_manager& ioll )
  {

    const float drift_v = larutil::LArProperties::GetME()->DriftVelocity();

    // loop over reconstructed tracks
    larlite::event_track* ev_cosmic
      = (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"cosmictrack");

    // class to calculate distance to space charge boundary
    larflow::scb::SCBoundary scb;

    struct BoundaryAna_t {
      int track_idx;
      int num_ends_on_boundary;
      float x_offset;
    };

    // save boundary matches, one set per track
    std::vector<BoundaryAna_t> track_boundary_v(ev_cosmic->size());

    const float contained_dist = 10;

    for ( int itrack=0; itrack<(int)ev_cosmic->size(); itrack++ ) {

      auto const& track = ev_cosmic->at(itrack);

      LARCV_DEBUG() << "==== TRACK[" << itrack << "] ==========" << std::endl;
      LARCV_DEBUG() << "start(" << track.Vertex()(0) << "," << track.Vertex()(1) << "," << track.Vertex()(2) << ") "
                    << "--> end(" << track.End()(0) << "," << track.End()(1) << "," << track.End()(2) << ") "
                    << std::endl;

      BoundaryAna_t& ba = track_boundary_v.at(itrack);
      ba.track_idx = itrack;
      ba.num_ends_on_boundary = 0;
      ba.x_offset = 0.;

      // mark out-of-time track
      if ( track.Vertex()(0)<0.0 || track.Vertex()(0)>256.0
           || track.End()(0)<0.0 || track.End()(0)>256.0 ) {
        ba.num_ends_on_boundary = 1;
        LARCV_DEBUG() << "track is out-of-time" << std::endl;
        continue;
      }

      // check non anode/cathode boundaries
      if ( 116.0-fabs(track.Vertex()(1))<contained_dist
           || track.Vertex()(2)<contained_dist
           || track.Vertex()(2)>1036-contained_dist
           || 116.0-fabs(track.End()(1))<contained_dist
           || track.End()(2)<contained_dist
           || track.End()(2)>1036.0-contained_dist )  {
        ba.num_ends_on_boundary = 1;
        LARCV_DEBUG() << "track is already near TPC boundary" << std::endl;
        continue;
      }

      // calculate x-position if we shift point to the cathode SCE
      float xboundary_min = scb.XatBoundary( track.Vertex()(0), track.Vertex()(1), track.Vertex()(2) );
      float xboundary_max = scb.XatBoundary( track.End()(0), track.End()(1), track.End()(2) );

      float xshift_min = xboundary_min-track.Vertex()(0);
      float xshift_max = xboundary_max-track.End()(0);

      // if we move the min point, check the max-point
      int btype_min = -1;
      float dwall_min_at_scb = scb.dist2boundary( track.End()(0)+xshift_min, track.End()(1), track.End()(2), btype_min );
      bool min_at_scb_valid  = (track.End()(0)+xshift_min)>=0 && (track.End()(0)+xshift_min)<=256.0;

      // if we move the max point, check the min-point is on the space charge boundary
      int btype_max = -1;
      float dwall_max_at_scb = scb.dist2boundary( track.Vertex()(0)+xshift_max, track.Vertex()(1), track.Vertex()(2), btype_max );
      bool max_at_scb_valid  = (track.Vertex()(0)+xshift_max)>=0 && (track.Vertex()(0)+xshift_max)<=256.0;

      LARCV_DEBUG() << " if is shifted to cathode SCB: " << std::endl;
      LARCV_DEBUG() << "    move min: (" << track.Vertex()(0)+xshift_min << "," <<  track.Vertex()(1) << "," << track.Vertex()(2) << ") <--> "
                    << "(" << track.End()(0)+xshift_min << "," <<  track.End()(1) << "," <<  track.End()(2) << ")"
                    << " dist=" << dwall_min_at_scb << " b=" << btype_min
                    << std::endl;
      LARCV_DEBUG() << "    move max: (" << track.Vertex()(0)+xshift_max << "," <<  track.Vertex()(1) << "," << track.Vertex()(2) << ") <--> "
                    << "(" << track.End()(0)+xshift_max << "," <<  track.End()(1) << "," <<  track.End()(2) << ")"
                    << " dist=" << dwall_max_at_scb << " b=" << btype_max
                    << std::endl;

      // does one of them work
      if ( (fabs(dwall_min_at_scb)<contained_dist && min_at_scb_valid )
           || (fabs(dwall_max_at_scb)<contained_dist && max_at_scb_valid ) ) {
        ba.num_ends_on_boundary = 1;

        if ( min_at_scb_valid && max_at_scb_valid ) {
          if ( fabs(dwall_min_at_scb)<fabs(dwall_max_at_scb) )
            ba.x_offset = xshift_min;
          if ( fabs(dwall_min_at_scb)<fabs(dwall_max_at_scb) )
            ba.x_offset = xshift_max;
        }
        else if ( min_at_scb_valid && !max_at_scb_valid ) {
          ba.x_offset = xshift_max;
        }
        else if ( !min_at_scb_valid && max_at_scb_valid ) {
          ba.x_offset = xshift_min;
        }

        LARCV_DEBUG() << "is thrumu if moved to SCB" << std::endl;
        continue;
      }

      // move min to anode
      int btype_anode_min = -1;
      float dwall_min_at_anode = scb.dist2boundary( track.End()(0)-track.Vertex()(0), track.End()(1), track.End()(2), btype_anode_min );
      bool min_at_anode_valid  = (track.End()(0)-track.Vertex()(0))>=0.0 && (track.End()(0)-track.Vertex()(0))<=256;

      // move max to anode
      int btype_anode_max = -1;
      float dwall_max_at_anode = scb.dist2boundary( track.Vertex()(0)-track.End()(0), track.Vertex()(1), track.Vertex()(2), btype_anode_max );
      bool max_at_anode_valid  = (track.Vertex()(0)-track.End()(0))>=0.0 && (track.Vertex()(0)-track.End()(0))<=256;

      LARCV_DEBUG() << "  track if shifted to anode: " << std::endl;
      LARCV_DEBUG() << "    move min: (" << 0 << "," <<  track.Vertex()(1) << "," << track.Vertex()(2) << ") <--> "
                    << "(" << track.End()(0)-track.Vertex()(0) << "," <<  track.End()(1) << "," <<  track.End()(2) << ")"
                    << " dist2_scb=" << dwall_min_at_anode << " btype=" << btype_anode_min
                    << std::endl;
      LARCV_DEBUG() << "    move max: (" << track.Vertex()(0)-track.End()(0) << "," <<  track.Vertex()(1) << "," << track.Vertex()(2) << ") <--> "
                    << "(" << 0.0 << "," <<  track.End()(1) << "," <<  track.End()(2) << ")"
                    << " dist2_scb=" << dwall_max_at_anode << " btype=" << btype_anode_max
                    << std::endl;

      if ( (fabs(dwall_min_at_anode)<2.0 && min_at_anode_valid )
           || (fabs(dwall_max_at_anode)<2.0 && max_at_anode_valid ) ) {
        ba.num_ends_on_boundary = 1;

        if ( min_at_anode_valid && max_at_anode_valid ) {
          if ( fabs(dwall_min_at_anode)<fabs(dwall_max_at_anode) )
            ba.x_offset = xshift_min;
          if ( fabs(dwall_min_at_anode)<fabs(dwall_max_at_anode) )
            ba.x_offset = xshift_max;
        }
        else if ( min_at_anode_valid && !max_at_anode_valid ) {
          ba.x_offset = xshift_max;
        }
        else if ( !min_at_anode_valid && max_at_anode_valid ) {
          ba.x_offset = xshift_min;
        }

        LARCV_DEBUG() << "is thrumu if moved to anode" << std::endl;
        continue;
      }

      LARCV_DEBUG() << "track is contained" << std::endl;

    }//end of loop over track

    // save track with matches along with flash as a pair
    larlite::event_track* evout_boundary_track =
      (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"boundarycosmic");
    larlite::event_track* evout_boundary_noshift_track =
      (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"boundarycosmicnoshift");
    larlite::event_track* evout_contained_track =
      (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"containedcosmic");

    for ( auto const& bm : track_boundary_v ) {
      auto const& track = ev_cosmic->at(bm.track_idx);
      larlite::track cptrack;
      cptrack.reserve( track.NumberTrajectoryPoints() );
      for ( int ipt=0; ipt<(int)track.NumberTrajectoryPoints(); ipt++ ) {
        const TVector3& pos = track.LocationAtPoint(ipt);
        cptrack.add_vertex( TVector3(pos(0)+bm.x_offset,pos(1),pos(2)) );
        cptrack.add_direction( track.DirectionAtPoint(ipt) );
      }

      if ( bm.num_ends_on_boundary==1 ) {
        evout_boundary_track->emplace_back( std::move(cptrack) );
        evout_boundary_noshift_track->push_back( track );
      }
      else {
        evout_contained_track->emplace_back( std::move(cptrack) );
      }
    }

    LARCV_DEBUG() << "input cosmics=" << ev_cosmic->size() << "; "
                  << "boundary=" << evout_boundary_track->size() << "; "
                  << "contained=" << evout_contained_track->size()
                  << std::endl;

  }

  void CosmicTrackBuilder::_boundary_analysis_wflash( larlite::storage_manager& ioll )
  {
    //

    const float drift_v = larutil::LArProperties::GetME()->DriftVelocity();

    // get the flash info
    std::vector< std::string > flash_producers_v = { "simpleFlashBeam",
                                                     "simpleFlashCosmic" };

    struct FlashInfo_t {
      std::string producer;
      int index;
      float usec;
      float anode_x;
      float cathode_x;
      float centroid_z;
      float centroid_y;
      float totpe;
    };

    std::vector< FlashInfo_t > flash_v;
    flash_v.reserve(100);

    for ( auto& prodname : flash_producers_v ) {
      larlite::event_opflash* ev_flash
        = (larlite::event_opflash*)ioll.get_data( larlite::data::kOpFlash, prodname );

      LARCV_INFO() << "opflashes from " << prodname << ": " << ev_flash->size() << std::endl;

      for (int idx=0; idx<(int)ev_flash->size(); idx++) {
        auto const& flash = ev_flash->at(idx);

        FlashInfo_t info;
        info.producer = prodname;
        info.index    = idx;
        info.usec     = flash.Time(); // usec from trigger
        // calculate x-position, if we assume track that made flash crossed anode
        // calculate x-position, if we assume track that made flash crossed cathode

        info.anode_x   = info.usec*drift_v;
        info.cathode_x = info.anode_x + 256.0;
        info.centroid_z = 0.;
        info.centroid_y = 0.;
        info.totpe = 0.;
        std::vector<float> pe_v(32,0);
        for (int iopdet=0; iopdet<(int)flash.nOpDets(); iopdet++) {
          float pe = flash.PE(iopdet);
          if ( pe<=0 ) {
            continue;
          }
          int ch = iopdet%32;
          if ( pe>pe_v[ch] && ch<32)
            pe_v[ch] = pe;
        }
        for (int ch=0; ch<32; ch++) {
          float pe = pe_v[ch];
          std::vector<double> xyz;
          //larutil::Geometry::GetME()->GetOpDetPosition( ch, xyz );
          larutil::Geometry::GetME()->GetOpChannelPosition( ch, xyz );
          info.totpe += pe;
          info.centroid_z += pe*xyz[2];
          info.centroid_y += pe*xyz[1];
        }
        if ( info.totpe>0 ) {
          info.centroid_z /= info.totpe;
          info.centroid_y /= info.totpe;
        }
        LARCV_DEBUG() << "FLASH[" << flash_v.size() << "] pe=" << info.totpe << " centroid-z=" << info.centroid_z << std::endl;

        flash_v.push_back( info );
      }
    }

    LARCV_INFO() << "OpFlashes loaded: " << flash_v.size() << std::endl;


    // loop over reconstructed tracks
    larlite::event_track* ev_cosmic
      = (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"cosmictrack");

    larflow::scb::SCBoundary scb;

    struct BoundaryMatch_t {
      int track_idx;
      int flash_idx;
      float x_offset;
      float x_min;
      float x_max;
      float minpt_dist2scb;
      float maxpt_dist2scb;
      int minpt_btype;
      int maxpt_btype;
      float dist2scb; // min of the two above, for sorting
      int num_ends_on_boundary;
      float pmt_dz;
      bool operator<( const BoundaryMatch_t& rhs ) const {
        if ( dist2scb<rhs.dist2scb ) return true;
        return false;
      };
    };

    // save boundary matches, one set per track
    std::vector< std::vector<BoundaryMatch_t> > track_boundary_vv(ev_cosmic->size());

    for ( int itrack=0; itrack<(int)ev_cosmic->size(); itrack++ ) {

      auto const& track = ev_cosmic->at(itrack);

      LARCV_DEBUG() << "==== TRACK[" << itrack << "] ==========" << std::endl;
      LARCV_DEBUG() << "   start(" << track.Vertex()(0) << "," << track.Vertex()(1) << "," << track.Vertex()(2) << ") "
                    << "--> end(" << track.End()(0) << "," << track.End()(1) << "," << track.End()(2) << ") "
                    << std::endl;

      float x_min;
      float x_max;
      float pt_min[3];
      float pt_max[3];
      if ( track.Vertex()(0)<track.End()(0) ) {
        x_min = track.Vertex()(0);
        x_max = track.End()(0);
        for (int v=0; v<3; v++) {
          pt_min[v] = track.Vertex()(v);
          pt_max[v] = track.End()(v);
        }
      }
      else {
        x_max = track.Vertex()(0);
        x_min = track.End()(0);
        for (int v=0; v<3; v++) {
          pt_max[v] = track.Vertex()(v);
          pt_min[v] = track.End()(v);
        }
      }

      float z_min;
      float z_max;
      if ( track.Vertex()(2)<track.End()(2) ) {
        z_min = track.Vertex()(2);
        z_max = track.End()(2);
      }
      else {
        z_max = track.Vertex()(2);
        z_min = track.End()(2);
      }

      // we now pair with every flash, along with a null flash match where we shift
      // to the space-charge boundary in x
      int nmatches = 0;   //< number of flashes that if matched to track, moves it to a boundary
      int npossible = 0;  //< number of flashes that were consistent with track
      for (int iflash=0; iflash<(int)flash_v.size(); iflash++) {

        auto const& info = flash_v[iflash];


        float real_x_min = x_min-info.anode_x;
        float real_x_max = x_max-info.anode_x;

        //  we allow for slop of 10 cm;
        float x_offset = 0.;
        if ( real_x_min>-10 && real_x_min<0.0 ) {
          x_offset = -real_x_min;
        }
        else if ( real_x_max>256.0 && real_x_max<256.0+10.0 ) {
          x_offset = 256.0-real_x_max;
        }

        real_x_min += x_offset;
        real_x_max += x_offset;

        // check if track within flash bounds
        if ( real_x_min<0.0 || real_x_min>256.0
             || real_x_max<0.0 || real_x_max>256.0 ) {
          continue; // out-of-bounds
        }

        npossible++;

        // check distance to boundary of ends

        // min bound
        int btype_min = -1;
        float dist2scb_min = scb.dist2boundary( real_x_min, pt_min[1], pt_min[2], btype_min );

        // max bound
        int btype_max = -1;
        float dist2scb_max = scb.dist2boundary( real_x_max, pt_max[1], pt_max[2], btype_max );

        // ok, is anything close to the bound!
        if ( (dist2scb_min<10.0 || dist2scb_max<10.0 ) && btype_min!=btype_max ) {


          // record a boundary match!
          BoundaryMatch_t match;
          match.track_idx = itrack;
          match.flash_idx = iflash;
          match.x_offset = -info.anode_x+x_offset;
          match.x_min = real_x_min;
          match.x_max = real_x_max;
          match.minpt_dist2scb = dist2scb_min;
          match.maxpt_dist2scb = dist2scb_max;
          match.minpt_btype = btype_min;
          match.maxpt_btype = btype_max;
          match.dist2scb = fabs(dist2scb_min)+fabs(dist2scb_max);

          if ( info.centroid_z<z_min )
            match.pmt_dz = info.centroid_z-z_min;
          else if ( info.centroid_z>z_max )
            match.pmt_dz = z_max - info.centroid_z;
          else {
            match.pmt_dz = fabs(0.5*(z_min+z_max) - info.centroid_z);
          }

          match.num_ends_on_boundary = 0;
          if ( dist2scb_min<3.0 ) match.num_ends_on_boundary++;
          if ( dist2scb_max<3.0 ) match.num_ends_on_boundary++;

          LARCV_DEBUG() << "qualifying boundary match: track[" << itrack << "]<->flash[" << iflash << "]" << std::endl;
          LARCV_DEBUG() << "   dist2scb@min extremum pt (" << real_x_min << "," << pt_min[1] << "," << pt_min[2] << "): "
                        << " " << dist2scb_min << std::endl;
          LARCV_DEBUG() << "   dist2scb@max extremum pt (" << real_x_max << "," << pt_max[1] << "," << pt_max[2] << "): "
                        << " " << dist2scb_max << std::endl;
          LARCV_DEBUG() << "  pmt_dz: " << match.pmt_dz << std::endl;


          track_boundary_vv[itrack].push_back(match);
          nmatches++;
        }

      }//end of flash loop

      LARCV_DEBUG() << "track[" << itrack << "] at bounds for " << nmatches << " flashes,"
                    << " of " << npossible << " flash-track matches" << std::endl;
    }//end of track loop


    // now we make boundary tag + flash assignments

    // first we go through tracks where one end x-position is > 100 cm. this is range where being
    //  on the boundary and matching to a flash is valuable info!
    // we first make assignment to tracks with only one 2-boundary matches
    // we then go through others and veto matches to previously matched flashes
    //   of the non-veto'd matches, we pick the ones closest to the boundary

    std::vector< BoundaryMatch_t > final_match_v;
    std::vector<int> flash_used_v( flash_v.size(), 0 );
    std::vector<int> tracks_matched_v( track_boundary_vv.size(), 0 );

    // first pass: accept matches where there is only one solution
    //             one end must be > 100 cm in position
    for ( size_t itrack=0; itrack<track_boundary_vv.size(); itrack++ ) {

      // first, count number of qualifying matches
      int nmatches = 0;
      const BoundaryMatch_t* qualifying_match = nullptr;
      for ( auto const& bm : track_boundary_vv[itrack] ) {

        if ( (bm.x_min>100.0 || bm.x_max>100.0) && bm.dist2scb<3.0 && bm.num_ends_on_boundary==2 ) {
          qualifying_match = &bm;
          nmatches++;
        }

      }

      if ( nmatches==1 ) {
        // accept this match
        final_match_v.push_back( *qualifying_match );
        flash_used_v[ qualifying_match->flash_idx ] = 1;
        tracks_matched_v[ itrack ] = 1;
        LARCV_DEBUG() << "FIRST PASS MATCH: track[" << itrack << "] <--> flash[" << qualifying_match->flash_idx << "]" << std::endl;
      }

    }//end of track loop

    LARCV_DEBUG() << "Number of first pass matches: "
                  << final_match_v.size() << " tracks of " << track_boundary_vv.size() << std::endl;

    // second pass, 2 ends matched, best match viz smallest dist2scb, do not reuse first pass flashes.
    int n2ndpass = 0;
    for ( size_t itrack=0; itrack<track_boundary_vv.size(); itrack++ ) {
      if ( tracks_matched_v[itrack]==1 ) continue;

      // loop through matches, accept only 2-end, exclude previous flash matches
      // rank match by pmt_dz for qualifying matches only
      const BoundaryMatch_t* min_match = nullptr;
      float min_match_dist = -1e9;
      for ( size_t ibm=0; ibm<track_boundary_vv[itrack].size(); ibm++ ) {
        auto const& bm = track_boundary_vv[itrack].at(ibm);
        if( flash_used_v[ bm.flash_idx ]==1 ) continue;

        if ( bm.dist2scb<5.0 && bm.num_ends_on_boundary==2 && min_match_dist<bm.pmt_dz) {
          min_match_dist = bm.pmt_dz;
          min_match = &bm;
        }
      }

      if ( min_match ) {
        final_match_v.push_back( *min_match );
        flash_used_v[ min_match->flash_idx ] = 2;
        tracks_matched_v[itrack] = 2;
        n2ndpass++;
        LARCV_DEBUG() << "SECOND PASS MATCH: track[" << itrack << "] <--> flash[" << min_match->flash_idx << "]" << std::endl;
      }

    }

    LARCV_DEBUG() << "Number of second pass matches: "
                  << n2ndpass << ", total matches: " << final_match_v.size() << " tracks of " << track_boundary_vv.size()
                  << std::endl;

    // third pass: 1 end matched, best match viz smallest dist2scb of matched end, do not reuse first+second pass flashes.
    int n3rdpass = 0;
    for ( size_t itrack=0; itrack<track_boundary_vv.size(); itrack++ ) {
      if ( tracks_matched_v[itrack]>0 ) continue;

      // loop through matches, accept only 2-end, exclude previous flash matches
      const BoundaryMatch_t* min_match = nullptr;
      float min_match_dist = 1e9;
      for ( size_t ibm=0; ibm<track_boundary_vv[itrack].size(); ibm++ ) {
        auto const& bm = track_boundary_vv[itrack].at(ibm);
        if( flash_used_v[ bm.flash_idx ]>0 ) continue;

        if ( bm.num_ends_on_boundary==1 ) {

          float minend_dist = (bm.minpt_dist2scb<bm.maxpt_dist2scb) ? bm.minpt_dist2scb : bm.maxpt_dist2scb;
          if( minend_dist<5.0 && minend_dist<min_match_dist) {
            min_match_dist = minend_dist;
            min_match = &bm;
          }
        }
      }

      if ( min_match ) {
        final_match_v.push_back( *min_match );
        flash_used_v[ min_match->flash_idx ] = 3;
        tracks_matched_v[itrack] = 3;
        n3rdpass++;
        LARCV_DEBUG() << "THIRD PASS MATCH: track[" << itrack << "] <--> flash[" << min_match->flash_idx << "]" << std::endl;
      }

    }//end of third pass track loop
    LARCV_DEBUG() << "Number of third pass matches: "
                  << n3rdpass << ", total matches: " << final_match_v.size() << " tracks of " << track_boundary_vv.size()
                  << std::endl;

    // save track with matches along with flash as a pair
    larlite::event_track* evout_match_track =
      (larlite::event_track*)ioll.get_data(larlite::data::kTrack,"matchedcosmictrack");
    larlite::event_opflash* evout_match_opflash =
      (larlite::event_opflash*)ioll.get_data(larlite::data::kOpFlash,"matchedcosmictrack");

    for ( auto const& bm : final_match_v ) {
      auto const& track = ev_cosmic->at(bm.track_idx);
      larlite::track cptrack;
      cptrack.reserve( track.NumberTrajectoryPoints() );
      for ( int ipt=0; ipt<(int)track.NumberTrajectoryPoints(); ipt++ ) {
        const TVector3& pos = track.LocationAtPoint(ipt);
        cptrack.add_vertex( TVector3(pos(0)+bm.x_offset,pos(1),pos(2)) );
        cptrack.add_direction( track.DirectionAtPoint(ipt) );
      }

      evout_match_track->emplace_back( std::move(cptrack) );

      larlite::event_opflash* ev_flash
        = (larlite::event_opflash*)ioll.get_data( larlite::data::kOpFlash, flash_v[bm.flash_idx].producer );
      evout_match_opflash->push_back( ev_flash->at(flash_v[bm.flash_idx].index) );

    }

  }

}
}