diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..71e3e7e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +ublarcvapp is a C++ framework with Python bindings for analyzing Liquid Argon Time Projection Chamber (LArTPC) data from the MicroBooNE neutrino physics experiment. It provides tools for: +- Deep learning-based particle tagging and classification +- 3D reconstruction algorithms +- Image manipulation and processing +- Monte Carlo truth analysis +- Event filtering and selection + +## Build Commands + +### Prerequisites Setup +Ensure these environment variables are set: +- `LARCV_LIBDIR` - LArCV library directory +- `LARLITE_LIBDIR` and `LARLITE_INCDIR` - larlite directories +- `LARCV_INCDIR` - LArCV include directory +- `LAROPENCV_LIBDIR` and `LAROPENCV_INCDIR` - LArOpenCV directories +- Optional: `GEO2D_BASEDIR` - for Geo2D support + +### Building +```bash +mkdir build +cd build +cmake .. +make -j4 +make install +source ../configure.sh +``` + +### Running Tests +Tests are located in individual module `test/` directories. Example: +```bash +cd ublarcvapp/Reco3D/test +python test_astar.py + +cd ublarcvapp/UBImageMod/test +python test_ubsplit.py [input_larcv_file] [adc_producer_name] +``` + +## Code Architecture + +### Core Components + +1. **Processing Framework** (`LLCVProcessor/`) + - `LLCVProcessBase` - Base class for all processing modules + - `LLCVProcessDriver` - Manages processing chain execution + - Configuration via `.cfg` files + +2. **Image Processing** (`UBImageMod/`) + - `UBSplitDetector` - Splits detector images into subregions + - `UBCropLArFlow` - Crops sparse larflow data + - `InfillImageStitcher` - Stitches subimages back together + +3. **3D Reconstruction** (`Reco3D/`) + - `AStar3DAlgo` - A* pathfinding for 3D track reconstruction + - Configuration through `AStar3DAlgoConfig` + - Supports both regular and proton-specific tracking + +4. **Deep Learning Integration** (`DLTagger/`) + - `DLTagger` - Main interface for DL model predictions + - `MRCNNMatch` - Mask R-CNN matching algorithms + - Integrates with external DL model servers + +5. **Monte Carlo Tools** (`MCTools/`) + - Truth matching utilities + - Space charge effect corrections + - Flash matching algorithms + +### Key Design Patterns + +1. **Factory Pattern**: Most algorithms use factory registration: + ```cpp + static LArbysImageFactory __global_LArbysImageFactory__; + ``` + +2. **Configuration**: Algorithms use dedicated config classes: + ```cpp + class AlgoConfig : public larcv::PSet { + // Configuration parameters + }; + ``` + +3. **Process Chain**: Modules inherit from `LLCVProcessBase` and implement: + - `configure()` - Setup from config file + - `initialize()` - One-time initialization + - `process()` - Per-event processing + - `finalize()` - Cleanup + +### Data Flow + +1. Input: ROOT files containing larcv::EventImage2D, larlite::event_* objects +2. Processing: Chain of LLCVProcess modules +3. Output: Modified/new data products saved to output ROOT files + +## Development Guidelines + +### Adding New Modules + +1. Create directory under `ublarcvapp/` +2. Add CMakeLists.txt with: + ```cmake + set(MODULE_NAME YourModule) + add_subdirectory(${MODULE_NAME}) + ``` +3. Implement classes inheriting from `LLCVProcessBase` +4. Add LinkDef.h for ROOT dictionary generation +5. Register with factory if needed + +### Working with Sparse Data + +Many algorithms work with sparse representations: +- Use `larcv::EventSparseTensor2D` for sparse image data +- Convert between dense/sparse with utility functions +- Handle coordinate transformations carefully + +### Coordinate Systems + +Be aware of multiple coordinate systems: +- Wire/tick 2D coordinates per plane +- 3D spatial coordinates (with/without space charge effects) +- Image pixel coordinates +- Detector geometry coordinates \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c791cc..75d8110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,14 +7,28 @@ if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) endif() # CFLAGS -SET(CMAKE_C_FLAGS "-std=gnu99 -pedantic -fPIC -pedantic -fopenmp -mtune=native") -SET(CMAKE_CXX_FLAGS "-std=c++11 -Wall -fPIC -pedantic -fopenmp -mtune=native") +#SET(CMAKE_C_FLAGS "-std=gnu99 -pedantic -fPIC -pedantic -fopenmp -mtune=native") +#SET(CMAKE_CXX_FLAGS "-Wall -fPIC -pedantic -fopenmp -mtune=native") ## Dependencies #---CERN ROOT (required) find_package(ROOT CONFIG REQUIRED) - +# we set the compiler version based on the one used for ROOT +execute_process( + COMMAND bash "-c" "root-config --features" + OUTPUT_VARIABLE TEST_ROOT_CXX_STANDARD ) +if (${TEST_ROOT_CXX_STANDARD} MATCHES "cxx17") + message( STATUS "using cxx17") + set(CMAKE_CXX_STANDARD 17) +elseif (${TEST_ROOT_CXX_STANDARD} MATCHES "cxx14") + message( STATUS "using cxx14") + set(CMAKE_CXX_STANDARD 14) +else () + message( STATUS "using cxx11") + set(CMAKE_CXX_STANDARD 11) +endif() + #---LArCV (required) set( larcv_DIR "$ENV{LARCV_LIBDIR}/cmake/larcv") find_package( larcv REQUIRED ) @@ -55,9 +69,12 @@ endif() #---Eigen3 find_package(Eigen3 REQUIRED) +get_property(EIGEN3_INCLUDE_DIRS TARGET Eigen3::Eigen PROPERTY INTERFACE_INCLUDE_DIRECTORIES ) #---LArLite -set( LARLITE_INC_DIRS "$ENV{LARLITE_COREDIR}" "$ENV{LARLITE_BASEDIR}" "$ENV{LARLITE_USERDEVDIR}" "$ENV{LARLITE_BASEDIR}/../" ) +set( larlite_DIR $ENV{LARLITE_LIBDIR}/cmake/larlite ) +find_package( larlite REQUIRED ) +set( LARLITE_INC_DIRS "$ENV{LARLITE_INCDIR}" ) set( LARLITE_LIB_DIR "$ENV{LARLITE_LIBDIR}" ) set( HAS_LARLITE 1 ) file( GLOB LARLITE_LIBS "$ENV{LARLITE_LIBDIR}/libLArLite*.so" ) @@ -83,9 +100,42 @@ if (Boost_FOUND) message(STATUS "Using boost libraries: ${Boost_LIBRARIES}") message(STATUS "Using boost thread library: ${Boost_THREAD_LIBRARY}") link_directories(${Boost_LIBRARY_DIRS}) +else() + message(STATUS "Boost not found") endif (Boost_FOUND) -#INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} ) +#---HDF5 (for machine learning data output) +find_package(HDF5 COMPONENTS C CXX QUIET) +if(HDF5_FOUND) + message(STATUS "Found HDF5: ${HDF5_VERSION}") + add_compile_definitions(HAVE_HDF5) +else() + message(STATUS "HDF5 not found, HDF5 output features will be disabled") +endif() + +#---HighFive (header-only HDF5 C++ interface) +if(HDF5_FOUND) + # Try to find HighFive as an installed package first + find_package(HighFive QUIET) + if(NOT HighFive_FOUND) + # If not found, check if it exists as a submodule or in a known location + if(EXISTS "$ENV{UBDL_BASEDIR}/extern/highfive/include") + set(HIGHFIVE_INCLUDE_DIR "$ENV{UBDL_BASEDIR}/extern/highfive/include") + message(STATUS "Found HighFive headers at: ${HIGHFIVE_INCLUDE_DIR}") + set(HighFive_FOUND TRUE) + else() + message(STATUS "HighFive not found. To use HDF5 features, please:") + message(STATUS " 1. Install HighFive: https://github.com/highfive-devs/highfive/tree/main ") + message(STATUS " 2. Or add as submodule: git submodule add https://github.com/highfive-devs/highfive/tree/main extern/highfive") + endif() + else() + message(STATUS "Found HighFive package") + endif() + + if(HighFive_FOUND) + add_compile_definitions(HAVE_HIGHFIVE) + endif() +endif() #--- glog #find_package(glog REQUIRED) diff --git a/python/ublarcvapp/__init__.py b/python/ublarcvapp/__init__.py index 424ea28..fe67b52 100644 --- a/python/ublarcvapp/__init__.py +++ b/python/ublarcvapp/__init__.py @@ -17,3 +17,4 @@ ROOT.gSystem.Load(l) import ROOT.ublarcvapp as ublarcvapp +ublarcvapp.mctools.MCPixelPGraph diff --git a/ublarcvapp/CMakeLists.txt b/ublarcvapp/CMakeLists.txt index bebffc2..c489072 100644 --- a/ublarcvapp/CMakeLists.txt +++ b/ublarcvapp/CMakeLists.txt @@ -1,22 +1,19 @@ -add_subdirectory(LLCVProcessor) add_subdirectory(dbscan) -add_subdirectory(Reco3D) -add_subdirectory(ContourTools) - +#add_subdirectory(Reco3D) +#add_subdirectory(LArOpenCVHandle) +add_subdirectory(ContourTools) # requires opencv, laropencv if (HAS_LARLITE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") add_subdirectory(LArliteHandler) + add_subdirectory(LLCVProcessor) add_subdirectory(UBImageMod) add_subdirectory(UBWireTool) add_subdirectory(ubdllee) - add_subdirectory(Filter) +# add_subdirectory(Filter) add_subdirectory(MCTools) + add_subdirectory(UBPhotonLib) + add_subdirectory(ParticleToPixelUtils) endif() - - -if (LARCV_USE_GEO2D) - add_subdirectory(LArOpenCVHandle) -endif() - -if (Boost_FOUND) - add_subdirectory(DLTagger) -endif() +#if (Boost_FOUND) +# add_subdirectory(DLTagger) +#endif() diff --git a/ublarcvapp/ContourTools/CMakeLists.txt b/ublarcvapp/ContourTools/CMakeLists.txt index 7ad2f7e..f51b022 100644 --- a/ublarcvapp/ContourTools/CMakeLists.txt +++ b/ublarcvapp/ContourTools/CMakeLists.txt @@ -1,31 +1,61 @@ set(MODULE_NAME ContourTools) +set(LIBNAME LArCVApp_${MODULE_NAME}) + # Collect the headers -file(GLOB HEADERS "*.h") +set( HEADERS + ContourShapeMeta.h + ContourCluster.h + ContourClusterAlgo.h + #ContourAStarClusterAlgo.h +) -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") +# Collect the sources +add_library( ${LIBNAME} SHARED + ContourShapeMeta.cxx + ContourCluster.cxx + ContourClusterAlgo.cxx + #ContourAStarClusterAlgo.cxx +) -# library name -set(LIBNAME LArCVApp_${MODULE_NAME}) -#message("opencv libs: ${OpenCV_LIBS}") -#message("laropencv libs: ${LAROPENCV_LIBS}") -#message("opencv inc: ${OpenCV_INCLUDE_DIRS}") +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -# includes -include_directories(${LARCV_INCLUDE_DIR} $ENV{LAROPENCV_BASEDIR} $ENV{UBLARCVAPP_BASEDIR} ${OpenCV_INCLUDE_DIRS} ${GEO2D_INC_DIR} $ENV{LARLITE_USERDEVDIR} ) +target_include_directories(${LIBNAME} + PUBLIC + $ + PRIVATE + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${GEO2D_INC_DIR} + ${LAROPENCV_INC_DIR} +) +#target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) +target_link_libraries( ${LIBNAME} PUBLIC + opencv_imgcodecs + opencv_core + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreCVUtil + ${GEO2D_LIBS} + ${LAROPENCV_LIBS} + #LArCVApp_LArOpenCVHandle + #LArCVApp_Reco3D +) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${GEO2D_INC_DIR} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS} ) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreCVUtil LArCVApp_LArOpenCVHandle LArCVApp_Reco3D ${LAROPENCV_LIBS} ${GEO2D_LIBS} opencv_core) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/ContourTools/ContourAStarClusterAlgo.cxx b/ublarcvapp/ContourTools/ContourAStarClusterAlgo.cxx index 81d8313..c1d9423 100644 --- a/ublarcvapp/ContourTools/ContourAStarClusterAlgo.cxx +++ b/ublarcvapp/ContourTools/ContourAStarClusterAlgo.cxx @@ -4,8 +4,8 @@ #include // larlite -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" // larcv #include "larcv/core/CVUtil/CVUtil.h" diff --git a/ublarcvapp/ContourTools/ContourAStarClusterAlgo.h b/ublarcvapp/ContourTools/ContourAStarClusterAlgo.h index 2b74363..196fddb 100644 --- a/ublarcvapp/ContourTools/ContourAStarClusterAlgo.h +++ b/ublarcvapp/ContourTools/ContourAStarClusterAlgo.h @@ -20,7 +20,7 @@ #include #include -#include "ContourShapeMeta.h" +#include "ublarcvapp/ContourTools/ContourShapeMeta.h" namespace ublarcvapp { diff --git a/ublarcvapp/ContourTools/ContourCluster.h b/ublarcvapp/ContourTools/ContourCluster.h index 1de7d3b..361e534 100644 --- a/ublarcvapp/ContourTools/ContourCluster.h +++ b/ublarcvapp/ContourTools/ContourCluster.h @@ -4,13 +4,13 @@ #include #include -#include "ContourShapeMeta.h" - #ifdef USE_OPENCV #include #include #endif +#include "ublarcvapp/ContourTools/ContourShapeMeta.h" + namespace ublarcvapp { class ContourCluster : public std::vector< std::vector > { diff --git a/ublarcvapp/ContourTools/ContourClusterAlgo.h b/ublarcvapp/ContourTools/ContourClusterAlgo.h index 444cffe..f302de7 100644 --- a/ublarcvapp/ContourTools/ContourClusterAlgo.h +++ b/ublarcvapp/ContourTools/ContourClusterAlgo.h @@ -14,7 +14,7 @@ #include -#include "ContourShapeMeta.h" +#include "ublarcvapp/ContourTools/ContourShapeMeta.h" namespace ublarcvapp { diff --git a/ublarcvapp/DLTagger/CMakeLists.txt b/ublarcvapp/DLTagger/CMakeLists.txt index 43be887..b4b9826 100644 --- a/ublarcvapp/DLTagger/CMakeLists.txt +++ b/ublarcvapp/DLTagger/CMakeLists.txt @@ -37,6 +37,7 @@ add_subdirectory( mrcnnmatch ) message("headers: ${HEADERS}") message("project source dir: ${PROJECT_SOURCE_DIR}") message("eigen3: ${EIGEN3_INCLUDE_DIRS}") +message("boost include dir: ${Boost_INCLUDE_DIRS}") get_target_property(ALL_HEADERS ${LIBNAME} PUBLIC_HEADER) @@ -57,7 +58,7 @@ target_include_directories(${LIBNAME} ) target_link_libraries(${LIBNAME} ${LARLITE_LIBS_USED} ${LARCV_LIBS_USED} ${UBLARCVAPP_LIBS_USED} ${LAROPENCV_LIBS} ${GEO2D_LIBS} opencv_core) -include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/mrcnnmatch ${LARCV_INCLUDE_DIR} ${LARLITE_INCLUDES} $ENV{CILANTRO_INC_DIR} ${EIGEN3_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS} ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/mrcnnmatch ${LARCV_INCLUDE_DIR} ${LARLITE_INCLUDES} $ENV{CILANTRO_INC_DIR} ${EIGEN3_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${ALL_HEADERS} LINKDEF LinkDef.h) target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) diff --git a/ublarcvapp/DLTagger/DLTagger.cxx b/ublarcvapp/DLTagger/DLTagger.cxx index f5ee3f4..c6219bb 100644 --- a/ublarcvapp/DLTagger/DLTagger.cxx +++ b/ublarcvapp/DLTagger/DLTagger.cxx @@ -6,8 +6,8 @@ #include "ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.h" #include "ublarcvapp/ContourTools/ContourClusterAlgo.h" -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" #include "Geo2D/Core/Geo2D.h" diff --git a/ublarcvapp/DLTagger/DLTagger.h b/ublarcvapp/DLTagger/DLTagger.h index a75fa97..aac727a 100644 --- a/ublarcvapp/DLTagger/DLTagger.h +++ b/ublarcvapp/DLTagger/DLTagger.h @@ -11,8 +11,8 @@ #include "larcv/core/DataFormat/ClusterMask.h" // larlite -#include "DataFormat/opflash.h" -#include "DataFormat/track.h" +#include "larlite/DataFormat/opflash.h" +#include "larlite/DataFormat/track.h" // mrcnnmatch #include "MRCNNMatch.h" diff --git a/ublarcvapp/DLTagger/DLTaggerProcess.cxx b/ublarcvapp/DLTagger/DLTaggerProcess.cxx index 1992b90..f0db24f 100644 --- a/ublarcvapp/DLTagger/DLTaggerProcess.cxx +++ b/ublarcvapp/DLTagger/DLTaggerProcess.cxx @@ -1,7 +1,7 @@ #include "DLTaggerProcess.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "larcv/core/DataFormat/EventImage2D.h" #include "larcv/core/DataFormat/EventChStatus.h" diff --git a/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.cxx b/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.cxx index 9c09612..0bfea16 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.cxx +++ b/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.cxx @@ -1,8 +1,8 @@ #include "AStarMaskCombo.h" #include "ublarcvapp/UBImageMod/EmptyChannelAlgo.h" -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" namespace ublarcvapp { namespace dltagger { diff --git a/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.h b/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.h index 04510a4..1948702 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.h +++ b/ublarcvapp/DLTagger/mrcnnmatch/AStarMaskCombo.h @@ -4,7 +4,7 @@ #include // larlite -#include "DataFormat/track.h" +#include "larlite/DataFormat/track.h" // larcv #include "larcv/core/Base/larcv_base.h" @@ -24,9 +24,9 @@ namespace dltagger { public: AStarMaskCombo() : larcv::larcv_base("AStarMaskCombo"), - pEndpoint(nullptr), astar_completed(0), - used_astar(false) + used_astar(false), + pEndpoint(nullptr) {}; virtual ~AStarMaskCombo() {}; diff --git a/ublarcvapp/DLTagger/mrcnnmatch/CMakeLists.txt b/ublarcvapp/DLTagger/mrcnnmatch/CMakeLists.txt index a4a2854..d2e4503 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/CMakeLists.txt +++ b/ublarcvapp/DLTagger/mrcnnmatch/CMakeLists.txt @@ -2,16 +2,20 @@ # submodule name set(SUBMODULE_NAME MRCNNMatch) -# However, the file(GLOB...) allows for wildcard additions: +# this is a submodule of LARCVAPP_DLTagger +# for the source below, good practice is to get full paths, so we need +# the directory of target property +get_target_property(SOURCE_DIR LArCVApp_DLTagger SOURCE_DIR) + set(SOURCES - mrcnnmatch/MRCNNMatch.cxx - mrcnnmatch/MRCNNMatchConfig.cxx - mrcnnmatch/MRCNNMatchTypes.cxx - mrcnnmatch/CropMaskCombo.cxx - mrcnnmatch/FeaturesMaskCombo.cxx - mrcnnmatch/Gen3DEndpoints.cxx - mrcnnmatch/GenGraphPoints.cxx - mrcnnmatch/AStarMaskCombo.cxx + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatch.cxx + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatchConfig.cxx + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatchTypes.cxx + ${SOURCE_DIR}/mrcnnmatch/CropMaskCombo.cxx + ${SOURCE_DIR}/mrcnnmatch/FeaturesMaskCombo.cxx + ${SOURCE_DIR}/mrcnnmatch/Gen3DEndpoints.cxx + ${SOURCE_DIR}/mrcnnmatch/GenGraphPoints.cxx + ${SOURCE_DIR}/mrcnnmatch/AStarMaskCombo.cxx ) # ADD THE SOURCES @@ -20,13 +24,13 @@ target_sources( ${LIBNAME} PRIVATE ${SOURCES} ) # append the list of public headers for the target set_property( TARGET ${LIBNAME} APPEND PROPERTY PUBLIC_HEADER - mrcnnmatch/MRCNNMatch.h - mrcnnmatch/MRCNNMatchConfig.h - mrcnnmatch/MRCNNMatchTypes.h - mrcnnmatch/CropMaskCombo.h - mrcnnmatch/FeaturesMaskCombo.h - mrcnnmatch/Gen3DEndpoints.h - mrcnnmatch/GenGraphPoints.h - mrcnnmatch/AStarMaskCombo.h + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatch.h + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatchConfig.h + ${SOURCE_DIR}/mrcnnmatch/MRCNNMatchTypes.h + ${SOURCE_DIR}/mrcnnmatch/CropMaskCombo.h + ${SOURCE_DIR}/mrcnnmatch/FeaturesMaskCombo.h + ${SOURCE_DIR}/mrcnnmatch/Gen3DEndpoints.h + ${SOURCE_DIR}/mrcnnmatch/GenGraphPoints.h + ${SOURCE_DIR}/mrcnnmatch/AStarMaskCombo.h ) diff --git a/ublarcvapp/DLTagger/mrcnnmatch/CropMaskCombo.cxx b/ublarcvapp/DLTagger/mrcnnmatch/CropMaskCombo.cxx index 87c2e84..8390db6 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/CropMaskCombo.cxx +++ b/ublarcvapp/DLTagger/mrcnnmatch/CropMaskCombo.cxx @@ -1,7 +1,7 @@ #include "CropMaskCombo.h" // larlite -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/Geometry.h" namespace ublarcvapp { namespace dltagger { diff --git a/ublarcvapp/DLTagger/mrcnnmatch/Gen3DEndpoints.cxx b/ublarcvapp/DLTagger/mrcnnmatch/Gen3DEndpoints.cxx index c336da9..7856b0c 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/Gen3DEndpoints.cxx +++ b/ublarcvapp/DLTagger/mrcnnmatch/Gen3DEndpoints.cxx @@ -1,7 +1,7 @@ #include "Gen3DEndpoints.h" // larlite -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/Geometry.h" // ublarcvapp #include "ublarcvapp/UBWireTool/UBWireTool.h" diff --git a/ublarcvapp/DLTagger/mrcnnmatch/GenGraphPoints.cxx b/ublarcvapp/DLTagger/mrcnnmatch/GenGraphPoints.cxx index 0d7fe42..7e80877 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/GenGraphPoints.cxx +++ b/ublarcvapp/DLTagger/mrcnnmatch/GenGraphPoints.cxx @@ -1,7 +1,7 @@ #include "GenGraphPoints.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "ublarcvapp/UBWireTool/UBWireTool.h" diff --git a/ublarcvapp/DLTagger/mrcnnmatch/MRCNNMatchTypes.cxx b/ublarcvapp/DLTagger/mrcnnmatch/MRCNNMatchTypes.cxx index 749f7bf..5d063f6 100644 --- a/ublarcvapp/DLTagger/mrcnnmatch/MRCNNMatchTypes.cxx +++ b/ublarcvapp/DLTagger/mrcnnmatch/MRCNNMatchTypes.cxx @@ -1,7 +1,7 @@ #include "MRCNNMatchTypes.h" // larlite -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/Geometry.h" namespace ublarcvapp { namespace dltagger { diff --git a/ublarcvapp/LArOpenCVHandle/CMakeLists.txt b/ublarcvapp/LArOpenCVHandle/CMakeLists.txt index a36d5ff..199b981 100644 --- a/ublarcvapp/LArOpenCVHandle/CMakeLists.txt +++ b/ublarcvapp/LArOpenCVHandle/CMakeLists.txt @@ -1,37 +1,108 @@ set(MODULE_NAME LArOpenCVHandle) -# Collect the headers -file(GLOB HEADERS "*.h") - -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") - -# library name set(LIBNAME LArCVApp_${MODULE_NAME}) -# includes -include_directories(${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${LARLITE_INCDIRS} ${GEO2D_INC_DIR} ${LAROPENCV_INC_DIR} ${OpenCV_INCLUDE_DIRS} ) -#message("opencv: ${OpenCV_INCLUDE_DIRS}") -#message("opencv: ${OpenCV_LIBS}") +# Collect the headers +set( HEADERS + BadRegionAna.h + BlankImage.h + CosmicPixelAna.h + CosmicTrackAna.h + CosmicVertexAna.h + DeadWireAna.h + GenDeadChannelMap.h + GoodcROIFilter.h + ImageROICheckMC.h + InterWriteOutLC.h + LArbysImage.h + LArbysImageExtract.h + LArbysImageMC.h + LArbysImageMaker.h + LArbysLoader.h + LArbysUtils.h + OperateImage.h + PGraphTruthMatch.h + PIDImageMaker.h + PostTagger.h + PreProcessor.h + ROIAna.h + SSNetEventAna.h + SSNetTruthAna.h + SegmentFilter.h + SingleROIFaker.h + VertexAna.h + VertexFilter.h + VertexInROI.h +) -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +add_library( ${LIBNAME} SHARED + BadRegionAna.cxx + BlankImage.cxx + CosmicPixelAna.cxx + CosmicTrackAna.cxx + CosmicVertexAna.cxx + DeadWireAna.cxx + GenDeadChannelMap.cxx + GoodcROIFilter.cxx + ImageROICheckMC.cxx + InterWriteOutLC.cxx + LArbysImage.cxx + LArbysImageExtract.cxx + LArbysImageMC.cxx + LArbysImageMaker.cxx + LArbysLoader.cxx + LArbysUtils.cxx + OperateImage.cxx + PGraphTruthMatch.cxx + PIDImageMaker.cxx + PostTagger.cxx + PreProcessor.cxx + ROIAna.cxx + SSNetEventAna.cxx + SSNetTruthAna.cxx + SegmentFilter.cxx + SingleROIFaker.cxx + VertexAna.cxx + VertexFilter.cxx + VertexInROI.cxx +) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_include_directories(${LIBNAME} + PUBLIC + $ + PRIVATE + ${PROJECT_SOURCE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${LARLITE_INCLUDE_DIR} + ${GEO2D_INC_DIR} + ${LAROPENCV_INC_DIR} + ${LARCV_INCLUDE_DIR} +) +target_link_libraries( ${LIBNAME} PUBLIC + opencv_imgcodecs + opencv_core + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreCVUtil + ${GEO2D_LIBS} + ${LAROPENCV_LIBS} + LArCVApp_Reco3D +) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${LARLITE_INCLUDE_DIR} + ${GEO2D_INC_DIR} + ${LAROPENCV_INC_DIR} + ${LARCV_INCLUDE_DIR} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor opencv_core) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.cxx b/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.cxx index 9279bd9..c97e97a 100644 --- a/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.cxx @@ -5,8 +5,8 @@ #include "LArbysImageMaker.h" #include "larcv/core/DataFormat/EventROI.h" #include "LArbysUtils.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include #include #include "larcv/core/CVUtil/CVUtil.h" diff --git a/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.h b/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.h index 65c4029..c2a293f 100644 --- a/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.h +++ b/ublarcvapp/LArOpenCVHandle/CosmicPixelAna.h @@ -4,7 +4,7 @@ #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" #include "LArbysImageMaker.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/CosmicVertexAna.cxx b/ublarcvapp/LArOpenCVHandle/CosmicVertexAna.cxx index 19e1681..48b415a 100644 --- a/ublarcvapp/LArOpenCVHandle/CosmicVertexAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/CosmicVertexAna.cxx @@ -9,8 +9,8 @@ #include "larcv/core/DataFormat/EventPixel2D.h" #include "larcv/core/DataFormat/EventImage2D.h" #include "LArOpenCV/ImageCluster/Base/ImageClusterTypes.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "LArbysUtils.h" #include "LArbysImageMaker.h" #include "LArOpenCV/ImageCluster/AlgoFunction/Contour2DAnalysis.h" diff --git a/ublarcvapp/LArOpenCVHandle/DeadWireAna.cxx b/ublarcvapp/LArOpenCVHandle/DeadWireAna.cxx index 2aa8173..ba78caf 100644 --- a/ublarcvapp/LArOpenCVHandle/DeadWireAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/DeadWireAna.cxx @@ -4,8 +4,8 @@ #include "DeadWireAna.h" #include "larcv/core/DataFormat/EventImage2D.h" #include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/DeadWireAna.h b/ublarcvapp/LArOpenCVHandle/DeadWireAna.h index 62211c1..dd36583 100644 --- a/ublarcvapp/LArOpenCVHandle/DeadWireAna.h +++ b/ublarcvapp/LArOpenCVHandle/DeadWireAna.h @@ -4,7 +4,7 @@ #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.cxx b/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.cxx index 01f3508..4e6cce4 100644 --- a/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.cxx +++ b/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.cxx @@ -4,8 +4,8 @@ #include "GoodcROIFilter.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.h b/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.h index 65ea1a9..1e7b68f 100644 --- a/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.h +++ b/ublarcvapp/LArOpenCVHandle/GoodcROIFilter.h @@ -18,7 +18,7 @@ #include "larcv/core/Processor/ProcessFactory.h" #include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/LArbysImage.cxx b/ublarcvapp/LArOpenCVHandle/LArbysImage.cxx index e45c951..2a0827b 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysImage.cxx +++ b/ublarcvapp/LArOpenCVHandle/LArbysImage.cxx @@ -19,6 +19,10 @@ namespace larcv { static LArbysImageProcessFactory __global_LArbysImageProcessFactory__; + LArbysImage::LArbysImage() + : LArbysImage("LArbysImage") + {}; + LArbysImage::LArbysImage(const std::string name) : ProcessBase(name), _image_cluster_cfg("aho"), @@ -483,8 +487,8 @@ namespace larcv { kINVALID_DOUBLE); // Store the type - if (par.type==larocv::data::ParticleType_t::kTrack) proi.Shape(kShapeTrack); - else if (par.type==larocv::data::ParticleType_t::kShower) proi.Shape(kShapeShower); + if (par.type==larocv::data::ParticleCluster::ParticleType_t::kTrack) proi.Shape(kShapeTrack); + else if (par.type==larocv::data::ParticleCluster::ParticleType_t::kShower) proi.Shape(kShapeShower); // Push the ROI into the PGraph LARCV_DEBUG() << " @ pg array index " << pidx << std::endl; diff --git a/ublarcvapp/LArOpenCVHandle/LArbysImage.h b/ublarcvapp/LArOpenCVHandle/LArbysImage.h index 36bb566..5fc52ec 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysImage.h +++ b/ublarcvapp/LArOpenCVHandle/LArbysImage.h @@ -19,10 +19,11 @@ namespace larcv { public: /// Default constructor - LArbysImage(const std::string name="LArbysImage"); + LArbysImage(); + LArbysImage(const std::string name); /// Default destructor - virtual ~LArbysImage(){} + virtual ~LArbysImage(){}; void configure(const PSet&); diff --git a/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.cxx b/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.cxx index e61ffd5..14a3ec2 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.cxx +++ b/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.cxx @@ -7,6 +7,10 @@ namespace larcv { static LArbysImageExtractProcessFactory __global_LArbysImageExtractProcessFactory__; + LArbysImageExtract::LArbysImageExtract() + : LArbysImageExtract("LArbysImageExtract") + {} + LArbysImageExtract::LArbysImageExtract(const std::string name) : ProcessBase(name), _LArbysImageMaker() diff --git a/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.h b/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.h index 8575e61..d580b22 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.h +++ b/ublarcvapp/LArOpenCVHandle/LArbysImageExtract.h @@ -12,9 +12,10 @@ namespace larcv { class LArbysImageExtract : public ProcessBase { public: - - LArbysImageExtract(const std::string name="LArbysImageExtract"); - ~LArbysImageExtract(){} + + LArbysImageExtract(); + LArbysImageExtract(const std::string name); + ~LArbysImageExtract(){}; void configure(const PSet&); void initialize(); diff --git a/ublarcvapp/LArOpenCVHandle/LArbysImageMC.h b/ublarcvapp/LArOpenCVHandle/LArbysImageMC.h index 7fc6505..635a344 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysImageMC.h +++ b/ublarcvapp/LArOpenCVHandle/LArbysImageMC.h @@ -6,7 +6,7 @@ #include #include "TTree.h" -#include "LArUtil/PxUtils.h" +#include "larlite/LArUtil/PxUtils.h" #include "larcv/core/DataFormat/Image2D.h" #include "Geo2D/Core/HalfLine.h" #include "Geo2D/Core/Line.h" diff --git a/ublarcvapp/LArOpenCVHandle/LArbysUtils.cxx b/ublarcvapp/LArOpenCVHandle/LArbysUtils.cxx index 5233a83..3da99fe 100644 --- a/ublarcvapp/LArOpenCVHandle/LArbysUtils.cxx +++ b/ublarcvapp/LArOpenCVHandle/LArbysUtils.cxx @@ -2,8 +2,8 @@ #define LARBYSUTILS_CXX #include "LArbysUtils.h" -#include "LArUtil/GeometryHelper.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/GeometryHelper.h" +#include "larlite/LArUtil/LArProperties.h" #include #include diff --git a/ublarcvapp/LArOpenCVHandle/OperateImage.h b/ublarcvapp/LArOpenCVHandle/OperateImage.h index a6c4b41..da92791 100644 --- a/ublarcvapp/LArOpenCVHandle/OperateImage.h +++ b/ublarcvapp/LArOpenCVHandle/OperateImage.h @@ -35,7 +35,7 @@ namespace larcv { /// Default destructor ~OperateImage(){} - enum class Operation_t : uint { kSum, kSubtract }; //, kMultiply, kDivide }; + enum Operation_t : uint { kSum, kSubtract }; //, kMultiply, kDivide }; void configure(const PSet&); void initialize() {} diff --git a/ublarcvapp/LArOpenCVHandle/PreProcessor.cxx b/ublarcvapp/LArOpenCVHandle/PreProcessor.cxx index c6145ba..30ba50f 100644 --- a/ublarcvapp/LArOpenCVHandle/PreProcessor.cxx +++ b/ublarcvapp/LArOpenCVHandle/PreProcessor.cxx @@ -207,8 +207,8 @@ namespace larcv { pchunk.overallPCA = larocv::CalcPCA(ctor); pchunk.edge1PCA = larocv::SquarePCA(img,edge1,_pca_box_size,_pca_box_size); pchunk.edge2PCA = larocv::SquarePCA(img,edge2,_pca_box_size,_pca_box_size); - pchunk.track_frac = type==larocv::ChunkType_t::kTrack ? 1 : 0; - pchunk.shower_frac = type==larocv::ChunkType_t::kShower ? 1 : 0; + pchunk.track_frac = type==larocv::ChunkType_t::kTrackPixelChunk ? 1 : 0; + pchunk.shower_frac = type==larocv::ChunkType_t::kShowerPixelChunk ? 1 : 0; auto masked_pts=larocv::MaskImage(img,ctor,0,false); pchunk.mean_pixel_dist = larocv::MeanDistanceToLine(masked_pts,pchunk.overallPCA); pchunk.sigma_pixel_dist = larocv::SigmaDistanceToLine(masked_pts,pchunk.overallPCA); @@ -302,8 +302,8 @@ namespace larcv { auto track_img_t = PrepareImage(track_img); auto shower_img_t = PrepareImage(shower_img); - auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrack,false); - auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShower,false); + auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrackPixelChunk,false); + auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShowerPixelChunk,false); LARCV_DEBUG() << "Track chunks " << track_pchunk_v.size() << " & Shower chunks " << shower_pchunk_v.size() << std::endl; @@ -335,8 +335,8 @@ namespace larcv { auto track_img_t = PrepareImage(track_img); auto shower_img_t = PrepareImage(shower_img); - auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrack,true); - auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShower,true); + auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrackPixelChunk,true); + auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShowerPixelChunk,true); for(auto& shower_pchunk : shower_pchunk_v) { auto& shower_ctor = shower_pchunk.ctor; @@ -364,9 +364,9 @@ namespace larcv { auto track_img_t = PrepareImage(track_img); auto shower_img_t = PrepareImage(shower_img); - auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknown,true); - auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrack,true); - auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShower,true); + auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknownPixelChunk,true); + auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrackPixelChunk,true); + auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShowerPixelChunk,true); /// determine track/shower fraction of ADC contours for(auto& adc_pchunk : adc_pchunk_v) { @@ -462,9 +462,9 @@ namespace larcv { auto track_img_t = PrepareImage(track_img); auto shower_img_t = PrepareImage(shower_img); - auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknown); - auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrack); - auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShower); + auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknownPixelChunk); + auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrackPixelChunk); + auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShowerPixelChunk); std::vector cidx_v; @@ -533,9 +533,9 @@ namespace larcv { auto track_img_t = PrepareImage(track_img); auto shower_img_t = PrepareImage(shower_img); - auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknown,false); - auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrack,false); - auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShower,false); + auto adc_pchunk_v = MakePixelChunks(adc_img_t,larocv::ChunkType_t::kUnknownPixelChunk,false); + auto track_pchunk_v = MakePixelChunks(track_img_t,larocv::ChunkType_t::kTrackPixelChunk,false); + auto shower_pchunk_v = MakePixelChunks(shower_img_t,larocv::ChunkType_t::kShowerPixelChunk,false); std::vector cidx_v; diff --git a/ublarcvapp/LArOpenCVHandle/PreProcessor.h b/ublarcvapp/LArOpenCVHandle/PreProcessor.h index 074c6fd..6be281e 100644 --- a/ublarcvapp/LArOpenCVHandle/PreProcessor.h +++ b/ublarcvapp/LArOpenCVHandle/PreProcessor.h @@ -62,13 +62,19 @@ namespace larcv { void FilterContours(larocv::GEO2D_ContourArray_t& ctor_v); - + +#ifndef __CINT__ +#ifndef __CLING__ + // hide the enum std::vector MakePixelChunks(const cv::Mat& img, larocv::ChunkType_t type, bool calc_params=true, size_t min_ctor_size=0, size_t min_track_size=0); +#endif +#endif + bool EdgeConnected(const larocv::PixelChunk& track1, const larocv::PixelChunk& track2); diff --git a/ublarcvapp/LArOpenCVHandle/ROIAna.cxx b/ublarcvapp/LArOpenCVHandle/ROIAna.cxx index f546f54..f3eaae2 100644 --- a/ublarcvapp/LArOpenCVHandle/ROIAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/ROIAna.cxx @@ -5,8 +5,8 @@ #include #include -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/ROIAna.h b/ublarcvapp/LArOpenCVHandle/ROIAna.h index 358f4d2..8fa9910 100644 --- a/ublarcvapp/LArOpenCVHandle/ROIAna.h +++ b/ublarcvapp/LArOpenCVHandle/ROIAna.h @@ -18,7 +18,7 @@ #include "larcv/core/Processor/ProcessFactory.h" #include "larcv/core/DataFormat/EventImage2D.h" #include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.cxx b/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.cxx index 48d1a9b..2fce727 100644 --- a/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.cxx @@ -5,8 +5,8 @@ #include "LArbysImageMaker.h" #include "larcv/core/DataFormat/EventROI.h" #include "LArbysUtils.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "larcv/core/CVUtil/CVUtil.h" #include #include diff --git a/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.h b/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.h index 45b59c3..137bcff 100644 --- a/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.h +++ b/ublarcvapp/LArOpenCVHandle/SSNetTruthAna.h @@ -3,7 +3,7 @@ #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/VertexAna.cxx b/ublarcvapp/LArOpenCVHandle/VertexAna.cxx index 5830b89..4d9a28f 100644 --- a/ublarcvapp/LArOpenCVHandle/VertexAna.cxx +++ b/ublarcvapp/LArOpenCVHandle/VertexAna.cxx @@ -7,8 +7,8 @@ #include "larcv/core/DataFormat/EventPixel2D.h" #include "larcv/core/DataFormat/EventImage2D.h" #include "LArOpenCV/ImageCluster/Base/ImageClusterTypes.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "LArbysUtils.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/VertexAna.h b/ublarcvapp/LArOpenCVHandle/VertexAna.h index c1016c2..2f74864 100644 --- a/ublarcvapp/LArOpenCVHandle/VertexAna.h +++ b/ublarcvapp/LArOpenCVHandle/VertexAna.h @@ -3,7 +3,7 @@ #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" #include #include diff --git a/ublarcvapp/LArOpenCVHandle/VertexInROI.cxx b/ublarcvapp/LArOpenCVHandle/VertexInROI.cxx index f4b1563..ac1993d 100644 --- a/ublarcvapp/LArOpenCVHandle/VertexInROI.cxx +++ b/ublarcvapp/LArOpenCVHandle/VertexInROI.cxx @@ -3,8 +3,8 @@ #include "VertexInROI.h" #include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace larcv { diff --git a/ublarcvapp/LArOpenCVHandle/VertexInROI.h b/ublarcvapp/LArOpenCVHandle/VertexInROI.h index ed8d8a6..6ed4ac8 100644 --- a/ublarcvapp/LArOpenCVHandle/VertexInROI.h +++ b/ublarcvapp/LArOpenCVHandle/VertexInROI.h @@ -3,7 +3,7 @@ #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace larcv { diff --git a/ublarcvapp/LArliteHandler/CMakeLists.txt b/ublarcvapp/LArliteHandler/CMakeLists.txt index 158a992..59a8a1a 100644 --- a/ublarcvapp/LArliteHandler/CMakeLists.txt +++ b/ublarcvapp/LArliteHandler/CMakeLists.txt @@ -1,35 +1,47 @@ set(MODULE_NAME LArliteHandler) -# Collect the headers -file(GLOB HEADERS "*.h") +set(LIBNAME LArCVApp_${MODULE_NAME}) -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") +# Collect the headers +set( HEADERS + LArliteManager.h +) -# library name -set(LIBNAME LArCVApp_${MODULE_NAME}) +# Add sources to library +add_library( ${LIBNAME} SHARED + LArliteManager.cxx +) -# includes -include_directories(${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INC_DIRS} + ${LARCV_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries( ${LIBNAME} PUBLIC + larlite_Base + LArCVCoreBase + LArCVCoreDataFormat +) + +# includes for ROOT dictionary gen +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/LArliteHandler/LArliteManager.h b/ublarcvapp/LArliteHandler/LArliteManager.h index 44fd6e6..b4c9dcb 100644 --- a/ublarcvapp/LArliteHandler/LArliteManager.h +++ b/ublarcvapp/LArliteHandler/LArliteManager.h @@ -14,7 +14,7 @@ #include // larlite -#include "DataFormat/storage_manager.h" +#include "larlite/DataFormat/storage_manager.h" // larcv #include "larcv/core/Base/larcv_base.h" diff --git a/ublarcvapp/LLCVProcessor/CMakeLists.txt b/ublarcvapp/LLCVProcessor/CMakeLists.txt index 5f316a8..39413e7 100644 --- a/ublarcvapp/LLCVProcessor/CMakeLists.txt +++ b/ublarcvapp/LLCVProcessor/CMakeLists.txt @@ -20,7 +20,8 @@ set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") # DEFINE SOME VARS THAT MIGHT BE USEFUL FOR SUBMODULES # larlite includes -set(LARLITE_INCLUDES $ENV{LARLITE_BASEDIR} $ENV{LARLITE_USERDEVDIR} $ENV{LARLITE_COREDIR} $ENV{LARLITE_USERDEVDIR}/BasicTool ) +#set(LARLITE_INCLUDES $ENV{LARLITE_BASEDIR} $ENV{LARLITE_USERDEVDIR} $ENV{LARLITE_COREDIR} $ENV{LARLITE_USERDEVDIR}/BasicTool ) +message( STATUS ${LARLITE_INCLUDE_DIR} ) # larcv libs used set(LARCV_LIBS_USED LArCVCoreBase LArCVCoreDataFormat LArCVCoreCVUtil) @@ -38,17 +39,21 @@ target_include_directories(${LIBNAME} PUBLIC $ ${LARCV_INCLUDE_DIR} - ${LARLITE_INCLUDES} + ${LARLITE_INCLUDE_DIR} PRIVATE ${PROJECT_SOURCE_DIR} ) -target_link_libraries(${LIBNAME} ${LARLITE_LIBS_USED} ${LARCV_LIBS_USED} ${UBLARCVAPP_LIBS_USED}) -include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/ublarcvapp/${MODULE_NAME} ${LARCV_INCLUDE_DIR} ${LARLITE_INCLUDES} ) +target_link_libraries(${LIBNAME} + ${LARLITE_LIBS_USED} + ${LARCV_LIBS_USED} + ${UBLARCVAPP_LIBS_USED}) + +include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/ublarcvapp/${MODULE_NAME} ${LARCV_INCLUDE_DIR} ${LARLITE_INCLUDE_DIR} ) + ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) -#message("public headers: ${ALL_HEADERS}") +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/LLCVProcessor/LLCVProcessBase.h b/ublarcvapp/LLCVProcessor/LLCVProcessBase.h index 208a69d..37f5b20 100644 --- a/ublarcvapp/LLCVProcessor/LLCVProcessBase.h +++ b/ublarcvapp/LLCVProcessor/LLCVProcessBase.h @@ -6,7 +6,7 @@ #include "larcv/core/DataFormat/IOManager.h" // larlite -#include "DataFormat/storage_manager.h" +#include "larlite/DataFormat/storage_manager.h" namespace larcv { class ProcessDriver; diff --git a/ublarcvapp/LLCVProcessor/LLCVProcessDriver.h b/ublarcvapp/LLCVProcessor/LLCVProcessDriver.h index 9b3fdbf..f9958b0 100644 --- a/ublarcvapp/LLCVProcessor/LLCVProcessDriver.h +++ b/ublarcvapp/LLCVProcessor/LLCVProcessDriver.h @@ -11,7 +11,7 @@ #include "larcv/core/Processor/ProcessDriver.h" // larlite -#include "core/DataFormat/storage_manager.h" +#include "larlite/DataFormat/storage_manager.h" #include "ublarcvapp/LArliteHandler/LArliteManager.h" diff --git a/ublarcvapp/LLCVProcessor/LLCVProcessTest.h b/ublarcvapp/LLCVProcessor/LLCVProcessTest.h index 3774d49..484c62a 100644 --- a/ublarcvapp/LLCVProcessor/LLCVProcessTest.h +++ b/ublarcvapp/LLCVProcessor/LLCVProcessTest.h @@ -6,7 +6,7 @@ #include #include "larcv/core/Processor/ProcessFactory.h" #include "larcv/core/DataFormat/IOManager.h" -#include "DataFormat/storage_manager.h" +#include "larlite/DataFormat/storage_manager.h" namespace ublarcvapp { namespace llcv { diff --git a/ublarcvapp/LLCVProcessor/LinkDef.h b/ublarcvapp/LLCVProcessor/LinkDef.h index 56ec8bd..81d0aa3 100644 --- a/ublarcvapp/LLCVProcessor/LinkDef.h +++ b/ublarcvapp/LLCVProcessor/LinkDef.h @@ -9,9 +9,7 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ namespace ublarcvapp; -#pragma link C++ namespace ublarcvapp::dltagger; - +#pragma link C++ namespace ublarcvapp::llcv; #pragma link C++ class ublarcvapp::llcv::LLCVProcessDriver+; #pragma link C++ class ublarcvapp::llcv::LLCVProcessTest+; #pragma link C++ class ublarcvapp::llcv::LArCVProcessTest+; diff --git a/ublarcvapp/MCTools/CMakeLists.txt b/ublarcvapp/MCTools/CMakeLists.txt index ac173cd..aa6ab26 100644 --- a/ublarcvapp/MCTools/CMakeLists.txt +++ b/ublarcvapp/MCTools/CMakeLists.txt @@ -4,44 +4,80 @@ set(MODULE_NAME MCTools) set(LIBNAME LArCVApp_${MODULE_NAME}) # Collect the headers -set( HEADERS MCPixelPGraph.h +set( HEADERS MCPos2ImageUtils.h + MCPGNode.h + MCParticleGraph.h + MCPixelPGraph.h TruthTrackSCE.h TruthShowerTrunkSCE.h crossingPointsAnaMethods.h NeutrinoVertex.h LArbysMC.h - NeutrinoPixelFilter.h ) + NeutrinoPixelFilter.h + FlashMatcher.h + FlashMatcherV2.h + MCPixelLabels.h + EventMCPixelLabels.h + MCPixelLabelMaker.h + MCPixelPMap.h ) # Add sources -add_library( ${LIBNAME} SHARED MCPixelPGraph.cxx +add_library( ${LIBNAME} SHARED MCPos2ImageUtils.cxx + MCPGNode.cxx + MCParticleGraph.cxx + MCPixelPGraph.cxx TruthTrackSCE.cxx TruthShowerTrunkSCE.cxx crossingPointsAnaMethods.cxx NeutrinoVertex.cxx LArbysMC.cxx - NeutrinoPixelFilter.cxx ) + NeutrinoPixelFilter.cxx + FlashMatcher.cxx + FlashMatcherV2.cxx + MCPixelLabels.cxx + EventMCPixelLabels.cxx + MCPixelLabelMaker.cxx + MCPixelPMap.cxx ) # larlite libraries set(LARLITE_LIBS_USED ${LARLITE_LIBS} ) +set(UBLARCVAPP_LIBS_USED LArCVApp_ubdllee LArCVApp_UBWireTool ) -# includes for ROOT dictionary gen -include_directories(${LARCV_INCLUDE_DIR} ${LARCV_JSON_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/ublarcvapp/${MODULE_NAME}) +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} + ${LARCV_JSON_INCLUDE_DIR} + ${HIGHFIVE_INCLUDE_DIR} + ${HDF5_INCLUDE_DIRS} +) + +target_link_libraries( ${LIBNAME} PUBLIC + ${LARLITE_LIBS_USED} + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreProcessor + ${UBLARCVAPP_LIBS_USED} + ${HDF5_LIBRARIES} +) -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +# includes for ROOT dictionary gen +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) -# Generate the shared library from the sources -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${LARCV_JSON_INCLUDE_DIR} ) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor ${LARLITE_LIBS_USED}) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/MCTools/EventMCPixelLabels.cxx b/ublarcvapp/MCTools/EventMCPixelLabels.cxx new file mode 100644 index 0000000..d405b1e --- /dev/null +++ b/ublarcvapp/MCTools/EventMCPixelLabels.cxx @@ -0,0 +1,13 @@ +#include "EventMCPixelLabels.h" + +namespace ublarcvapp { +namespace mctools{ + + void EventMCPixelLabels::clear() + { + _triplets_v.clear(); + _imgcoord_to_tripindex.clear(); + } + +} +} \ No newline at end of file diff --git a/ublarcvapp/MCTools/EventMCPixelLabels.h b/ublarcvapp/MCTools/EventMCPixelLabels.h new file mode 100644 index 0000000..b26783f --- /dev/null +++ b/ublarcvapp/MCTools/EventMCPixelLabels.h @@ -0,0 +1,32 @@ +#ifndef __UBLARCVAPP_MCTOOLS_EVENT_MCPIXEL_LABELS_H__ +#define __UBLARCVAPP_MCTOOLS_EVENT_MCPIXEL_LABELS_H__ + +#include +#include +#include + +#include "MCPixelLabels.h" + +namespace ublarcvapp { +namespace mctools { + +class EventMCPixelLabels { + +public: + + EventMCPixelLabels(){}; + ~EventMCPixelLabels(){}; + + void clear(); + + std::vector _triplets_v; //< container of triplet info + std::map< std::array, unsigned long > _imgcoord_to_tripindex; //< (u,v,y,row) to position in _triplets_v + + //MCPixelLabels& get( int u, int v, int y, ) + +}; + +} +} + +#endif \ No newline at end of file diff --git a/ublarcvapp/MCTools/FlashMatcher.cxx b/ublarcvapp/MCTools/FlashMatcher.cxx new file mode 100644 index 0000000..623665b --- /dev/null +++ b/ublarcvapp/MCTools/FlashMatcher.cxx @@ -0,0 +1,374 @@ +#include "FlashMatcher.h" + +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/opflash.h" +#include "larlite/LArUtil/LArProperties.h" + +#include "crossingPointsAnaMethods.h" + +namespace ublarcvapp { +namespace mctools { + + + void FlashMatcher::initialize() + { + _fm_tree = new TTree("fmtree","Flashmatched Tree"); + bindAnaVariables(_fm_tree); + + std::cout << "FLASHMATCHER: Made tree and bound vars" << std::endl; + + //_voxelTree = new TTree("voxtree","Voxeldata Tree"); + //_voxelTree = mgr.CloneTree() + + } + + void FlashMatcher::bindAnaVariables( TTree* fm_tree ) + { + + // event indexing + fm_tree->Branch("flashmatcher_run", &_run, "flashmatcher_run/I"); + fm_tree->Branch("flashmatcher_subrun", &_subrun, "flashmatcher_subrun/I"); + fm_tree->Branch("flashmatcher_event", &_event, "flashmatcher_event/I"); + fm_tree->Branch("flashmatcher_ancestorid" , &_ancestorID, "flashmatcher_ancestorid/I"); + //fm_tree->Branch("flashmatcher_trackid" , &_trackID, "flashmatcher_trackid/I"); + fm_tree->Branch("flashmatcher_clustertick" , &_clusterTick, "flashmatcher_clustertick/D"); + fm_tree->Branch("flashmatcher_flashtick" , &_flashTick, "flashmatcher_flashtick/D"); + fm_tree->Branch("flashmatcher_origin" , &_origin, "flashmatcher_origin/I"); + + } + + bool FlashMatcher::process(larlite::storage_manager& mgr) + { + + Clear(); + + _run = mgr.run_id(); + _subrun = mgr.subrun_id(); + _event = mgr.event_id(); + _ancestorID = ancestorID; + //_trackID = trackID; + _clusterTick = clusterTick; + _flashTick = flashTick; + _origin = origin; + + if ( _fm_tree ) + _fm_tree->Fill(); + + return true; + + } + + void FlashMatcher::finalize() + { + if ( _fm_tree ) + _fm_tree->Write(); + } + + void FlashMatcher::Clear() + { + _run = 0; + _subrun = 0; + _event = 0; + _ancestorID = 0; + //_trackID = 0; + _clusterTick = 0; + _flashTick = 0; + _origin = 0; + + } + + int FlashMatcher::numTracks( larlite::storage_manager& ioll ) { + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + + int numTracks = ev_mctrack->size(); + + return numTracks; + } + +/* + int FlashMatcher::trackAncestorID( larlite::storage_manager& ioll, int i ) { + + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + + auto const& mctrack = ev_mctrack->at(i); + + auto ancestorID = mctrack.AncestorTrackID(); + + return ancestorID; + + } + */ + + int FlashMatcher::trackAncestorID() { + + if (!ancestorID) + return -9999; + + return ancestorID; + + } + + int FlashMatcher::getTrackID() { + + if (!trackID) + return -9999; + + return trackID; + + } + + int FlashMatcher::trackOrigin() { + + if (!origin) + return -9999; + + return origin; + + } + +/* + int FlashMatcher::trackOrigin( larlite::storage_manager& ioll, int i ) { + + + } + */ + + int FlashMatcher::numShowers( larlite::storage_manager& ioll ) { + + larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + int numShowers = ev_mcshower->size(); + + return numShowers; + + } + /* + * grab time coordinate from mctrack mcstep -> convert to tick + * + * @param[in] ioll The larlite storage manager that contains mctruth class + * @return tuple with tick, producer string, and cosmic flag + */ + double FlashMatcher::grabTickFromMCTrack( larlite::storage_manager& ioll, int i ) { + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + + ///std::cout << "Number of tracks in event: " << ev_mctrack->size() << std::endl; + + auto const& mctrack = ev_mctrack->at(i); + + trackID = mctrack.TrackID(); + ancestorID = mctrack.AncestorTrackID(); + + ///std::cout << "TrackID is: " << mctrack.TrackID() << std::endl; + ///std::cout << "Mother TrackID is: " << mctrack.MotherTrackID() << std::endl; + ///std::cout << "PDG is: " << mctrack.PdgCode() << std::endl; + + ///std::cout << "Origin: " << mctrack.Origin() << std::endl; + origin = mctrack.Origin(); + + if ( mctrack.Origin() == 1 ) { + producer = "simpleFlashBeam"; + isCosmic = 0; + } else { + producer = "simpleFlashCosmic"; + isCosmic = 1; + } + + ///std::cout << "mctrack.size() = " << mctrack.size() << std::endl; + + if ( mctrack.size() == 0 ) { + ///std::cout << "Empty vector of mcsteps (probably a cosmic that didn't cross the TPC)" << std::endl; + return -9999.998; + } + + //const larlite::mcstep& start = mctrack.Start(); + const larlite::mcstep& start = mctrack.at(0); + + ///std::cout << "First mcstep X positiom is " << start.X() << " while the track started at: " << mctrack.Start().X() << std::endl; + + larutil::SpaceChargeMicroBooNE* _sce = nullptr; + + const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + double xPos = start.X(); + double xPos2 = mctrack.Start().X(); + + double tick = CrossingPointsAnaMethods::getTick(start, 4050.0, _sce); + tick = tick - xPos / cm_per_tick; + + double tick2 = CrossingPointsAnaMethods::getTick(mctrack.Start(), 4050.0, _sce); + tick2 = tick2 - xPos2 / cm_per_tick; + + ///std::cout << "Tick for first mcstep in TPC: " << tick << " and tick for starting up in the sky: " << tick2 << std::endl; + + // check for primaries + if ( mctrack.TrackID() != mctrack.MotherTrackID() ) { + return -9999.997; + } + + ///std::cout << "Ancestor ID: " << mctrack.AncestorTrackID() << std::endl; + + return tick; + + } + + double FlashMatcher::grabTickFromMCShower( larlite::storage_manager& ioll, int i ) { + + larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + ///std::cout << "Number of showers in event: " << ev_mcshower->size() << std::endl; + + auto const& mcshower = ev_mcshower->at(i); + + ///std::cout << "TrackID is: " << mcshower.TrackID() << std::endl; + ///std::cout << "Mother TrackID is: " << mcshower.MotherTrackID() << std::endl; + ///std::cout << "PDG is: " << mcshower.PdgCode() << std::endl; + + ///std::cout << "Origin: " << mcshower.Origin() << std::endl; + origin = mcshower.Origin(); + + if ( mcshower.Origin() == 1 ) { + producer = "simpleFlashBeam"; + isCosmic = 0; + } else { + producer = "simpleFlashCosmic"; + isCosmic = 1; + } + + const larlite::mcstep& start = mcshower.Start(); + + larutil::SpaceChargeMicroBooNE* _sce = nullptr; + + const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + double xPos = start.X(); + + double tick = CrossingPointsAnaMethods::getTick(start, 4050.0, _sce); + tick = tick - xPos / cm_per_tick; + + trackID = mcshower.TrackID(); + ancestorID = mcshower.AncestorTrackID(); + + // check for primaries + if ( mcshower.TrackID() != mcshower.MotherTrackID() ) { + return -9999.997; + } + + ///std::cout << "Ancestor ID: " << mcshower.AncestorTrackID() << std::endl; + + return tick; + + + } + + /* + * grab time coordinate from opflash -> convert to tick + * + * @param[in] opio The larlite storage manager that contains opflash class + * @param[in] producer String labeling the producer, e.g. "simpleFlashBeam" + * @return Vector containing all opflash times for the track in ticks + */ + std::vector< std::pair > FlashMatcher::grabTickFromOpflash( larlite::storage_manager& opio ) { + + std::vector< std::pair > tickContainer; + + larlite::event_opflash* ev_opflash + = (larlite::event_opflash*)opio.get_data(larlite::data::kOpFlash, producer); + + int counter = 0; + + for (auto const& opflash : *ev_opflash) { + double time = opflash.Time(); + //std::cout << time << std::endl; + double tick = time/0.5 + 3200.0; + tickContainer.push_back(std::make_pair(tick,counter)); + counter++; + } + + /* + std::cout << "Before sort: " << std::endl; + for (int i = 0; i < tickContainer.size(); i++) { + std::cout << tickContainer[i].first << "\t" + << tickContainer[i].second << std::endl; + } + */ + //std::cout << "Tick container: " << tickContainer << std::endl; + + std::sort( tickContainer.begin(), tickContainer.end() ); + + /* + std::cout << "After sort: " << std::endl; + for (int i = 0; i < tickContainer.size(); i++) { + std::cout << tickContainer[i].first << "\t" + << tickContainer[i].second << std::endl; + } + */ + + //std::cout << "Tick container: " << tickContainer << std::endl; + + return tickContainer; + } + + /* + * matches mctrack tick to opflash tick + * for cosmics, will return -9999.999 if there is no opflash found within the threshold + * for beam tracks, will find the closest matching opflash + * + * @param[in] mctrackTick Time in ticks for the mctrack to be matched to + * @param[in] flashTicks Vector of potential opflash matches in ticks + * @param[in] isCosmic Cosmic flag to determine threshold + * + * @return Value of the closest matching opflash tick + */ +std::pair FlashMatcher::matchTicks( double mctrackTick, std::vector< std::pair > flashTicks ) { + + double threshold; + if (isCosmic == 1) { + threshold = 2.0; // 1 tick = 0.5 us + } else { + threshold = std::numeric_limits::infinity(); + } + + if (flashTicks.empty() == 1) { + clusterTick = mctrackTick; + flashTick = 9999.999; + return std::make_pair(9999.999,9999); + } + + auto match = std::lower_bound( flashTicks.begin(), flashTicks.end(), std::make_pair( mctrackTick, std::numeric_limits::min()) ); + double b = (*(match)).first; + + //std::cout << "match [iterator] is: " << match << std::endl; + //std::cout << "*match [dereferenced iterator, shoudl be a pair] is: " << (*(match)) << std::endl; + ///std::cout << "(*(match)).first [actual value of the tick] is: " << b << std::endl; + + if (match == flashTicks.begin() && fabs(b - mctrackTick) <= threshold) { + clusterTick = mctrackTick; + flashTick = flashTicks[0].first; + return flashTicks[0]; + } + + double a = (*(match - 1)).first; + + if (fabs(mctrackTick - a) < fabs(mctrackTick - b) && fabs(mctrackTick - a) <= threshold) { + clusterTick = mctrackTick; + flashTick = flashTicks [ match - flashTicks.begin() - 1 ].first; + return flashTicks [ match - flashTicks.begin() - 1 ]; + } + + if ( fabs(mctrackTick - b) <= threshold ) { + clusterTick = mctrackTick; + flashTick = flashTicks[ match - flashTicks.begin() ].first; + return flashTicks[ match - flashTicks.begin() ]; + } + + return std::make_pair(-9999.999,9999); + + } + + +} +} diff --git a/ublarcvapp/MCTools/FlashMatcher.h b/ublarcvapp/MCTools/FlashMatcher.h new file mode 100644 index 0000000..26f4a0b --- /dev/null +++ b/ublarcvapp/MCTools/FlashMatcher.h @@ -0,0 +1,88 @@ +#ifndef __FLASH_MATCHER_H__ +#define __FLASH_MATCHER_H__ + +/* + * class for flash matching with mc truth + */ + +// larlite/core +#include "larlite/DataFormat/storage_manager.h" + +#include "TTree.h" +#include + +namespace ublarcvapp { +namespace mctools { + + class FlashMatcher { + public: + + FlashMatcher() + : isCosmic(0), + _fm_tree(nullptr) + { + Clear(); + }; + + virtual ~FlashMatcher() { + if (_fm_tree) { + delete _fm_tree; + _fm_tree = nullptr; + } + }; + + void initialize(); + void bindAnaVariables( TTree* ); + bool process(larlite::storage_manager& mgr); + void finalize(); + void Clear(); + + int numTracks( larlite::storage_manager& ioll ); + //int trackAncestorID( larlite::storage_manager& ioll, int i ); + + // Getters for meta information + int trackAncestorID(); + int getTrackID(); + int trackOrigin(); + + int numShowers( larlite::storage_manager& ioll ); + double grabTickFromMCTrack( larlite::storage_manager& ioll, int i ); + double grabTickFromMCShower( larlite::storage_manager& ioll, int i ); + std::vector< std::pair > grabTickFromOpflash( larlite::storage_manager& opio ); + //std::vector, std::vector> grabTickFromOpflash( larlite::storage_manager& opio ); + std::pair matchTicks( double mctrack_tick, std::vector< std::pair > flash_ticks ); + + Bool_t isCosmic; + std::string producer; + + protected: + + TTree* _fm_tree; + //TTree* _voxelTree; + + public: + + // Variables in TTree + int _run; + int _subrun; + int _event; + int _ancestorID; + //int _trackID; + double _clusterTick; + double _flashTick; + int _origin; + + // Vars not in tree but in loop + int ancestorID; + int trackID; + double clusterTick; + double flashTick; + int origin; + + + }; + +} +} + +#endif diff --git a/ublarcvapp/MCTools/FlashMatcherV2.cxx b/ublarcvapp/MCTools/FlashMatcherV2.cxx new file mode 100644 index 0000000..4ff6523 --- /dev/null +++ b/ublarcvapp/MCTools/FlashMatcherV2.cxx @@ -0,0 +1,567 @@ +#include "FlashMatcherV2.h" + +#include + +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/opflash.h" +#include "larlite/LArUtil/LArProperties.h" + +// ublarcvapp/mctools +#include "crossingPointsAnaMethods.h" +#include "MCPos2ImageUtils.h" + +namespace ublarcvapp { +namespace mctools { + + + + bool FlashMatcherV2::process(larlite::storage_manager& mgr) + { + + // first get list of times + buildRecoFlashPool( mgr ); + + // match truth tracks to flashes + matchTracksAndFlashes( mgr ); + + // filter matches + filterMatches(); + + std::sort( recoflash_v.begin(), recoflash_v.end() ); + + // list results + if ( _verbose_level>=1 ) { + printMatches(); + printFiltered(); + } + + + return true; + + } + + + int FlashMatcherV2::numTracks( larlite::storage_manager& ioll ) { + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + + int numTracks = ev_mctrack->size(); + + return numTracks; + } + + int FlashMatcherV2::numShowers( larlite::storage_manager& ioll ) { + + larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + int numShowers = ev_mcshower->size(); + + return numShowers; + + } + + void FlashMatcherV2::clear() + { + mcpg.clear(); + matched_ancestor_ids.clear(); + recoflash_v.clear(); + filtered_v.clear(); + } + + // /* + // * grab time coordinate from mctrack mcstep -> convert to tick + // * + // * @param[in] ioll The larlite storage manager that contains mctruth class + // * @return tuple with tick, producer string, and cosmic flag + // */ + // double FlashMatcherV2::grabTickFromMCTrack( larlite::storage_manager& ioll, int i ) { + // larlite::event_mctrack* ev_mctrack + // = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + + // std::cout << "Number of tracks in event: " << ev_mctrack->size() << std::endl; + + // auto const& mctrack = ev_mctrack->at(i); + + // std::cout << "TrackID is: " << mctrack.TrackID() << std::endl; + // std::cout << "Mother TrackID is: " << mctrack.MotherTrackID() << std::endl; + // std::cout << "PDG is: " << mctrack.PdgCode() << std::endl; + + // std::cout << "Origin: " << mctrack.Origin() << std::endl; + // origin = mctrack.Origin(); + + // if ( mctrack.Origin() == 1 ) { + // producer = "simpleFlashBeam"; + // isCosmic = 0; + // } else { + // producer = "simpleFlashCosmic"; + // isCosmic = 1; + // } + + // const larlite::mcstep& start = mctrack.Start(); + + // larutil::SpaceChargeMicroBooNE* _sce = nullptr; + + // const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + // double xPos = start.X(); + + // double tick = CrossingPointsAnaMethods::getTick(start, 4050.0, _sce); + // tick = tick - xPos / cm_per_tick; + + // // check for primaries + // if ( mctrack.TrackID() != mctrack.MotherTrackID() ) { + // return -999.997; + // } + + // std::cout << "Ancestor ID: " << mctrack.AncestorTrackID() << std::endl; + + // trackID = mctrack.TrackID(); + // ancestorID = mctrack.AncestorTrackID(); + + // return tick; + + // } + + // double FlashMatcherV2::grabTickFromMCShower( larlite::storage_manager& ioll, int i ) { + + // larlite::event_mcshower* ev_mcshower + // = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + // std::cout << "Number of showers in event: " << ev_mcshower->size() << std::endl; + + // auto const& mcshower = ev_mcshower->at(i); + + // std::cout << "TrackID is: " << mcshower.TrackID() << std::endl; + // std::cout << "Mother TrackID is: " << mcshower.MotherTrackID() << std::endl; + // std::cout << "PDG is: " << mcshower.PdgCode() << std::endl; + + // std::cout << "Origin: " << mcshower.Origin() << std::endl; + // origin = mcshower.Origin(); + + // if ( mcshower.Origin() == 1 ) { + // producer = "simpleFlashBeam"; + // isCosmic = 0; + // } else { + // producer = "simpleFlashCosmic"; + // isCosmic = 1; + // } + + // const larlite::mcstep& start = mcshower.Start(); + + // larutil::SpaceChargeMicroBooNE* _sce = nullptr; + + // const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + // double xPos = start.X(); + + // double tick = CrossingPointsAnaMethods::getTick(start, 4050.0, _sce); + // tick = tick - xPos / cm_per_tick; + + // // check for primaries + // if ( mcshower.TrackID() != mcshower.MotherTrackID() ) { + // return -999.997; + // } + + // std::cout << "Ancestor ID: " << mcshower.AncestorTrackID() << std::endl; + + // return tick; + + + // } + + + /** + * @brief inspect reco flash containers and store RecoFlash_t objects that store flash times + * + * When this is run, the member container \ref FlashMatcherV2.recoflash_v will be populated. + * This is a vector of RecoFlash_t objects that stores a list of trackIDs to each associated flash. + * + * + * + * params + * ------ + * @param[in] ioll larlite::storage_manager where an entry has already been loaded. + * + */ + void FlashMatcherV2::buildRecoFlashPool( larlite::storage_manager& ioll ) { + + // clear flash pools + recoflash_v.clear(); + filtered_v.clear(); + + std::vector producer_v = {"simpleFlashBeam","simpleFlashCosmic"} ; + + for (int iproducer=0; iproducer<(int)producer_v.size(); iproducer++) { + std::string producer = producer_v.at(iproducer); + + larlite::event_opflash* ev_opreco + = (larlite::event_opflash*)ioll.get_data(larlite::data::kOpFlash, producer ); + if ( _verbose_level>=2 ) + std::cout << "Reco flashes in producer[" << producer << "]: " << ev_opreco->size() << std::endl; + + for (int iflash=0; iflash<(int)ev_opreco->size(); iflash++) { + + auto const& dataflash = ev_opreco->at(iflash); + + RecoFlash_t flash; + flash.producerid = iproducer; + flash.index = iflash; + flash.used = 0; + flash.time_us = dataflash.Time(); // time relative to optical clock t=0, which is at TPC trigger (tick=3200) + flash.tick = dataflash.Time()/0.5 + 3200; // assuming optical time origin set at TPC clock tick 3200 + recoflash_v.emplace_back( std::move(flash) ); + }; + } + + // sort all the optical flashes by time + std::sort( recoflash_v.begin(), recoflash_v.end() ); + if ( _verbose_level>=1 ) + std::cout << "Number of reco flashes in the (truth) matching pool: " << recoflash_v.size() << std::endl; + + } + + /** + * @brief match flashes and to MC tracks + * + * + */ + void FlashMatcherV2::matchTracksAndFlashes( larlite::storage_manager& ioll ) + { + + // use the mc particle graph tool + mcpg.clear(); + mcpg.buildgraphonly( ioll ); + + associateTruthTrackIDs2recoFlashes( ioll, mcpg ); + buildNullFlashesToTrackIDs( ioll, mcpg ); + + // tag flashes with matched truth mc track trajectories that + // cross the image boundary + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + tagTracksThatCrossImageBoundary( mcpg, *ev_mctrack ); + + } + + /** + * @brief associate trackIDs from the mctrack and mcshower containers to the reco flashes + * + */ + void FlashMatcherV2::associateTruthTrackIDs2recoFlashes( larlite::storage_manager& ioll, + ublarcvapp::mctools::MCPixelPGraph& mcpg ) + { + + if ( _verbose_level>=2 ) + mcpg.printGraph(nullptr,false); + + // get the primary list + auto node_v = mcpg.getPrimaryParticles(); + + int nwarnings = 0; + int warning_limit = 100; + + // loop over the primary nodes: these are the recorded list of primary particles + // defined as trackid=ancestorid + for ( auto const& node : node_v ) { + if ( _verbose_level>=2 ) + std::cout << "primary node: trackid=" << node->tid << " ancestorid=" << node->aid << " origin=" << node->origin << std::endl; + + std::vector txyz = { node->start[3] , node->start[0], node->start[1], node->start[2] }; + + float tpctick_nodrift = CrossingPointsAnaMethods::getTrueTick( txyz, 4050.0, NULL ); + if ( _verbose_level>=2 ) + std::cout << " tpctick_nodrift=" << tpctick_nodrift << std::endl; + + int closest_flashidx = -1; + float min_dtick = 1.0e9; + for ( int idx=0; idx<(int)recoflash_v.size(); idx++ ) { + float dtick = fabs(recoflash_v[idx].tick-tpctick_nodrift); + if (dtick=2 ) + std::cout << " closest flashidx=" << closest_flashidx << " dtick=" << min_dtick << std::endl; + + if ( min_dtick < _dtick_threshold && closest_flashidx>=0 ) { + // we've matched this track to a flash + + // get the reco flash that best matched to the primary + auto& flash = recoflash_v[closest_flashidx]; + + bool added_primary = false; + + // assign this track ID to the flash + if ( node->origin==2 ) { + // cosmic origin track + if ( flash.ancestorid<0 ) { + flash.ancestorid = node->aid; + flash.trackid_v.insert( node->tid ); + added_primary = true; + } + else if (flash.ancestorid>=0 && flash.ancestorid!=node->aid) { + std::cout << " WARNING: flash already matched to node with another ancestorid! old=" << flash.ancestorid << std::endl; + nwarnings++; + + if ( nwarnings>warning_limit ) { + std::stringstream msg; + msg << "[FlashMatcherV2::associateTruthTrackIDs2recoFlashes] too many missing ID warnings. Stopping" << std::endl; + throw std::runtime_error(msg.str()); + } + } + matched_ancestor_ids.insert( node->aid ); + } + else if (node->origin==1 ) { + // neutrino origin track, set the flash as neutrino origin + flash.ancestorid = 0; + matched_ancestor_ids.insert( node->tid ); + flash.trackid_v.insert( node->tid ); + added_primary = true; + } + + if ( added_primary ) { + // get all the descendent particle records matched to this primary + auto node_et_daughters = mcpg.getNodeAndDescendentsFromTrackID( node->tid ); + int nadded = 0; + for ( auto& dnode : node_et_daughters ) { + flash.trackid_v.insert( dnode->tid ); + nadded++; + } + if ( _verbose_level>=2 ) + std::cout << " inserted " << nadded << " trackids to flash. Flash len(track_id)=" << flash.trackid_v.size() << std::endl; + } + + }//end of if tick difference is below max threshold + + }//end of loop over vector of primary node pointers + + return; + } + + /** + * @brief find trackIDs that did not have a reco flash + * + */ + void FlashMatcherV2::buildNullFlashesToTrackIDs( larlite::storage_manager& ioll, + ublarcvapp::mctools::MCPixelPGraph& mcpg ) + { + + // we need a list of ancestor ids where we've already matched + for ( auto const& recoflash : recoflash_v ) { + if ( recoflash.ancestorid>0 ) { + matched_ancestor_ids.insert( recoflash.ancestorid ); + } + else if ( recoflash.ancestorid==0 ) { + // for neutrino cluster, the trackids are their own ancestorids + for (auto const& trackid : recoflash.trackid_v ) { + matched_ancestor_ids.insert( trackid ); + } + } + } + + // container for null flashes + std::vector< RecoFlash_t > null_v; + + // now look for ancestor ids not in the above list + auto node_v = mcpg.getPrimaryParticles(); + for ( auto const& node : node_v ) { + + auto it_aid = matched_ancestor_ids.find( node->aid ); + if ( it_aid!=matched_ancestor_ids.end() ) + continue; // we've already matched this ancestor id + + // not in the list, make a null flash and associate + std::vector txyz = { node->start[3] , node->start[0], node->start[1], node->start[2] }; + float tpctick_nodrift = CrossingPointsAnaMethods::getTrueTick( txyz, 4050.0, NULL ); + + if (std::isinf(tpctick_nodrift)) + continue; + + RecoFlash_t nullflash; + nullflash.producerid = -1; + nullflash.index = int(null_v.size()); + nullflash.used = 0; + nullflash.ancestorid = node->aid; + nullflash.tick = tpctick_nodrift; + nullflash.time_us = (tpctick_nodrift-3200.0)*(0.5); // (tick diff from trigger tick)*(0.5 usec per tick) + nullflash.trackid_v.insert( node->tid ); + + // put in daughters + auto node_et_daughters = mcpg.getNodeAndDescendentsFromTrackID( node->tid ); + int nadded = 0; + for ( auto& dnode : node_et_daughters ) { + nullflash.trackid_v.insert( dnode->tid ); + nadded++; + } + + if ( _verbose_level>=2 ) { + std::cout << " created null flash for ancestorid=" << node->aid << std::endl; + std::cout << " inserted " << nadded << " trackids to flash. Flash len(track_id)=" << nullflash.trackid_v.size() << std::endl; + } + + null_v.emplace_back( std::move(nullflash) ); + } + + if ( _verbose_level>=1 ) { + std::cout << "created " << null_v.size() << " null flashes" << std::endl; + } + + for (int ii=0; ii<(int)null_v.size(); ii++) { + recoflash_v.push_back( null_v.at(ii) ); + } + + } + + std::string FlashMatcherV2::strRecoMatchInfo( const RecoFlash_t& flash, int iflash ) const + { + std::stringstream flashinfo; + if ( iflash>=0 ) + flashinfo << "flash[" << iflash << "] index=" << flash.index; + else + flashinfo << "flash[index=" << flash.index << "] "; + + flashinfo << " producer[" << flash.producerid << "]" + << " time_us=" << flash.time_us + << " tick=" << flash.tick + << " aid=" << flash.ancestorid + << " matched={ "; + for ( auto const& tid : flash.trackid_v ) { + flashinfo << tid << " "; + } + flashinfo << "}"; + return flashinfo.str(); + } + + void FlashMatcherV2::printRecoMatchInfo( const RecoFlash_t& flash, int iflash ) const + { + std::cout << strRecoMatchInfo( flash, iflash ) << std::endl; + } + + void FlashMatcherV2::printMatches() { + + std::cout << "=================================" << std::endl; + std::cout << " FlashMatcherV2::printMatches" << std::endl; + + for (int iflash=0; iflash<(int)recoflash_v.size(); iflash++) { + auto const& flash = recoflash_v.at(iflash); + std::cout << " " << strRecoMatchInfo(flash, iflash) << std::endl; + } + std::cout << "==================================" << std::endl; + } + + void FlashMatcherV2::printFiltered() { + + std::cout << "=================================" << std::endl; + std::cout << " FlashMatcherV2::printFiltered" << std::endl; + + for (int iflash=0; iflash<(int)filtered_v.size(); iflash++) { + auto const& flash = filtered_v.at(iflash); + std::cout << strRecoMatchInfo( flash, iflash ) << std::endl; + } + std::cout << "==================================" << std::endl; + } + + /** + * @brief look for tracks with incomplete charge + * + * this is usually due to tracks crossing the image boundary, so + * reconstruction spacepoints in the TPC are missing. + * + */ + void FlashMatcherV2::tagTracksThatCrossImageBoundary( ublarcvapp::mctools::MCPixelPGraph& mcpg, + const std::vector< larlite::mctrack >& track_v ) + { + + for (auto& recoflash : recoflash_v ) { + // for each flash, we loop over the mc truth of the matched particle + // tracks. for track-objects, we check if they cross the image boundary + // and look for missing charge. + if (_verbose_level>=2) { + std::cout << "[check flash] aid=" << recoflash.ancestorid << " tick=" << recoflash.tick << " time_us=" << recoflash.time_us << std::endl; + } + for (auto& trackid : recoflash.trackid_v ) { + + // we get info about this track already parsed by the pgraph class + auto pnode = mcpg.findTrackID(trackid); // returns pointer to MCPixelPGraph::Node_t struct + if ( pnode->type==0 ) { + // is a track + + // used the stored vector index to get larlite::mctrack object + auto const& mctrackinfo = track_v.at( pnode->vidx ); + + // convert track trajectory to list of points + std::vector< std::vector > reco_traj + = MCPos2ImageUtils::Get()->getRecoSpacepoints( mctrackinfo ); + + for (size_t i=0; i=8448.0 ) { + recoflash.within_image_bounds = false; + if ( _verbose_level>=1 ) + std::cout << " [ out of image bounds ]" << std::endl; + if ( _verbose_level>=2 ) { + std::cout << " [step " << i << "] reco=(" + << reco_step[0] << "," + << reco_step[1] << "," + << reco_step[2] << "," + << " tick=" << reco_step[3] << ") " + << " true=(" + << truth_step.X() << "," + << truth_step.Y() << "," + << truth_step.Z() << "," + << " t=" << truth_step.T()*1.0e-3 << " us)" + << std::endl; + } + break; + } + }// end of loop over reco traj points + + }// if node is track-type + + }//end of loop over track ids in reco flash + + }//end of loop over reco flashes + + return; + } + + /** + * @brief filter out matches + * + * moves filtered flash-track matches out of recoflash_v + * and into filtered_v member container. + */ + void FlashMatcherV2::filterMatches() { + + filtered_v.clear(); + + std::vector passes_v; + int nbefore_filter = recoflash_v.size(); + + for ( auto& recoflash : recoflash_v ) { + // apply cuts (truth-only based) + if ( recoflash.within_image_bounds ) { + passes_v.emplace_back( std::move(recoflash) ); + } + else { + filtered_v.emplace_back( std::move(recoflash) ); + } + } + + std::swap( recoflash_v, passes_v ); + + + std::cout << "[FlashMatcherV2::filterMatches] " + << " before filter=" << nbefore_filter + << " num passing=" << recoflash_v.size() + << " num filtered=" << filtered_v.size() + << std::endl; + + } + + +} +} diff --git a/ublarcvapp/MCTools/FlashMatcherV2.h b/ublarcvapp/MCTools/FlashMatcherV2.h new file mode 100644 index 0000000..c30da31 --- /dev/null +++ b/ublarcvapp/MCTools/FlashMatcherV2.h @@ -0,0 +1,134 @@ +#ifndef __FLASH_MATCHER_V2_H__ +#define __FLASH_MATCHER_V2_H__ + +#include +#include +#include "TTree.h" + +// larlite/core +#include "larlite/DataFormat/storage_manager.h" + +// ublarcvapp/mctools +#include "ublarcvapp/MCTools/MCPixelPGraph.h" + +namespace ublarcvapp { +namespace mctools { + + /** + * @class RecoFlash_t + * + * class for storing mc tracks associated to a flash + * + * can represent null flashes as well for charge depositing tracks + * that produce zero light in the PMTs. in this case the producerid + * will be 'null'. + * + */ + class RecoFlash_t { + public: + + // default constructor + RecoFlash_t() + : producerid(-1), + index(-1), + used(0), + ancestorid(-1), + time_us(0.0), + tick(0.0), + within_image_bounds(true) + { + trackid_v.clear(); + }; + ~RecoFlash_t() {}; + + int producerid; + int index; + int used; + int ancestorid; ///< truth-matched ancestor ID of the particle cascade that made the flash + float time_us; ///< relative to optical system trigger + float tick; ///< TPC clock tick + bool within_image_bounds; + + float operator<( const RecoFlash_t& rhs ) const { + if ( time_us < rhs.time_us ) return true; + return false; + }; + + std::set trackid_v; // geant track ids + + std::vector trackid_list() const { + std::vector out_v; + for ( auto const& tid : trackid_v ) { + out_v.push_back(tid); + } + return out_v; + }; + + }; + + + /** + * @class FlashMatcherV2 + * + * class for matching reco optical flashes to true track information. + * + * + */ + class FlashMatcherV2 { + public: + + FlashMatcherV2() + : _dtick_threshold(1.0), + _verbose_level(0) + { + recoflash_v.clear(); + }; + virtual ~FlashMatcherV2() {}; + + bool process(larlite::storage_manager& mgr); + void printMatches(); + void printFiltered(); + std::string strRecoMatchInfo( const RecoFlash_t& flash, int iflash=-1 ) const; + void printRecoMatchInfo( const RecoFlash_t& flash, int iflash=-1 ) const; + + int numTracks( larlite::storage_manager& ioll ); + int numShowers( larlite::storage_manager& ioll ); + + void buildRecoFlashPool( larlite::storage_manager& ioll ); + void matchTracksAndFlashes( larlite::storage_manager& ioll ); + void associateTruthTrackIDs2recoFlashes( larlite::storage_manager& ioll, + ublarcvapp::mctools::MCPixelPGraph& mgpg ); + void buildNullFlashesToTrackIDs( larlite::storage_manager& ioll, + ublarcvapp::mctools::MCPixelPGraph& mgpg ); + + void setVerboseLevel( int level ) { _verbose_level=level; }; + + void tagTracksThatCrossImageBoundary( ublarcvapp::mctools::MCPixelPGraph& mcpg, + const std::vector< larlite::mctrack >& track_v ); + + void filterMatches(); + + virtual void clear(); + + protected: + + float _dtick_threshold; ///< absolute time difference between flash and mc track object to match + int _verbose_level; ///< control output to stdout. [0] quiet (default), [1] info, [2] debug + + public: + + MCPixelPGraph mcpg; + + // track ancestor ids + std::set< int > matched_ancestor_ids; + + // container with matches + std::vector recoflash_v; + std::vector filtered_v; + + }; + +} +} + +#endif diff --git a/ublarcvapp/MCTools/LArbysMC.cxx b/ublarcvapp/MCTools/LArbysMC.cxx index d669fb2..41b1bbf 100644 --- a/ublarcvapp/MCTools/LArbysMC.cxx +++ b/ublarcvapp/MCTools/LArbysMC.cxx @@ -5,14 +5,14 @@ #include "LArbysMC.h" -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" -#include "Base/MCConstants.h" -#include "DataFormat/storage_manager.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/mcshower.h" -#include "DataFormat/mctruth.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/Base/MCConstants.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctruth.h" #include "ublarcvapp/ubdllee/dwall.h" @@ -31,7 +31,7 @@ namespace mctools { _producer_wireimage = "wire"; _psce = new larutil::SpaceChargeMicroBooNE(); for (int p=0; p<3; p++) _plane_vtx_pixsum[p] = 0.; - + Clear(); } @@ -137,6 +137,14 @@ namespace mctools { _event = mgr.event_id(); _entry = (int)mgr.get_index(); + // no truth? + _neutrino_present = false; + if ( ev_mctruth->size()==0 ) { + if ( _mc_tree ) + _mc_tree->Fill(); + return false; + } + // If we've got a neutrino, sample that auto const& mct = ev_mctruth->at(0); _neutrino_present = mct.Origin()==larlite::simb::kBeamNeutrino; @@ -389,7 +397,7 @@ namespace mctools { struct plane_pixsum_t { int plane; float pixsum; - bool operator<(const plane_pixsum_t& rhs ) { + bool operator<(const plane_pixsum_t& rhs ) const { if ( pixsum+; +//#pragma link C++ class std::vector >+; + +#pragma link C++ class ublarcvapp::mctools::MCPGNode+; +#pragma link C++ class ublarcvapp::mctools::MCParticleGraph+; + +#pragma link C++ class ublarcvapp::mctools::MCPixelLabels+; +#pragma link C++ class ublarcvapp::mctools::MCPos2ImageUtils+; #pragma link C++ class ublarcvapp::mctools::MCPixelPGraph+; +#pragma link C++ class ublarcvapp::mctools::MCPixelPMap+; #pragma link C++ class ublarcvapp::mctools::CrossingPointsAnaMethods+; #pragma link C++ class ublarcvapp::mctools::NeutrinoVertex+; #pragma link C++ class ublarcvapp::mctools::LArbysMC+; #pragma link C++ class ublarcvapp::mctools::NeutrinoPixelFilter+; #pragma link C++ class ublarcvapp::mctools::TruthTrackSCE+; #pragma link C++ class ublarcvapp::mctools::TruthShowerTrunkSCE+; +#pragma link C++ class ublarcvapp::mctools::RecoFlash_t+; +#pragma link C++ class ublarcvapp::mctools::FlashMatcher+; +#pragma link C++ class ublarcvapp::mctools::FlashMatcherV2+; + #endif diff --git a/ublarcvapp/MCTools/MCPGNode.cxx b/ublarcvapp/MCTools/MCPGNode.cxx new file mode 100644 index 0000000..b4d6732 --- /dev/null +++ b/ublarcvapp/MCTools/MCPGNode.cxx @@ -0,0 +1 @@ +#include "MCPGNode.h" diff --git a/ublarcvapp/MCTools/MCPGNode.h b/ublarcvapp/MCTools/MCPGNode.h new file mode 100644 index 0000000..e253c13 --- /dev/null +++ b/ublarcvapp/MCTools/MCPGNode.h @@ -0,0 +1,119 @@ +#ifndef __UBLARCVAPP_MCTOOLS_MCPGNODE_H__ +#define __UBLARCVAPP_MCTOOLS_MCPGNODE_H__ + +#include +#include + +namespace ublarcvapp { +namespace mctools { + + class MCPGNode { + + public: + + int nodeidx; // book-keeping index + int type; // track=0, shower=1, nu-vertex=2, genie_fs=3 + int vidx; // position in mcshower or mctrack vector + int tid; // geant4 track-ID + int aid; // ancestor geant4 trackid + int mtid; // mother geant4 trackid + int pid; // particle ID + MCPGNode* mother; // pointer to Mother Node + int mid; // mother nodeidx + float E_MeV; // energy + std::string process; // creating process + std::string mother_process; // mother process + std::string ancestor_process; // ancestor process + std::vector daughter_v; // pointer to daughters + std::vector daughter_idx_v; // daughter node indices in node_v + std::vector start; //< (x,y,z,t) before sce, true start of particle + std::vector first_edep_pos; //< (x,y,z,t) before sce, first step that leaves edep in cryostat + std::vector first_tpc_pos; //< (x,y,z,t) before sce, first step inside the TPC, visible in the image + std::vector mom4; //< (E,px,py,pz) + + int origin; // 1=neutrino, 2=cosmic, 0=unassigned, -1=unassigned + + MCPGNode() + : nodeidx(-1), + type(-1), + vidx(-1), + tid(-1), + aid(-1), + mtid(-1), + pid(-1), + mother(nullptr), + mid(-1), + E_MeV(-1.0), + process("null"), + start({0,0,0,0}), + first_edep_pos({0,0,0,0}), + first_tpc_pos({0,0,0,0}), + mom4({0,0,0,0}), + origin(-1) + { + daughter_v.clear(); + daughter_idx_v.clear(); + }; + + MCPGNode(int _nodeidx, int _type, int _tid, int _vidx, + int _pid, + MCPGNode* _mother=nullptr, + int _mid=-1, + float _energy=-1.0, + std::string proc="null") + : nodeidx(_nodeidx), + type(_type), + vidx(_vidx), + tid(_tid), + aid(-1), + mtid(-1), + pid(_pid), + mother(_mother), + mid(_mid), + E_MeV(_energy), + process(proc), + start({0,0,0,0}), + first_edep_pos({0,0,0,0}), + first_tpc_pos({0,0,0,0}), + mom4({0,0,0,0}), + origin(-1) + { + daughter_v.clear(); + daughter_idx_v.clear(); + }; + + bool operator<( const MCPGNode& rhs ) const { + if ( tid < rhs.tid ) return true; + return false; + }; + + bool isTrackObject() const { + if ( type==0 ) + return true; + return false; + }; + + bool isShowerObject() const { + if (type==1) + return true; + return false; + }; + + bool isNuVertexObject() const { + if (type==2) + return true; + return false; + }; + + bool isGenieFinalStateObject() const { + if (type==3) + return true; + return false; + }; + + }; + +} +} + +#endif \ No newline at end of file diff --git a/ublarcvapp/MCTools/MCParticleGraph.cxx b/ublarcvapp/MCTools/MCParticleGraph.cxx new file mode 100644 index 0000000..0fdea65 --- /dev/null +++ b/ublarcvapp/MCTools/MCParticleGraph.cxx @@ -0,0 +1,1181 @@ +#include "MCParticleGraph.h" + +#include +#include + +// larlite +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctruth.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" + +// ublarcvapp +#include "ublarcvapp/dbscan/DBScan.h" + +#include "crossingPointsAnaMethods.h" +#include "MCPos2ImageUtils.h" + +namespace ublarcvapp { +namespace mctools { + + /** + * @brief clear all data containers + */ + void MCParticleGraph::clear() + { + node_v.clear(); + _eventRootNode = nullptr; + _shower_daughter2mother.clear(); + _tid_to_node_v.clear(); + } + + /** + * @brief build only the particle graph (no pixel scan) + * + */ + void MCParticleGraph::buildgraph( larlite::storage_manager& ioll ) + { + + auto ev_mctrack = (larlite::event_mctrack*) ioll.get_data( larlite::data::kMCTrack, "mcreco" ); + auto ev_mcshower = (larlite::event_mcshower*)ioll.get_data( larlite::data::kMCShower, "mcreco" ); + auto ev_mctruth = (larlite::event_mctruth*) ioll.get_data( larlite::data::kMCTruth, "generator" ); + + buildgraph( *ev_mcshower, *ev_mctrack, *ev_mctruth ); + + } + + /** + * @brief build the particle graph (no pixel assignments) + * + */ + void MCParticleGraph::buildgraph( const larlite::event_mcshower& shower_v, + const larlite::event_mctrack& track_v, + const larlite::event_mctruth& mctruth_v ) { + + // how do we build this graph? + // we want to order N + + // (0) create root node + // (1) loop through track and shower, creating node objects + // (2) loop through node objects connecting daughters to mothers + // (3) (optional) get depth of each node by doing breath-first traversal + // (4) sort vector pointers by depth (necessary?) + + // dump mctruth info + // LARCV_DEBUG() << "MCTruth Dump" << std::endl; + // int imctruth=0; + // for ( auto& mctruth : mctruth_v ) { + // LARCV_DEBUG() << "MCTRUTH[" << imctruth << "] --------------" << std::endl; + // int imcpart = 0; + // for ( auto& part : mctruth.GetParticles() ) { + // LARCV_DEBUG() << " mcpart[" << imcpart << "] -------" << std::endl; + // LARCV_DEBUG() << " status=" << part.StatusCode() << std::endl; + // LARCV_DEBUG() << " trackid=" << part.TrackId() << std::endl; + // LARCV_DEBUG() << " pdg=" << part.PdgCode() << std::endl; + // LARCV_DEBUG() << " motherid=" << part.Mother() << std::endl; + // LARCV_DEBUG() << " process=" << part.Process() << " endprocess=" << part.EndProcess() << std::endl; + // LARCV_DEBUG() << " num daughters=" << part.Daughters().size() << std::endl; + // } + // } + + clear(); + + // fill the daugher to mother shower ID map + _fill_shower_daughter2mother_map( shower_v ); + + node_v.clear(); + node_v.reserve( shower_v.size()+track_v.size()+100 ); + + // Create ROOT node + MCPGNode neutrino ( node_v.size(), -1, 0, 0, -1 ); + + // define TPC widths + float tpc_x = larutil::Geometry::GetME()->DetHalfWidth()*2.0; + float tpc_y = larutil::Geometry::GetME()->DetHalfHeight(); + float tpc_z = larutil::Geometry::GetME()->DetLength(); + + // if there is a neutrino, then we add the start position + if ( mctruth_v.size()>0 ) { + const larlite::mctruth& mct = mctruth_v.front(); + neutrino.E_MeV = mct.GetNeutrino().Nu().Trajectory().front().E()*1000.0; + neutrino.start.resize(4); + neutrino.start[0] = mct.GetNeutrino().Nu().Trajectory().front().X(); + neutrino.start[1] = mct.GetNeutrino().Nu().Trajectory().front().Y(); + neutrino.start[2] = mct.GetNeutrino().Nu().Trajectory().front().Z(); + neutrino.start[3] = mct.GetNeutrino().Nu().Trajectory().front().T(); + } + node_v.emplace_back( std::move(neutrino) ); + + _eventRootNode = &node_v[0]; + + // keep tabs on all track IDs found + std::set tid_list; + + // load spacechargemicroboone: forward effect + //larutil::SpaceChargeMicroBooNE sce; + + // struct for primary particles from nu interactions + struct NuPart_t { + int geantid; + int pdg; + float E_MeV; + std::vector pos; + }; + + std::vector< NuPart_t > nu_part_v; + + // collect data from mcreco tracks + int mc_product_type = 0; // larlite::mctrack + for (int vidx=0; vidx<(int)track_v.size(); vidx++ ) { + const larlite::mctrack& mct = track_v[vidx]; + LARCV_DEBUG() << "track[" << vidx << "] origin=" << mct.Origin() + << " tid=" << mct.TrackID() + << " mid=" << mct.MotherTrackID() + << " aid=" << mct.AncestorTrackID() + << " pid=" << mct.PdgCode() + << std::endl; + + MCPGNode tracknode( node_v.size(), mc_product_type, mct.TrackID(), vidx, mct.PdgCode() ); + tracknode.E_MeV = mct.Start().E(); + tracknode.aid = mct.AncestorTrackID(); + tracknode.mtid = mct.MotherTrackID(); + tracknode.process = mct.Process(); + tracknode.mother_process = mct.MotherProcess(); + tracknode.ancestor_process = mct.AncestorProcess(); + + // convert energy into relativistic KE + if ( mct.PdgCode()==2212 ) tracknode.E_MeV -= 938.0; + else if ( mct.PdgCode()==2112 ) tracknode.E_MeV -= 940.0; + else if ( abs(mct.PdgCode())==13 ) tracknode.E_MeV -= 105.; + else if ( abs(mct.PdgCode())==211 ) tracknode.E_MeV -= 135.; + tracknode.origin = mct.Origin(); + + // exception for process=neutronInelastic + if ( mct.PdgCode()==2112 && mct.Process()=="neutronInelastic" ) { + // these are actually protons made by a neutrino inelastic process + // just the way the MC reco producer saves these. + tracknode.E_MeV = mct.Start().E()-938.0; + } + + tid_list.insert( tracknode.tid ); + + // set creation position, time + tracknode.start.resize(4); + tracknode.start[0] = mct.Start().X(); + tracknode.start[1] = mct.Start().Y(); + tracknode.start[2] = mct.Start().Z(); + tracknode.start[3] = mct.Start().T(); + try { + tracknode.mom4[0] = mct.Start().E(); + tracknode.mom4[1] = mct.Start().Px(); + tracknode.mom4[2] = mct.Start().Py(); + tracknode.mom4[3] = mct.Start().Pz(); + } + catch (...) { + tracknode.mom4 = std::vector{0,0,0,0}; + } + + if ( tracknode.origin==1 ) { + // store nu particle + NuPart_t nuparticle; + nuparticle.geantid = tracknode.tid; + nuparticle.pdg = tracknode.pid; + nuparticle.E_MeV = mct.Start().E(); + nuparticle.pos = tracknode.start; + nu_part_v.push_back( nuparticle ); + } + + // find the first position + // - with cryo edep + // - inside the TPC + tracknode.first_edep_pos = std::vector(4,0); + tracknode.first_tpc_pos = std::vector(4,0); + + if ( mct.size()>=1 ) { + tracknode.first_edep_pos[0] = mct[0].X(); + tracknode.first_edep_pos[1] = mct[0].Y(); + tracknode.first_edep_pos[2] = mct[0].Z(); + tracknode.first_edep_pos[3] = mct[0].T(); + + // first TPC point + std::vector recopos(4,0); + std::vector edep_imgpos(4,0); + std::vector xyz(4,0); + bool intpc = false; + bool inimage = false; + bool first_step = false; + for (auto const& step : mct ) { + xyz[0] = (float)step.X(); + xyz[1] = (float)step.Y(); + xyz[2] = (float)step.Z(); + xyz[3] = (float)step.T(); + + if ( !first_step ) { + first_step = true; + tracknode.first_edep_pos = xyz; + } + + if ( ( xyz[0]>0.0 && xyz[0]0 && xyz[2]{ (float)mcsh.Start().X(), (float)mcsh.Start().Y(), (float)mcsh.Start().Z(), (float)mcsh.Start().T() }; + showernode.aid = mcsh.AncestorTrackID(); + showernode.mtid = mcsh.MotherTrackID(); + + showernode.first_edep_pos = std::vector(4,0); + showernode.first_tpc_pos = std::vector(4,0); + + try { + showernode.mom4[0] = mcsh.Start().E(); + showernode.mom4[1] = mcsh.Start().Px(); + showernode.mom4[2] = mcsh.Start().Py(); + showernode.mom4[3] = mcsh.Start().Pz(); + } + catch (...) { + showernode.mom4 = std::vector{0,0,0,0}; + } + + bool x_isinf = false; + if ( mcsh.DetProfile().X()>1.0e100 || mcsh.DetProfile().X()<-1.0e100 ) + x_isinf = true; + if ( !x_isinf && !std::isnan( mcsh.DetProfile().X() ) ) { + //std::cout << "inf test: " << mcsh.DetProfile().X() << " " << std::isinf( mcsh.DetProfile().X() ) << std::endl; + std::vector detprofile = { (float)mcsh.DetProfile().X(), (float)mcsh.DetProfile().Y(), (float)mcsh.DetProfile().Z(), (float)mcsh.DetProfile().T() }; + showernode.first_edep_pos = detprofile; + showernode.first_tpc_pos = detprofile; + } + if ( showernode.origin==1 ) { + // store nu particle + NuPart_t nuparticle; + nuparticle.geantid = showernode.tid; + nuparticle.pdg = showernode.pid; + nuparticle.E_MeV = mcsh.Start().E(); + nuparticle.pos = std::vector{ (float)mcsh.Start().X(), + (float)mcsh.Start().Y(), + (float)mcsh.Start().Z(), + (float)mcsh.Start().T() }; + nu_part_v.push_back( nuparticle ); + } + + tid_list.insert( showernode.tid ); + node_v.emplace_back( std::move(showernode) ); + } + + // find the geant4 trackid offset for the neutrinos + long smallest_nu_tid = -1; + for ( auto& node : node_v ) { + if ( node.origin==1 ) { + if (smallest_nu_tid<0 || node.tid < smallest_nu_tid ) { + smallest_nu_tid = node.tid; + } + } + } + LARCV_DEBUG() << "Smallest neutrino Geant4 Track ID: " << smallest_nu_tid << std::endl; + + // collect from mctruth (data form GENIE generator) + mc_product_type = 3; + + // we need to match the earliest track or shower object to + // a genie final state particle to get offset + int ifs_1 = 0; + int matched_fs = -1; + int matched_geant_id = -1; + for ( auto& mctruth : mctruth_v ) { + for ( auto& part : mctruth.GetParticles() ) { + if ( part.StatusCode()==1 ) { + ifs_1 += 1; + // does ginal state particel match any of the geant4 particles? + for (auto& nupart : nu_part_v) { + // match pdg + if ( nupart.pdg!=part.PdgCode() ) + continue; + + // match energy + float dE_MeV = std::fabs(nupart.E_MeV-part.Momentum(0)[3]*1000.0); + LARCV_DEBUG() << "mctruth part: " << dE_MeV << std::endl; + if ( dE_MeV > 10.0 ) + continue; + + matched_fs = ifs_1; + matched_geant_id = nupart.geantid; + LARCV_DEBUG() << "have first match. " + << "genie finalstate id=" << matched_fs + << " geantid=" << matched_geant_id + << std::endl; + break; + } + + }//end of if status code + if (matched_fs>=0) + break; + }//end of mcpart loop + if (matched_fs>=0) + break; + }//end of mctruth loop + + int geantid_offset = smallest_nu_tid - (matched_fs-1); + LARCV_INFO() << "geantid offset: " << geantid_offset << " smallest_nu_tid=" << smallest_nu_tid << std::endl; + LARCV_INFO() << " matched_fs=" << matched_fs << " matched_geant4=" << matched_geant_id << std::endl; + + int imctruth=0; + int ifs = 0; + for ( auto& mctruth : mctruth_v ) { + int imcpart = 0; + for ( auto& part : mctruth.GetParticles() ) { + LARCV_DEBUG() << " mcpart[" << imctruth << "," << imcpart << "] -------" << std::endl; + LARCV_DEBUG() << " status=" << part.StatusCode() << std::endl; + LARCV_DEBUG() << " trackid=" << part.TrackId() << std::endl; + LARCV_DEBUG() << " pdg=" << part.PdgCode() << std::endl; + LARCV_DEBUG() << " motherid=" << part.Mother() << std::endl; + LARCV_DEBUG() << " process=" << part.Process() << " endprocess=" << part.EndProcess() << std::endl; + LARCV_DEBUG() << " num daughters=" << part.Daughters().size() << std::endl; + + if ( part.StatusCode()==1 ) { + int geant_trackid = geantid_offset + ifs; + LARCV_DEBUG() << " Stable Final State. Implied Geant4 ID = " << geant_trackid << std::endl; + ifs++; + + auto it_tid = tid_list.find(geant_trackid); + if (it_tid==tid_list.end() ) { + int nodeidx = node_v.size(); + int type = 3; // genie-final-state + int tid = geant_trackid; + int vidx = imcpart; + int pid = part.PdgCode(); + MCPGNode* mother = nullptr; + int mid = -1; + float energy = part.Momentum(0)[3]*1e3 - part.Mass()*1.0e3; // in GeV, convert to MeV + std::vector start { (float)part.Position(0)[0], + (float)part.Position(0)[1], + (float)part.Position(0)[2], + (float)part.Position(0)[3]}; + + //LARCV_DEBUG() << "Creating Mother node from GENIE final states: tid=" << tid << " type=" << 3 << std::endl; + MCPGNode fsnode( nodeidx, mc_product_type, tid, vidx, pid, mother, mid, energy, "primary" ); + fsnode.start = start; + // fsnode.first_edep_pos = start; + // fsnode.first_tpc_pos = start; + fsnode.mtid = tid; + fsnode.aid = tid; + fsnode.origin = 1; // neutrino origin (from genie) + + try { + for (int i=0; i<3; i++) + fsnode.mom4[i+1] = part.Momentum(0)[i]; + fsnode.mom4[0] = part.Momentum(0)[3]; + } + catch (...) { + fsnode.mom4 = std::vector{0,0,0,0}; + } + + LARCV_DEBUG() << "Add Genie Final State Particle to initial List: tid=" << tid << " pdg=" << pid << std::endl; + node_v.emplace_back( std::move(fsnode) ); + } + else { + LARCV_DEBUG() << "Genie Final State Partial [tid=" << geant_trackid << "] Already in MCReco List" << std::endl; + } + } + imcpart++; + } + imctruth++; + } + + // try to connect primary neutrino origin nodes to mctruth info + //_adoptNeutrinoOrphans( &mctruth_v ); // whats this? + + // now we connect the graph + + // sort MCPGNode object by geant track ID, relabel node IDs + std::sort( node_v.begin(), node_v.end() ); + for ( size_t nid=0; nidtid!=(int)track.MotherTrackID() ) { + // try ancestor ID + mothernode = findTrackID( track.AncestorTrackID() ); + if ( mothernode && mothernode->tid!=(int)track.AncestorTrackID() ) + mothernode = nullptr; + } + } + } + else if (node.type==1) { + //shower nodes + const larlite::mcshower& shower = shower_v[node.vidx]; + if (shower.TrackID()==shower.MotherTrackID() ) { + //primary + mothernode = &node_v[0]; + } + else { + //secondary + mothernode = findTrackID( shower.MotherTrackID() ); + if( mothernode==nullptr || mothernode->tid!=(int)shower.MotherTrackID() ) { + // try ancestor ID + mothernode = findTrackID( shower.AncestorTrackID() ); + if ( mothernode && mothernode->tid!=(int)shower.AncestorTrackID() ) + mothernode = nullptr; + } + } + } + else if (node.type==3) { + // genie fs nodes + // connet to ROOT node as they are primary by definition + mothernode = &(node_v[0]); + } + else { + continue; + } + + if (mothernode) { + // found mother, connect + //std::cout << "found mother: " << strNodeInfo(*mothernode) << std::endl; + node.mother = mothernode; + node.mid = mothernode->nodeidx; + mothernode->daughter_v.push_back( &node ); + mothernode->daughter_idx_v.push_back( node.nodeidx ); + } + + }//end of node loop + + if ( _cluster_neutrino_particles ) { + int nnu = _define_neutrino_interaction_nodes( track_v, shower_v ); + LARCV_INFO() << "Rearranged graph to include neutrino vertex nodes. Number of Nu Interactions: " << nnu << std::endl; + } + + // map track id to position in node_v container + for (size_t i=0; ividx); + // to do: check that truth info matches? + return x; + } + catch ( std::exception& ex ) { + std::stringstream err; + err << "Error accessing event_mctrack container at index=" << node->vidx << " for node[idx]=nodeidx" << std::endl; + err << "Node info: " << std::endl; + err << strNodeInfo( *node ) << std::endl; + err << "Exception: " << ex.what() << std::endl; + throw std::runtime_error( err.str() ); + } + } + + /** + * @brief wrapper function to retrieve Node's corresponding mcshower object from larlite container + */ + const larlite::mcshower& + MCParticleGraph::_retrieve_mcshowerobject( const MCPGNode* node, + const larlite::event_mcshower& ev_shower_v ) + { + try { + const larlite::mcshower& x = ev_shower_v.at(node->vidx); + // to do: check that truth info matches? + return x; + } + catch ( std::exception& ex ) { + std::stringstream err; + err << "Error accessing event_mcshower container at index=" << node->vidx << " for node[idx]=nodeidx" << std::endl; + err << "Node info: " << std::endl; + err << strNodeInfo( *node ) << std::endl; + err << "Exception: " << ex.what() << std::endl; + throw std::runtime_error( err.str() ); + } + } + + /** + * @brief convenience function to retrieve Node's corresponding mctrack object from larlite top-level io interface + */ + const larlite::mctrack& MCParticleGraph::_retrieve_mctrackobject( const MCPGNode* node, + larlite::storage_manager& ioll, + std::string producername ) + { + const larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack, producername ); + + return _retrieve_mctrackobject( node, *ev_mctrack ); + } + + /** + * @brief convenience function to retrieve Node's corresponding mcshower object from larlite top-level io interface + */ + const larlite::mcshower& + MCParticleGraph::_retrieve_mcshowerobject( const MCPGNode* node, + larlite::storage_manager& ioll, + std::string producername ) + { + const larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower, producername ); + + return _retrieve_mcshowerobject( node, *ev_mcshower ); + } + + + /** + * @brief locate MCPGNode in node_v using trackid (from geant4) + * + * we first search our vector of MCPGNode objects, which are sorted by track id. + * + * we then search the keys of the _shower_daughter2mother map, where the keys + * are track id of particles made by the initial shower. + * + * @return The node if found, nullptr if not found + * + */ + MCPGNode* MCParticleGraph::findTrackID( long trackid ) { + + auto it = _tid_to_node_v.find( (long)trackid ); + if ( it==_tid_to_node_v.end()) + return nullptr; + + auto& node = node_v.at(it->second); + return &node; + + // MCPGNode dummy; + // dummy.tid = trackid; + // auto it = std::lower_bound( node_v.begin(), node_v.end(), dummy ); + // if ( it==node_v.end() || it->tid!=dummy.tid ) { + // // no node, check the daughter ID map + // auto it_showerdaughter = _shower_daughter2mother.find( trackid ); + // if ( it_showerdaughter!=_shower_daughter2mother.end() ) { + // // found an id + // //LARCV_DEBUG() << " found map to mother: " << it_showerdaughter->second << std::endl; + // dummy.tid = it_showerdaughter->second; + // } + // else { + // // still nope + // return nullptr; + // } + // // with the mother shower's trackid, try to find the node again + // it = std::lower_bound( node_v.begin(), node_v.end(), dummy ); + // //if ( it!=node_v.end() ) + // // LARCV_DEBUG() << " mother id maps to existing node" << std::endl; + // } + + // if ( it==node_v.end() || it->tid!=dummy.tid ) { + // return nullptr; + // } + // //std::cout << "find trackid=" << trackid << ": " << strNodeInfo( *it ) << std::endl; + // return &*(it+0); + } + + /** + * print node info for all nodes stored in node_v + * + */ + void MCParticleGraph::printAllNodeInfo() { + for ( auto const& node : node_v ) { + printNodeInfo(node); + } + } + + /** + * create string with info from a given MCPGNode + * + * @param[in] node Note_t object to make info for. + * + */ + std::string MCParticleGraph::strNodeInfo( const MCPGNode& node ) + { + + int hasmother = ( node.mother ) ? 1 : 0; + + std::stringstream ss; + //ss << "node[" << node.nodeidx << "," << &node << "] " + ss << "node[" << node.nodeidx << "] " + << " (type,vidx)=(" << node.type << "," << node.vidx << ") " + << " origin=" << node.origin + << " pdg=" << node.pid + << " p=" << node.process; + if ( node.mother_process!="" ) + ss << "/" << node.mother_process; + if ( node.ancestor_process!="" ) + ss << "/" << node.ancestor_process; + ss << " tid=" << node.tid + << " mtid=" << node.mtid + << " aid=" << node.aid + << " KE=" << node.E_MeV << " MeV" + //<< " xyzt= + //<< " tusec=" << node.start[3]*1.0e-3 << " us" + //<< " (mid,mother)=(" << node.mid << "," << node.mother << ") " + //<< " (mid,mother)=(" << node.mid << ") " + << " hasmother=" << hasmother + << " ndaughters=" << node.daughter_v.size() + << std::endl; + // additional positional info + if ( node.start.size()>=4 ) + ss << " start(x,y,z,t)=(" << node.start[0] << "," << node.start[1] << "," << node.start[2] << "," << node.start[3]*1.0e-3 << " us) " << std::endl; + + if ( node.first_edep_pos.size()>=4 ) + ss << " edep-cryo(x,y,z,t)=(" << node.first_edep_pos[0] << "," + << node.first_edep_pos[1] << "," + << node.first_edep_pos[2] << "," + << node.first_edep_pos[3]*1.0e-3 + << " us)" << std::endl; + + if ( node.first_tpc_pos.size()>=4 ) + ss << " edep-tpc(x,y,z,t)=(" << node.first_tpc_pos[0] << "," + << node.first_tpc_pos[1] << "," + << node.first_tpc_pos[2] << "," + << node.first_tpc_pos[3]*1.0e-3 + << " us)" << std::endl; + + if ( node.mom4.size()>=4 ) + ss << " mom4(E,px,py,pz)=(" << node.mom4[0] << "," + << node.mom4[1] << "," + << node.mom4[2] << "," + << node.mom4[3] << ") " + << " MeV" << std::endl; + + + //ss << std::endl; + return ss.str(); + } + + /** + * print MCPGNode info to standard out + * + * @param[in] node MCPGNode object to print info for. + * + */ + void MCParticleGraph::printNodeInfo( const MCPGNode& node ) { + std::cout << strNodeInfo(node) << std::endl; + } + + /** + * dump graph to standard out + * + */ + void MCParticleGraph::printGraph( MCPGNode* rootnode, bool visible_only ) { + // here we go! + std::cout << "=======[ MCParticleGraph::printGraph ]==============" << std::endl; + int depth = 0; + if (rootnode==nullptr ) + rootnode = &node_v.front(); + _recursivePrintGraph( rootnode, depth, visible_only ); + } + + /* + * internal recursive function that prints node info + * + */ + void MCParticleGraph::_recursivePrintGraph( MCPGNode* node, int& depth, bool visible_only ) { + if ( depth<0 ) return; // we're done (error?) + + // depth first printing of nodes + std::string info = strNodeInfo( *node ); + std::string branch = ""; + std::string branch2 = ""; + for ( int i=0; i0 ) { + branch += "-- "; + branch2 += " "; + } + + std::stringstream ss(info); + std::string to; + bool firstline = true; + while(std::getline(ss,to,'\n')) + { + if (firstline) + std::cout << branch << to << std::endl; + else + std::cout << branch2 << to << std::endl; + firstline = false; + } + + //std::cout << branch << info;// << std::endl; + + // we loop through our daughters + ++depth; + for ( auto& daughter : node->daughter_v ) { + _recursivePrintGraph( daughter, depth, visible_only ); + } + --depth; + return; + } + + /** + * get list of Nodes_t that are decendents of the given trackID + * + * + */ + std::vector MCParticleGraph::getNodeAndDescendentsFromTrackID( const int& trackid ) { + std::vector nodelist; + + MCPGNode* rootnode = findTrackID( trackid ); + if ( rootnode==nullptr ) + return nodelist; + + nodelist.push_back( rootnode ); + recursiveGetNodeAndDescendents( rootnode, nodelist ); + return nodelist; + } + + /** + * recursively get list of Nodes_t that are descendents of the given MCPGNode* + * + * follows depth-first traversal + */ + void MCParticleGraph::recursiveGetNodeAndDescendents( MCPGNode* node, std::vector& nodelist ) { + if ( node==nullptr ) return; + for ( auto& pdaughter : node->daughter_v ) { + nodelist.push_back( pdaughter ); + recursiveGetNodeAndDescendents( pdaughter, nodelist ); + } + return; + } + + /** + * get list of primary particles + * + * by default, neutrons are excluded + * + */ + std::vector MCParticleGraph::getPrimaryParticles( bool exclude_neutrons ) { + std::vector nodelist; + MCPGNode* rootnode = &node_v[0]; + for ( auto& node : node_v ) { + if ( node.mother==rootnode ) { + // primary + if ( !exclude_neutrons || node.pid!=2112 ) { + nodelist.push_back( &node ); + } + } + } + return nodelist; + } + + /** + * get list of neutrino-only primary particles + * + * by default, neutrons are excluded + * + */ + std::vector MCParticleGraph::getNeutrinoPrimaryParticles( bool exclude_neutrons ) { + std::vector nodelist; + MCPGNode* rootnode = &node_v[0]; + for ( auto& node : node_v ) { + if ( node.mother==rootnode && node.origin==1 ) { + // primary + if ( !exclude_neutrons || node.pid!=2112 ) { + nodelist.push_back( &node ); + } + } + } + return nodelist; + } + + /** + * get list of neutrino-only primary particles + * + * by default, neutrons are excluded + * + */ + std::vector MCParticleGraph::getNeutrinoParticles( bool exclude_neutrons ) { + std::vector nodelist; + //MCPGNode* rootnode = &node_v[0]; + for ( auto& node : node_v ) { + if ( node.origin==1 ) { + // neutrino particle + if ( !exclude_neutrons || node.pid!=2112 ) { + nodelist.push_back( &node ); + } + } + } + return nodelist; + } + +// /** +// * convert real position+time and calculate apparent position +// */ +// void MCParticleGraph::_get_imgpos( std::vector& realpos4, +// std::vector& imgpos4, +// larutil::SpaceChargeMicroBooNE& sce, +// bool apply_sce ) +// { + +// imgpos4.resize(4,0); + +// // apparent position according to image +// std::vector dpos(3,0); +// for (int i=0; i<3; i++) { +// dpos[i] = realpos4[i]; +// } + +// std::vector txyz(4,0); +// if ( apply_sce ) { +// std::vector offset = sce.GetPosOffsets( dpos[0], dpos[1], dpos[2] ); +// dpos[0] = dpos[0] - offset[0] + 0.7; +// dpos[1] = dpos[1] + offset[1]; +// dpos[2] = dpos[2] + offset[2]; +// } + +// for (int i=0; i<3; i++) { +// txyz[1+i] = realpos4[i]; +// } +// txyz[0] = realpos4[3]; +// float tick = 0.; +// if (apply_sce) +// tick = CrossingPointsAnaMethods::getTick( txyz, 4050.0, &sce ); +// else +// tick = CrossingPointsAnaMethods::getTick( txyz, 4050.0, NULL ); + +// for (int i=0; i<3; i++) { +// imgpos4[i] = dpos[i]; +// } +// imgpos4[3] = tick; + +// // now make x an apparent x +// imgpos4[0] = (tick-3200)*0.5*larutil::LArProperties::GetME()->DriftVelocity(); + +// } + + /** + * @brief makes a map from daughter track IDs to their mother (shower) track ID + * + * The info comes from the MCShowerObjecs themselves. They store daughter IDs + * in a vector accessed by mcshower::DaughterTrackID(). + * + * This map will let us map the track ids stored in each pixel of the + * the instance ID map to its original shower. + * + */ + void MCParticleGraph::_fill_shower_daughter2mother_map( const std::vector& mcsh_v ) + { + LARCV_DEBUG() << "daughter2mother fill" << std::endl; + _shower_daughter2mother.clear(); + + + for (auto const& mcsh : mcsh_v ) { + int showerid = mcsh.TrackID(); + std::vector dlist = mcsh.DaughterTrackID(); + std::sort( dlist.begin(), dlist.end() ); + for (auto const& daughterid : dlist ) { + _shower_daughter2mother[daughterid]= showerid; + //LARCV_DEBUG() << " " << daughterid << " -> " << showerid << std::endl; + } + } + LARCV_INFO() << "Num entries in daughter2mother map: " << _shower_daughter2mother.size() << std::endl; + } + + int MCParticleGraph::_define_neutrino_interaction_nodes( larlite::storage_manager& ioll ) + { + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + return _define_neutrino_interaction_nodes( *ev_mctrack, *ev_mcshower ); + } + + int MCParticleGraph::_define_neutrino_interaction_nodes( const larlite::event_mctrack& ev_mctrack, + const larlite::event_mcshower& ev_mcshower ) + { + // first we collect nodes with neutrino origin + bool exclude_neutrons = false; + std::vector< MCPGNode* > _nu_primary_v = getNeutrinoPrimaryParticles( exclude_neutrons ); + LARCV_DEBUG() << "Number of nu primaries returned: " << _nu_primary_v.size() << std::endl; + + // if we have access to MCTruth info, we should use it to define vertex locations. + // otherwise we use some distance threshold. 0.3 mm, the pitch length? + std::map< int, std::set > _nu_collected_primaries_v; + _nu_vertices_v.clear(); + + + for ( auto& pnode : _nu_primary_v ) { + // we test the vertex for every primary + LARCV_DEBUG() << "Considering Nu Primary node[" << pnode->nodeidx << "] tid=" << pnode->tid << std::endl; + + std::vector start = {0,0,0,0}; + + if ( pnode->isTrackObject() ) { + auto const& track = _retrieve_mctrackobject( pnode, ev_mctrack ); + for (int i=0; i<4; i++) { + start[i] = track.Start().Position()[i]; + } + } + else if (pnode->isShowerObject()) { + auto const& shower = _retrieve_mcshowerobject( pnode, ev_mcshower ); + for (int i=0; i<4; i++) { + start[i] = shower.Start().Position()[i]; // creation point in geant4, for photon, not the same as conversion point where visible EM cascade begins + } + } + else if (pnode->isGenieFinalStateObject()) { + start = pnode->start; + } + else { + LARCV_DEBUG() << "Node Primary neither a shower nor track object: " << pnode->nodeidx << std::endl; + LARCV_DEBUG() << "node: " << strNodeInfo( *pnode ) << std::endl; + continue; + } + + bool found_vertex_match = false; + int idx_matched_vertex = -1; + float closest_match = 1e9; + std::vector pos = start; + + LARCV_DEBUG() << "Nu primary start: " << pos[0] << " " << pos[1] << " " << pos[2] << " " << pos[3] << std::endl; + + for ( int idx_vertex=0; idx_vertex<(int)_nu_vertices_v.size(); idx_vertex++ ) { + + auto& vertex = _nu_vertices_v.at(idx_vertex); + + // distance to existing vertex + float dist = 0.; + for (int i=0; i<3; i++) { + dist += ( pos[i]-vertex[i] )*( pos[i]-vertex[i] ); + } + dist = sqrt(dist); + + // update closest match + if ( dist < closest_match ) { + idx_matched_vertex = idx_vertex; + closest_match = dist; + + // qualifies as found? + if ( dist < _kNuVertexDistCutoff_cm ) { + found_vertex_match = true; + idx_matched_vertex = idx_vertex; + } + } + + }//end of loop over established nu vertices + LARCV_DEBUG() << "result of vertex search: closest=" << closest_match << " idx_matched=" << idx_matched_vertex << " found_match=" << found_vertex_match << std::endl; + + if ( !found_vertex_match ) { + // new neutrino vertex defined using position + LARCV_DEBUG() << "New vertex defined." << std::endl; + std::vector new_vertex = { (float)pos[0], (float)pos[1], (float)pos[2], (float)pos[3] }; + _nu_vertices_v.push_back( new_vertex ); + + int nu_vertex_id = (int)_nu_vertices_v.size()-1; // using position in _nu_vertices as an id number (should use a struct I know) + std::set nu_primary_set; + nu_primary_set.insert( pnode->nodeidx ); // add node index + _nu_collected_primaries_v[ nu_vertex_id ] = nu_primary_set; + } + else { + // found match + LARCV_DEBUG() << "Matched primary to existing vertex. IDX=" << idx_matched_vertex << std::endl; + auto it=_nu_collected_primaries_v.find( idx_matched_vertex ); + it->second.insert( pnode->nodeidx ); + } + + }//end of loop over neutrino primaries + + LARCV_DEBUG() << "Number of vertices defined: " << _nu_vertices_v.size() << std::endl; + + if ( _nu_vertices_v.size()==0 ) + return 0; + + // now that we have nu vertices, we need to define a new neutrino ancestor ID, then relabel ancestor IDs for daughters + long max_geant4_trackid = -1; + if ( _nu_vertices_v.size()>0 ) { + for (auto pnode : node_v) { + if ( pnode.tid>max_geant4_trackid ) { + max_geant4_trackid = pnode.tid; + } + } + } + LARCV_DEBUG() << "Starting with max trackid=" << max_geant4_trackid << " to assign nu vertex nodes" << std::endl; + std::set nu_attached_v; // gather list of indices that have been attached to neutrinos + std::vector< MCPGNode* > nu_pnode_v; + + for (int inuvtx=0; inuvtx<(int)_nu_vertices_v.size(); inuvtx++) { + + LARCV_DEBUG() << "Build nu vertex node and assign daughters. [NU VTX IDX=" << inuvtx << "]" << std::endl; + + // get the next node index + int nu_node_idx = (int)node_v.size(); + // need an acceptable fake trackID + long fake_trackid = max_geant4_trackid+1; + max_geant4_trackid++; + int type_id = 2; + int pid = -1; + int mtid = fake_trackid; + float energy = 0.0; + std::string proc = "nuvertex"; + MCPGNode nu_node( nu_node_idx, type_id, fake_trackid, inuvtx, + pid, _eventRootNode, + mtid, energy, proc ); + + nu_node.origin = 1; + nu_node.aid = fake_trackid; + nu_node.mtid = -1; + + // insert into node vector + node_v.emplace_back( std::move(nu_node) ); + + // get pointer + MCPGNode* pnu_node = &(node_v.at(nu_node_idx)); + nu_pnode_v.push_back( pnu_node ); + + // now we need to + // (1) change the nu primaries associated to this interaction + // to list their mother node to this node representing the neutrino interaction + // (2) relabel the ancestor ID of all primary neutrinos to this ID + auto it_prim = _nu_collected_primaries_v.find( inuvtx ); + if ( it_prim==_nu_collected_primaries_v.end() ) + continue; + + for ( auto& nodeidx : it_prim->second ) { + + LARCV_DEBUG() << "Add primary, node=" << nodeidx << ", to nu vertex[" << inuvtx << "]" << std::endl; + + LARCV_DEBUG() << " reassign mother to nu vertex pnode=" << pnu_node << std::endl; + MCPGNode* pnode_nuprim = &(node_v.at(nodeidx)); + pnode_nuprim->mother = pnu_node; + + // collect daughters + std::vector prim_daughters = getNodeAndDescendentsFromTrackID( pnode_nuprim->tid ); + LARCV_DEBUG() << "Collect descendents of nu primary node[ " << nodeidx << "] ndaughters=" << (int)prim_daughters.size()-1 << std::endl; + + // reset the ancestor id of all of these nodes to the new fake track ID for nu interaction + // note: the function above returns the node of the starting track id + for ( auto& pdnode : prim_daughters ) { + pdnode->aid = (int)fake_trackid; + } + + // add this primary to the daughter list of the nu node + pnu_node->daughter_idx_v.push_back( pnode_nuprim->nodeidx ); + pnu_node->daughter_v.push_back( pnode_nuprim ); + LARCV_DEBUG() << "Add to NuVertex node list of daughters: now " << pnu_node->daughter_v.size() << std::endl; + nu_attached_v.insert( pnode_nuprim->nodeidx ); + + // add its E + pnu_node->E_MeV += pnode_nuprim->E_MeV; + LARCV_DEBUG() << "Add to NuVertex energy: now " << pnu_node->E_MeV << " MeV" << std::endl; + }//end of loop over primary node + + nu_attached_v.insert( nu_node_idx ); + + }//end of loop over newly creatd neutrino vertices + + // now we have to redefine the root node's connections + _eventRootNode = &(node_v[0]); + std::vector all_prim_v = getPrimaryParticles(exclude_neutrons); + + _eventRootNode->daughter_idx_v.clear(); + _eventRootNode->daughter_v.clear(); + + + for (auto& pnode : all_prim_v ) { + auto it_attached = nu_attached_v.find( pnode->nodeidx ); + if ( it_attached==nu_attached_v.end() ) { + // not attached to neutrino vertex, so add to root node + _eventRootNode->daughter_idx_v.push_back( pnode->nodeidx ); + _eventRootNode->daughter_v.push_back( pnode ); + } + } + + // attach the nu vertex nodes + for (auto& pnode : nu_pnode_v ) { + _eventRootNode->daughter_idx_v.push_back( pnode->nodeidx ); + _eventRootNode->daughter_v.push_back( pnode ); + } + + return _nu_vertices_v.size(); + } + + long MCParticleGraph::getShowerMotherID( long trackid ) + { + auto it = _shower_daughter2mother.find( trackid ); + if ( it!=_shower_daughter2mother.end() ) { + return it->second; + } + return -1; + } + + long MCParticleGraph::getParticleID( long trackid ) + { + auto pnode = findTrackID( trackid ); + if ( pnode==nullptr ) + return -1; + + return pnode->pid; + } + + long MCParticleGraph::getAncestorID( long trackid ) + { + auto pnode = findTrackID( trackid ); + if ( pnode==nullptr ) + return -1; + + return pnode->aid; + } + + +} +} diff --git a/ublarcvapp/MCTools/MCParticleGraph.h b/ublarcvapp/MCTools/MCParticleGraph.h new file mode 100644 index 0000000..bcab877 --- /dev/null +++ b/ublarcvapp/MCTools/MCParticleGraph.h @@ -0,0 +1,102 @@ +#ifndef __UBLARCVAPP_MCTOOLS_MCPARTICLE_GRAPH_H__ +#define __UBLARCVAPP_MCTOOLS_MCPARTICLE_GRAPH_H__ + +/** + * @ingroup ublarcvapp::mctools + * @class MCParticleGraph + * @brief Store simulated particle information in an event in graph form + * + * We build this graph from larlite::mcshower and larlite::mctrack information. + * These data products come from the larsorft MCReco producer. + */ + +#include +#include + +#include "larcv/core/Base/larcv_base.h" +#include "MCPGNode.h" + +// forward declarations +namespace larlite { + class storage_manager; + class event_mcshower; + class event_mctrack; + class event_mctruth; + class mcshower; + class mctrack; +} + +namespace ublarcvapp { +namespace mctools { + + class MCParticleGraph : public larcv::larcv_base { + + public: + + MCParticleGraph() + : larcv::larcv_base("MCParticleGraph"), + _eventRootNode(nullptr), + _cluster_neutrino_particles(false), + _kNuVertexDistCutoff_cm(0.3) + {}; + + ~MCParticleGraph() + {}; + + void clear(); + void cluster_nu_particles( bool doit=true ) { _cluster_neutrino_particles=doit; }; + void buildgraph( larlite::storage_manager& ioll ); + void buildgraph( const larlite::event_mcshower& shower_v, + const larlite::event_mctrack& track_v, + const larlite::event_mctruth& mctruth_v ); + + MCPGNode* findTrackID( long trackid ); + std::vector getNodeAndDescendentsFromTrackID( const int& trackid ); + std::vector getPrimaryParticles( bool exclude_neutrons ); + std::vector getNeutrinoPrimaryParticles( bool exclude_neutrons ); + std::vector getNeutrinoParticles( bool exclude_neutrons ); + + void printAllNodeInfo(); + std::string strNodeInfo( const MCPGNode& node ); + void printNodeInfo( const MCPGNode& node ); + void printGraph( MCPGNode* rootnode, bool visible_only ); + + long getShowerMotherID( long trackid ); + long getAncestorID( long trackid ); + long getParticleID( long trackid ); + + protected: + void recursiveGetNodeAndDescendents( MCPGNode* node, std::vector& nodelist ); + void _recursivePrintGraph( MCPGNode* node, int& depth, bool visible_only ); + const larlite::mctrack& _retrieve_mctrackobject( const MCPGNode* node, const larlite::event_mctrack& ev_track_v ); + const larlite::mcshower& _retrieve_mcshowerobject( const MCPGNode* node, const larlite::event_mcshower& ev_shower_v ); + const larlite::mctrack& _retrieve_mctrackobject( const MCPGNode* node, larlite::storage_manager& ioll, std::string producername="mcreco" ); + const larlite::mcshower& _retrieve_mcshowerobject( const MCPGNode* node, larlite::storage_manager& ioll, std::string producername="mcreco" ); + + + public: + + std::vector< MCPGNode > node_v; //< collection of nodes + MCPGNode* _eventRootNode; + bool _cluster_neutrino_particles; //< if true, nu primary particles clustered together + float _kNuVertexDistCutoff_cm; + std::vector< std::vector > _nu_vertices_v; + std::map _tid_to_node_v; ///< map from trackid to position in node_v + + std::map _shower_daughter2mother; + + protected: + void _fill_shower_daughter2mother_map( const std::vector& mcsh_v ); + int _define_neutrino_interaction_nodes( larlite::storage_manager& ioll ); + int _define_neutrino_interaction_nodes( const larlite::event_mctrack& ev_mctrack, + const larlite::event_mcshower& ev_mcshower ); + + + + + }; + +} +} + +#endif \ No newline at end of file diff --git a/ublarcvapp/MCTools/MCPixelLabelMaker.cxx b/ublarcvapp/MCTools/MCPixelLabelMaker.cxx new file mode 100644 index 0000000..d0feeda --- /dev/null +++ b/ublarcvapp/MCTools/MCPixelLabelMaker.cxx @@ -0,0 +1,406 @@ +#include "MCPixelLabelMaker.h" + +#include "larlite/LArUtil/TimeService.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/DataFormat/simch.h" + +#include "larcv/core/DataFormat/EventImage2D.h" + +#include "ublarcvapp/MCTools/MCPos2ImageUtils.h" + +#ifdef HAVE_HIGHFIVE +#include +#endif + +namespace ublarcvapp { +namespace mctools { + + MCPixelLabelMaker::~MCPixelLabelMaker() + { + if ( psce ) { + delete psce; + psce = nullptr; + } + + if ( preverse_sce ) { + delete preverse_sce; + preverse_sce = nullptr; + } + } + + void MCPixelLabelMaker::process( + larlite::storage_manager& ioll, + larcv::IOManager& iolcv, + std::string image2d_tree_name ) + { + + // moving real position to apparent position + if ( psce==nullptr ) { + psce = new larutil::SpaceChargeMicroBooNE(larutil::SpaceChargeMicroBooNE::kMCC9_Forward); + } + + ublarcvapp::mctools::MCParticleGraph mcpg; + mcpg.buildgraph(ioll); + + make_truthlabels_fromsimch(image2d_tree_name,ioll,iolcv,mcpg,psce); + + } + + void MCPixelLabelMaker::make_truthlabels_fromsimch( + std::string image2d_tree_name, + larlite::storage_manager& ioll, + larcv::IOManager& iolcv, + ublarcvapp::mctools::MCParticleGraph& mcpg, + larutil::SpaceChargeMicroBooNE* psce ) + { + + LARCV_INFO() << "start" << std::endl; + + // utility to go from simulated electronics TDC to + // ticks (tdcs after readout trigger) + const larutil::TimeService* timeservice = larutil::TimeService::GetME(); + + // geometry + const larutil::Geometry* geom = larutil::Geometry::GetME(); + + + // drift velocity + float driftv = larutil::LArProperties::GetME()->DriftVelocity(); + + // get the simch product we need + larlite::event_simch* ev_simch = + (larlite::event_simch*)ioll.get_data(larlite::data::kSimChannel,source); + + // get images + larcv::EventImage2D* ev_img = + (larcv::EventImage2D*)iolcv.get_data(larcv::kProductImage2D,image2d_tree_name); + + auto const& img_v = ev_img->as_vector(); + int nplanes = (int)img_v.size(); + + auto const& meta0 = img_v.at(0).meta(); + + // Clear the triplet info container + _pixels_v.clear(); + + // loop over simch information, making TripletLabels_t + size_t nsimch = ev_simch->size(); + size_t ide_w_no_t0 = 0; + size_t ide_outofimg = 0; + size_t ide_w_badwire = 0; + size_t num_ide_used = 0; + + for (size_t isimch=0; isimchat(isimch); + auto chid = simch.Channel(); + + larlite::geo::WireID wireid = geom->ChannelToWireID(chid); + int plane = wireid.Plane; + + //std::cout << "(" << isimch << ") chid=" << chid << " plane=" << plane << std::endl; + + auto& idcmap = simch.TDCIDEMap(); + for ( auto it=idcmap.begin(); it!=idcmap.end(); it++ ) { + long tdc = it->first; + int tick = int(timeservice->TPCTDC2Tick(tdc)); + size_t nide = it->second.size(); + for (auto& ide : it->second ) { + + std::vector pos = { ide.x, ide.y, ide.z }; + long tid = ide.trackID; + long xtid = (tid>=0) ? tid : -tid; + double edep = ide.energy; + + // get wire coordinates for these positions + std::vector wire_v(nplanes,0); + bool bad_wire = false; + for (int iplane=0; iplaneNearestWire( pos, iplane ); + wire_v[iplane] = wireid; + } + catch (...){ + bad_wire = true; + } + } + if ( bad_wire ) { + ide_w_badwire++; + continue; + } + + // replace low-energy shower trackid label with mother of the shower + long mtid = mcpg.getShowerMotherID( tid ); + if (mtid>0) { + xtid = mtid; + } + + auto pnode_t = mcpg.findTrackID( xtid ); + if ( pnode_t==nullptr ) { + // don't have an alternative for this right now + ide_w_no_t0++; + continue; + } + + long aid = mcpg.getAncestorID( xtid ); + int pid = pnode_t->pid; + int origin = pnode_t->origin; + + double t0 = pnode_t->start.at(3); // in ns + + + std::vector pos_sce(3,0); + std::vector imgpos(4,0); // (U,V,Y,tick) + int row = -1; + std::array imgindex = {-1,-1,-1,-1}; + + if ( source=="largeant" ) { + + bool applied = false; + pos_sce = psce->ApplySpaceChargeEffect( pos[0], pos[1], pos[2], applied ); + + // get (u,v,y,tick) + std::vector imgpos = + ublarcvapp::mctools::MCPos2ImageUtils::Get()->truepos_to_imagepos( pos[0], + pos[1], + pos[2], + t0, + true ); + pos[0] = (tick-3200)*0.5*driftv; + tick = (int)imgpos[3]; + if ( tick<(int)meta0.min_y() || tick>=(int)meta0.max_y() ) { + ide_outofimg++; + continue; + } + + row = meta0.row( imgpos[3] ); + + imgindex = { + (int)imgpos[0], + (int)imgpos[1], + (int)imgpos[2], + row }; + } + else if ( source=="driftWC:simpleSC:Detsim") { + if ( preverse_sce == nullptr ) { + preverse_sce = new larutil::SpaceChargeMicroBooNE(larutil::SpaceChargeMicroBooNE::kMCC9_Backward); + } + // the (y,z) is post spacecharge effect and x is drifted to the wireplanes + float x_t0_offset = (t0*1.0e-3)*driftv; + x_t0_offset = 0.0; + pos_sce[0] = float(tick-3200)*0.5*driftv - x_t0_offset; + pos_sce[1] = pos[1]; + pos_sce[2] = pos[2]; + pos[0] = pos_sce[0]; + + for (int p=0; p<3; p++) { + imgpos[p] = wire_v[p]; + imgindex[p] = wire_v[p]; + } + imgpos[3] = tick; + if ( tick<(int)meta0.min_y() || tick>=(int)meta0.max_y() ) { + ide_outofimg++; + continue; + } + row = meta0.row( tick ); + imgindex[3] = row; + } + else { + std::stringstream errmsg; + errmsg << "unrecognized simch source: " << source << std::endl; + throw std::runtime_error(errmsg.str()); + } + + auto it_index = _pixels_v._imgcoord_to_tripindex.find( imgindex ); + if ( it_index==_pixels_v._imgcoord_to_tripindex.end() ) { + MCPixelLabels trip; + trip.index = (long)_pixels_v._triplets_v.size(); + for (int i=0; i<3; i++) + trip.edep[i] = 0.0; + trip.imgcoord[0] = imgindex[0]; + trip.imgcoord[1] = imgindex[1]; + trip.imgcoord[2] = imgindex[2]; + trip.imgcoord[3] = (int)tick; + trip.imgcoord[4] = row; + trip.pos[0] = pos[0]; + trip.pos[1] = pos[1]; + trip.pos[2] = pos[2]; + trip.pos_reco[0] = pos_sce[0]; + trip.pos_reco[1] = pos_sce[1]; + trip.pos_reco[2] = pos_sce[2]; + + // make index map + _pixels_v._imgcoord_to_tripindex[imgindex] = trip.index; + + // we also index stuff + for (int iu=-dwire; iu<=dwire; iu++) { + for (int iv=-dwire; iv<=dwire; iv++) { + for (int iy=-dwire; iy<=dwire; iy++) { + for (int ir=-drow; ir<=drow; ir++) { + std::array modindex = imgindex; + modindex[0] += iu; + modindex[1] += iv; + modindex[2] += iy; + modindex[3] += ir; + auto it_mod = _pixels_v._imgcoord_to_tripindex.find( modindex ); + if ( it_mod==_pixels_v._imgcoord_to_tripindex.end()) { + _pixels_v._imgcoord_to_tripindex[modindex] = trip.index; + } + else { + // found a previous record + int new_dindex = std::abs(iu)+std::abs(iv)+std::abs(iy)+std::abs(ir); + + if ( new_dindex!=0 ) { + auto& oldtrip = _pixels_v._triplets_v.at( it_mod->second ); + int old_dindex = 0; + for (int ii=0; ii<3; ii++) { + old_dindex += std::abs( imgindex[ii]-oldtrip.imgcoord[ii] ); + } + old_dindex += std::abs( imgindex[3]-oldtrip.imgcoord[4] ); + + if ( new_dindex<=old_dindex ) { + // replace index + _pixels_v._imgcoord_to_tripindex[modindex] = trip.index; + } + } + } + } + } + } + } + + _pixels_v._triplets_v.emplace_back( std::move(trip) ); + it_index = _pixels_v._imgcoord_to_tripindex.find( imgindex ); + } + + auto& tripinfo = _pixels_v._triplets_v.at(it_index->second); + tripinfo.edep[plane] += edep; + tripinfo.trackids.insert(xtid); + tripinfo.aids.insert(aid); + tripinfo.pids.insert(pid); + tripinfo.origin.insert(origin); + num_ide_used++; + + } + + } + } + + LARCV_INFO() << "Number of Triplets Created: " << _pixels_v._triplets_v.size() << std::endl; + LARCV_INFO() << " IDEs with no track ID match and t0: " << ide_w_no_t0 << std::endl; + LARCV_INFO() << " IDEs out-of-image: " << ide_outofimg << std::endl; + LARCV_INFO() << " IDEs with no nearby-wire: " << ide_w_badwire << std::endl; + LARCV_INFO() << " IDEs used: " << num_ide_used << std::endl; + + + } + + void MCPixelLabelMaker::export_as_hdf( std::string hdf_outfile ) + { + +#ifdef HAVE_HIGHFIVE + + LARCV_INFO() << "export to " << hdf_outfile << std::endl; + + HighFive::File file(hdf_outfile, HighFive::File::Overwrite); + + file.createGroup("/mcpixel_labels"); + + // export different arrays for export + int ntriplets = _pixels_v._triplets_v.size(); + + std::vector pos_x(ntriplets,0); + std::vector pos_y(ntriplets,0); + std::vector pos_z(ntriplets,0); + + std::vector pos_x_reco(ntriplets,0); + std::vector pos_y_reco(ntriplets,0); + std::vector pos_z_reco(ntriplets,0); + + std::vector< std::array > edep(ntriplets); + std::vector trackid(ntriplets,0); + std::vector pid(ntriplets,0); + std::vector aid(ntriplets,0); + std::vector origin(ntriplets,0); + std::vector uwire(ntriplets,0); + std::vector vwire(ntriplets,0); + std::vector ywire(ntriplets,0); + std::vector tick(ntriplets,0); + std::vector row(ntriplets,0); + + for (auto const& triplet : _pixels_v._triplets_v ) { + long idx = triplet.index; + + pos_x[idx] = triplet.pos[0]; + pos_y[idx] = triplet.pos[1]; + pos_z[idx] = triplet.pos[2]; + + pos_x_reco[idx] = triplet.pos_reco[0]; + pos_y_reco[idx] = triplet.pos_reco[1]; + pos_z_reco[idx] = triplet.pos_reco[2]; + + edep[idx] = std::array{0,0,0}; + for (int i=0; i<3; i++) + edep[idx][i] = triplet.edep[i]; + + for ( auto& tid : triplet.trackids ) { + trackid[idx] = tid; + if (trackid[idx]!=-1) + break; + } + + for ( auto& xpid : triplet.pids ) { + pid[idx] = xpid; + if (pid[idx]!=-1) + break; + } + + for ( auto& xaid : triplet.aids ) { + aid[idx] = xaid; + if (aid[idx]!=-1) + break; + } + + for ( auto& xorigin : triplet.origin ) { + origin[idx] = xorigin; + if (origin[idx]!=-1) + break; + } + + uwire[idx] = triplet.imgcoord[0]; + vwire[idx] = triplet.imgcoord[1]; + ywire[idx] = triplet.imgcoord[2]; + tick[idx] = triplet.imgcoord[4]; + row[idx] = triplet.imgcoord[3]; + } + + H5Easy::dump( file, "/mcpixel_labels/pos_x", pos_x); + H5Easy::dump( file, "/mcpixel_labels/pos_y", pos_y); + H5Easy::dump( file, "/mcpixel_labels/pos_z", pos_z); + + H5Easy::dump( file, "/mcpixel_labels/pos_x_reco", pos_x_reco); + H5Easy::dump( file, "/mcpixel_labels/pos_y_reco", pos_y_reco); + H5Easy::dump( file, "/mcpixel_labels/pos_z_reco", pos_z_reco); + + H5Easy::dump( file, "/mcpixel_labels/edep", edep); + H5Easy::dump( file, "/mcpixel_labels/trackid", trackid); + H5Easy::dump( file, "/mcpixel_labels/pid", pid); + H5Easy::dump( file, "/mcpixel_labels/aid", aid); + H5Easy::dump( file, "/mcpixel_labels/origin", origin); + H5Easy::dump( file, "/mcpixel_labels/uwire", uwire); + H5Easy::dump( file, "/mcpixel_labels/vwire", vwire); + H5Easy::dump( file, "/mcpixel_labels/ywire", ywire); + H5Easy::dump( file, "/mcpixel_labels/tick", tick); + H5Easy::dump( file, "/mcpixel_labels/row", row); + + file.flush(); + +#else + LARCV_CRITICAL() << "Compiled without HDF5 support." << std::endl; +#endif + + } + + +} +} \ No newline at end of file diff --git a/ublarcvapp/MCTools/MCPixelLabelMaker.h b/ublarcvapp/MCTools/MCPixelLabelMaker.h new file mode 100644 index 0000000..280f6d5 --- /dev/null +++ b/ublarcvapp/MCTools/MCPixelLabelMaker.h @@ -0,0 +1,75 @@ +#ifndef __UBLARCVAPP_MCTOOLS_MCPIXEL_LABEL_MAKER_H__ +#define __UBLARCVAPP_MCTOOLS_MCPIXEL_LABEL_MAKER_H__ + +#include +#include +#include + +#include "larcv/core/Base/larcv_base.h" +#include "larcv/core/DataFormat/IOManager.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" + +#include "ublarcvapp/MCTools/MCParticleGraph.h" + +#include "EventMCPixelLabels.h" + +namespace ublarcvapp { +namespace mctools { + +class MCPixelLabelMaker : public larcv::larcv_base { + +public: + + MCPixelLabelMaker() + : larcv::larcv_base("MCPixelLabelMaker"), + dwire(1), + drow(1), + source("largeant"), + preverse_sce(nullptr), + psce(nullptr) + {}; + + virtual ~MCPixelLabelMaker(); + + void process( larlite::storage_manager& ioll, + larcv::IOManager& iolcv, + std::string image2d_tree_name ); + + void make_truthlabels_fromsimch( + std::string image2d_tree_name, + larlite::storage_manager& ioll, + larcv::IOManager& iolcv, + ublarcvapp::mctools::MCParticleGraph& mcpg, + larutil::SpaceChargeMicroBooNE* psce ); + + void set_dwire( int dwiremod ) { dwire=std::abs(dwiremod); }; + + void set_drow( int drowmod ) { drow=std::abs(drowmod); }; + + void set_largeant_source() { source="largeant"; }; + + void set_driftwc_source() { source="driftWC:simpleSC:Detsim"; }; + + void export_as_hdf(std::string hdf_outfile); + + void clear() { + _pixels_v.clear(); + }; + + EventMCPixelLabels _pixels_v; + + int dwire; ///< create copies of MCPixelLabels with triplet indices with modified wire index, wire+dwire + int drow; ///< create copies of MCPixelLabels with triplet indices with modified row index, row+drow + std::string source; + larutil::SpaceChargeMicroBooNE* preverse_sce; + larutil::SpaceChargeMicroBooNE* psce; + + +}; + +} +} + + +#endif \ No newline at end of file diff --git a/ublarcvapp/MCTools/MCPixelLabels.cxx b/ublarcvapp/MCTools/MCPixelLabels.cxx new file mode 100644 index 0000000..e69de29 diff --git a/ublarcvapp/MCTools/MCPixelLabels.h b/ublarcvapp/MCTools/MCPixelLabels.h new file mode 100644 index 0000000..ba24a76 --- /dev/null +++ b/ublarcvapp/MCTools/MCPixelLabels.h @@ -0,0 +1,56 @@ +#ifndef __UBLARCVAPP_MCTOOLS_MCPIXELLABELS_H__ +#define __UBLARCVAPP_MCTOOLS_MCPIXELLABELS_H__ + +#include +#include +#include + +namespace ublarcvapp { +namespace mctools { + +class MCPixelLabels { + +public: + + MCPixelLabels() + : index(-1), + imgcoord({-1,-1,-1,-1,-1}), + pos({0,0,0}), + pos_reco({0,0,0}), + edep({0.0,0.0,0.0}), + pixval({0,0,0}) + { + trackids.clear(); + aids.clear(); + pids.clear(); + origin.clear(); + }; + + ~MCPixelLabels() {}; + + long index; ///< book-keeping label + + std::array imgcoord; //< wire plane image coordinates (u,v,y,row,tick) + std::array pos; //< true energy deposition + std::array pos_reco; //< position as it appears when reco'd from wire plane images + + std::array edep; //< energy deposited as seen per wire plane + std::array pixval; //< pixel value at wire plane + + std::set trackids; //< track IDs contributing to edep position + std::set aids; //< ancestor track ID + std::set pids; //< particle ID + std::set origin; //< origin ID: 1=neutrino, 2=cosmic, 0=other + + int tick() { return imgcoord[4]; }; + int row() { return imgcoord[3]; }; + int numTrackIDs() { return trackids.size(); }; + + +}; + + +} +} + +#endif \ No newline at end of file diff --git a/ublarcvapp/MCTools/MCPixelPGraph.cxx b/ublarcvapp/MCTools/MCPixelPGraph.cxx index 22ab8a8..f25e820 100644 --- a/ublarcvapp/MCTools/MCPixelPGraph.cxx +++ b/ublarcvapp/MCTools/MCPixelPGraph.cxx @@ -1,31 +1,38 @@ #include "MCPixelPGraph.h" #include +#include // larcv #include "larcv/core/DataFormat/EventImage2D.h" #include "larcv/core/DataFormat/DataFormatTypes.h" +//#include "larcv/core/ROOTUtil/ROOTUtils.h" // larlite -#include "DataFormat/mctrack.h" -#include "DataFormat/mcshower.h" -#include "DataFormat/mctruth.h" -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctruth.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" + +// ublarcvapp +#include "ublarcvapp/dbscan/DBScan.h" #include "crossingPointsAnaMethods.h" +#include "MCPos2ImageUtils.h" namespace ublarcvapp { namespace mctools { void MCPixelPGraph::buildgraph( larcv::IOManager& iolcv, larlite::storage_manager& ioll ) { - - larcv::EventImage2D* ev_adc = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, adc_tree ); - larcv::EventImage2D* ev_seg = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "segment" ); - larcv::EventImage2D* ev_ins = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "instance" ); - larcv::EventImage2D* ev_anc = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "ancestor" ); + + ev_adc = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, adc_tree ); + ev_seg = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "segment" ); + ev_ins = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "instance" ); + ev_anc = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "ancestor" ); + ev_larflow = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, "larflow" ); if ( ev_adc->Image2DArray().size()==0 ) { throw std::runtime_error("No ADC images!"); @@ -41,15 +48,17 @@ namespace mctools { } - larlite::event_mctrack* ev_mctrack = (larlite::event_mctrack*) ioll.get_data( larlite::data::kMCTrack, "mcreco" ); - larlite::event_mcshower* ev_mcshower = (larlite::event_mcshower*)ioll.get_data( larlite::data::kMCShower, "mcreco" ); - larlite::event_mctruth* ev_mctruth = (larlite::event_mctruth*) ioll.get_data( larlite::data::kMCTruth, "generator" ); + ev_mctrack = (larlite::event_mctrack*) ioll.get_data( larlite::data::kMCTrack, "mcreco" ); + ev_mcshower = (larlite::event_mcshower*)ioll.get_data( larlite::data::kMCShower, "mcreco" ); + ev_mctruth = (larlite::event_mctruth*) ioll.get_data( larlite::data::kMCTruth, "generator" ); buildgraph( ev_adc->Image2DArray(), ev_seg->Image2DArray(), ev_ins->Image2DArray(), ev_anc->Image2DArray(), + ev_larflow->Image2DArray(), *ev_mcshower, *ev_mctrack, *ev_mctruth ); + } /** @@ -58,11 +67,14 @@ namespace mctools { */ void MCPixelPGraph::buildgraphonly( larlite::storage_manager& ioll ) { - larlite::event_mctrack* ev_mctrack = (larlite::event_mctrack*) ioll.get_data( larlite::data::kMCTrack, "mcreco" ); - larlite::event_mcshower* ev_mcshower = (larlite::event_mcshower*)ioll.get_data( larlite::data::kMCShower, "mcreco" ); - larlite::event_mctruth* ev_mctruth = (larlite::event_mctruth*) ioll.get_data( larlite::data::kMCTruth, "generator" ); + + ev_mctrack = (larlite::event_mctrack*) ioll.get_data( larlite::data::kMCTrack, "mcreco" ); + ev_mcshower = (larlite::event_mcshower*)ioll.get_data( larlite::data::kMCShower, "mcreco" ); + ev_mctruth = (larlite::event_mctruth*) ioll.get_data( larlite::data::kMCTruth, "generator" ); buildgraphonly( *ev_mcshower, *ev_mctrack, *ev_mctruth ); + + } /** @@ -73,18 +85,44 @@ namespace mctools { const std::vector& segment_v, const std::vector& instance_v, const std::vector& ancestor_v, + const std::vector& larflow_v, const larlite::event_mcshower& shower_v, const larlite::event_mctrack& track_v, const larlite::event_mctruth& mctruth_v ) { - buildgraphonly( shower_v, track_v, mctruth_v ); - - // fill the daugher to mother shower ID map - _fill_shower_daughter2mother_map( shower_v ); + clear(); + + buildgraphonly( shower_v, track_v, mctruth_v ); std::vector threshold_v(adc_v.size(),10.0); _scanPixelData( adc_v, segment_v, instance_v, ancestor_v, threshold_v ); + + // next we fix photons + for ( auto& node : node_v ) { + if ( node.pid==22 || abs(node.pid)==11 ) { + std::vector startpt_info + = fixingPhotonStartPoints( node, + instance_v, ancestor_v, + adc_v, larflow_v ); + // update edep position of the node for photons only + if ( node.pid==22 ) { + for (int v=0; v<4; v++) { + node.first_edep_pos[v] = startpt_info[v]; // set the location of the starting edep position (detectort coordinates) + node.imgpos4_edep[v] = startpt_info[4+v]; // set the location ion the image + } + } + // if ( node.origin==2 && abs(node.pid)==11 ) { + // // the cosmics timing are messed up (at least corsika samples are?) + // for (int v=0; v<4; v++) { + // node.start[v] = startpt_info[v]; // set the location of the starting edep position (detectort coordinates) + // node.first_edep_pos[v] = startpt_info[v]; // set the location of the starting edep position (detectort coordinates) + // node.imgpos4_edep[v] = startpt_info[4+v]; // set the location ion the image + // } + // } + } + } + } /** @@ -104,12 +142,40 @@ namespace mctools { // (3) (optional) get depth of each node by doing breath-first traversal // (4) sort vector pointers by depth (necessary?) + // dump mctruth info + // LARCV_DEBUG() << "MCTruth Dump" << std::endl; + // int imctruth=0; + // for ( auto& mctruth : mctruth_v ) { + // LARCV_DEBUG() << "MCTRUTH[" << imctruth << "] --------------" << std::endl; + // int imcpart = 0; + // for ( auto& part : mctruth.GetParticles() ) { + // LARCV_DEBUG() << " mcpart[" << imcpart << "] -------" << std::endl; + // LARCV_DEBUG() << " status=" << part.StatusCode() << std::endl; + // LARCV_DEBUG() << " trackid=" << part.TrackId() << std::endl; + // LARCV_DEBUG() << " pdg=" << part.PdgCode() << std::endl; + // LARCV_DEBUG() << " motherid=" << part.Mother() << std::endl; + // LARCV_DEBUG() << " process=" << part.Process() << " endprocess=" << part.EndProcess() << std::endl; + // LARCV_DEBUG() << " num daughters=" << part.Daughters().size() << std::endl; + // } + // } + + clear(); + + // fill the daugher to mother shower ID map + _fill_shower_daughter2mother_map( shower_v ); + node_v.clear(); - node_v.reserve( shower_v.size()+track_v.size() ); + node_v.reserve( shower_v.size()+track_v.size()+100 ); // Create ROOT node Node_t neutrino ( node_v.size(), -1, 0, 0, -1 ); + + std::set tid_list; + float tpc_x = larutil::Geometry::GetME()->DetHalfWidth()*2.0; + float tpc_y = larutil::Geometry::GetME()->DetHalfHeight(); + float tpc_z = larutil::Geometry::GetME()->DetLength(); + // if there is a neutrino, then we add the start position if ( mctruth_v.size()>0 ) { const larlite::mctruth& mct = mctruth_v.front(); @@ -120,12 +186,23 @@ namespace mctools { neutrino.start[2] = mct.GetNeutrino().Nu().Trajectory().front().Z(); neutrino.start[3] = mct.GetNeutrino().Nu().Trajectory().front().T(); } - node_v.emplace_back( std::move(neutrino) ); + _eventRootNode = &node_v[0]; // load spacechargemicroboone larutil::SpaceChargeMicroBooNE sce; + struct NuPart_t { + + int geantid; + int pdg; + float E_MeV; + std::vector pos; + }; + + std::vector< NuPart_t > nu_part_v; + + // collect from mcreco tracks for (int vidx=0; vidx<(int)track_v.size(); vidx++ ) { const larlite::mctrack& mct = track_v[vidx]; LARCV_DEBUG() << "track[" << vidx << "] origin=" << mct.Origin() @@ -143,25 +220,103 @@ namespace mctools { Node_t tracknode( node_v.size(), 0, mct.TrackID(), vidx, mct.PdgCode() ); tracknode.E_MeV = mct.Start().E(); tracknode.aid = mct.AncestorTrackID(); - tracknode.mtid = mct.MotherTrackID(); + tracknode.mtid = mct.MotherTrackID(); + tracknode.process = mct.Process(); if ( mct.PdgCode()==2212 ) tracknode.E_MeV -= 938.0; else if ( mct.PdgCode()==2112 ) tracknode.E_MeV -= 940.0; else if ( abs(mct.PdgCode())==13 ) tracknode.E_MeV -= 105.; else if ( abs(mct.PdgCode())==211 ) tracknode.E_MeV -= 135.; tracknode.origin = mct.Origin(); + tid_list.insert( tracknode.tid ); + // real position, time tracknode.start.resize(4); tracknode.start[0] = mct.Start().X(); tracknode.start[1] = mct.Start().Y(); tracknode.start[2] = mct.Start().Z(); tracknode.start[3] = mct.Start().T(); - _get_imgpos( tracknode.start, tracknode.imgpos4, sce ); + + if ( tracknode.origin==1 ) { + // store nu particle + NuPart_t nuparticle; + nuparticle.geantid = tracknode.tid; + nuparticle.pdg = tracknode.pid; + nuparticle.E_MeV = mct.Start().E(); + nuparticle.pos = tracknode.start; + nu_part_v.push_back( nuparticle ); + } + + + // set start position. + // try to move it into the tpc first + tracknode.first_edep_pos = std::vector(4,0); + tracknode.first_tpc_pos = std::vector(4,0); + tracknode.first_img_pos = std::vector(4,0); + tracknode.imgpos4 = std::vector(4,0); + tracknode.imgpos4_edep = std::vector(4,0); + tracknode.imgpos4_start = ublarcvapp::mctools::MCPos2ImageUtils::Get()->truepos_to_imagepos( tracknode.start[0], + tracknode.start[1], + tracknode.start[2], + tracknode.start[3], + true ); + + + if ( mct.size()>=1 ) { + tracknode.first_edep_pos[0] = mct[0].X(); + tracknode.first_edep_pos[1] = mct[0].Y(); + tracknode.first_edep_pos[2] = mct[0].Z(); + tracknode.first_edep_pos[3] = mct[0].T(); + + // first TPC point + std::vector recopos(4,0); + std::vector edep_imgpos(4,0); + std::vector xyz(4,0); + bool intpc = false; + bool inimage = false; + bool first_step = false; + for (auto const& step : mct ) { + xyz[0] = (float)step.X(); + xyz[1] = (float)step.Y(); + xyz[2] = (float)step.Z(); + xyz[3] = (float)step.T(); + + if ( !first_step ) { + first_step = true; + tracknode.first_edep_pos = xyz; + } + + if ( ( xyz[0]>0.0 && xyz[0]0 && xyz[2]truepos_to_imagepos( xyz[0], xyz[1], xyz[2], xyz[3], true ); + if ( !inimage && recopos[3]>2400.0 && recopos[3]<2400+1008*6 ) { + // in the image + inimage = true; + tracknode.first_img_pos = xyz; + tracknode.imgpos4 = recopos; + tracknode.imgpos4_edep = recopos; + } + } + if ( intpc && inimage ) { + // no need to keep searching + break; + } + }//end of mcstep loop + }//end of if more than 0 mcsteps + node_v.emplace_back( std::move(tracknode) ); } + // collect from mcshowers for (int vidx=0; vidx<(int)shower_v.size(); vidx++ ) { const larlite::mcshower& mcsh = shower_v[vidx]; @@ -170,6 +325,10 @@ namespace mctools { << " mid=" << mcsh.MotherTrackID() << " aid=" << mcsh.AncestorTrackID() << " pid=" << mcsh.PdgCode() + << " start=(" << (float)mcsh.Start().X() << "," + << (float)mcsh.Start().Y() << "," + << (float)mcsh.Start().Z() << "," + << " t=" << (float)mcsh.Start().T() << ")" << std::endl; //if ( mcsh.Origin()==1 ) { @@ -177,24 +336,198 @@ namespace mctools { Node_t showernode( node_v.size(), 1, mcsh.TrackID(), vidx, mcsh.PdgCode() ); showernode.E_MeV = mcsh.Start().E(); + showernode.process = mcsh.Process(); showernode.origin = mcsh.Origin(); - showernode.start.resize(4); + showernode.start = std::vector{ (float)mcsh.Start().X(), (float)mcsh.Start().Y(), (float)mcsh.Start().Z(), (float)mcsh.Start().T() }; showernode.aid = mcsh.AncestorTrackID(); - showernode.mtid = mcsh.MotherTrackID(); - // showernode.start[0] = mcsh.DetProfile().X(); - // showernode.start[1] = mcsh.DetProfile().Y(); - // showernode.start[2] = mcsh.DetProfile().Z(); - // showernode.start[3] = mcsh.DetProfile().T(); - showernode.start[0] = mcsh.Start().X(); - showernode.start[1] = mcsh.Start().Y(); - showernode.start[2] = mcsh.Start().Z(); - showernode.start[3] = mcsh.Start().T(); - _get_imgpos( showernode.start, showernode.imgpos4, sce ); - //showernode.imgpos4 = showernode.start; - //showernode.imgpos4[3] = 3200 + mcsh.DetProfile().X()/larutil::LArProperties::GetME()->DriftVelocity()/0.5; + showernode.mtid = mcsh.MotherTrackID(); + + showernode.first_edep_pos = std::vector(4,0); + showernode.first_tpc_pos = std::vector(4,0); + showernode.first_img_pos = std::vector(4,0); + showernode.imgpos4 = std::vector(4,0); + showernode.imgpos4_edep = std::vector(4,0); + showernode.imgpos4_start = std::vector(4,0); + + bool x_isinf = false; + if ( mcsh.DetProfile().X()>1.0e100 || mcsh.DetProfile().X()<-1.0e100 ) + x_isinf = true; + if ( !x_isinf && !std::isnan( mcsh.DetProfile().X() ) ) { + //std::cout << "inf test: " << mcsh.DetProfile().X() << " " << std::isinf( mcsh.DetProfile().X() ) << std::endl; + std::vector detprofile = { (float)mcsh.DetProfile().X(), (float)mcsh.DetProfile().Y(), (float)mcsh.DetProfile().Z(), (float)mcsh.DetProfile().T() }; + showernode.first_edep_pos = detprofile; + showernode.first_tpc_pos = detprofile; + showernode.first_img_pos = detprofile; + //_get_imgpos( detprofile, showernode.imgpos4, sce, false ); + showernode.imgpos4 = ublarcvapp::mctools::MCPos2ImageUtils::Get()->truepos_to_imagepos( showernode.first_edep_pos[0], + showernode.first_edep_pos[1], + showernode.first_edep_pos[2], + showernode.first_edep_pos[3], + true ); + if ( showernode.imgpos4.size()==0 ) + showernode.imgpos4.resize(4,0); + + if ( abs(showernode.pid)==11 ) { + showernode.imgpos4_edep = ublarcvapp::mctools::MCPos2ImageUtils::Get()->truepos_to_imagepos( showernode.start[0], + showernode.start[1], + showernode.start[2], + showernode.start[3], + true ); + if ( showernode.imgpos4_edep.size()==0 ) + showernode.imgpos4_edep.resize(4,0); + + showernode.first_edep_pos = showernode.start; + } + else { + // photons + showernode.imgpos4_edep = ublarcvapp::mctools::MCPos2ImageUtils::Get()->to_imagepos( showernode.first_edep_pos[0], + showernode.first_edep_pos[1], + showernode.first_edep_pos[2], + showernode.first_edep_pos[3] ); + if ( showernode.imgpos4_edep.size()==0 ) { + showernode.imgpos4_edep.resize(4,0); + } + else { + showernode.imgpos4_edep[3] += 12.0; // hacky fix for photons + } + } + + showernode.imgpos4_start = ublarcvapp::mctools::MCPos2ImageUtils::Get()->truepos_to_imagepos( showernode.start[0], + showernode.start[1], + showernode.start[2], + showernode.start[3], + true ); + if ( showernode.imgpos4_start.size()==0 ) { + showernode.imgpos4_start.resize(4,0); + } + } + if ( showernode.origin==1 ) { + // store nu particle + NuPart_t nuparticle; + nuparticle.geantid = showernode.tid; + nuparticle.pdg = showernode.pid; + nuparticle.E_MeV = mcsh.Start().E(); + nuparticle.pos = std::vector{ (float)mcsh.Start().X(), + (float)mcsh.Start().Y(), + (float)mcsh.Start().Z(), + (float)mcsh.Start().T() }; + nu_part_v.push_back( nuparticle ); + } + tid_list.insert( showernode.tid ); node_v.emplace_back( std::move(showernode) ); } + + // find the geant4 trackid offset for the neutrinos + long smallest_nu_tid = -1; + for ( auto& node : node_v ) { + if ( node.origin==1 ) { + if (smallest_nu_tid<0 || node.tid < smallest_nu_tid ) { + smallest_nu_tid = node.tid; + } + } + } + LARCV_DEBUG() << "Smallest neutrino Geant4 Track ID: " << smallest_nu_tid << std::endl; + + // collect from mctruth + // we need to match the earliest track or shower object to + // a genie final state particle to get offset + int ifs_1 = 0; + int matched_fs = -1; + int matched_geant_id = -1; + for ( auto& mctruth : mctruth_v ) { + for ( auto& part : mctruth.GetParticles() ) { + if ( part.StatusCode()==1 ) { + ifs_1 += 1; + // does ginal state particel match any of the geant4 particles? + for (auto& nupart : nu_part_v) { + // match pdg + if ( nupart.pdg!=part.PdgCode() ) + continue; + + // match energy + float dE_MeV = std::fabs(nupart.E_MeV-part.Momentum(0)[3]*1000.0); + LARCV_DEBUG() << "mctruth part: " << dE_MeV << std::endl; + if ( dE_MeV > 10.0 ) + continue; + + matched_fs = ifs_1; + matched_geant_id = nupart.geantid; + LARCV_DEBUG() << "have first match. " + << "genie finalstate id=" << matched_fs + << " geantid=" << matched_geant_id + << std::endl; + break; + } + + }//end of if status code + if (matched_fs>=0) + break; + }//end of mcpart loop + if (matched_fs>=0) + break; + }//end of mctruth loop + + int geantid_offset = smallest_nu_tid - (matched_fs-1); + LARCV_INFO() << "geantid offset: " << geantid_offset << " smallest_nu_tid=" << smallest_nu_tid << std::endl; + LARCV_INFO() << " matched_fs=" << matched_fs << " matched_geant4=" << matched_geant_id << std::endl; + + int imctruth=0; + int ifs = 0; + for ( auto& mctruth : mctruth_v ) { + int imcpart = 0; + for ( auto& part : mctruth.GetParticles() ) { + LARCV_DEBUG() << " mcpart[" << imctruth << "," << imcpart << "] -------" << std::endl; + LARCV_DEBUG() << " status=" << part.StatusCode() << std::endl; + LARCV_DEBUG() << " trackid=" << part.TrackId() << std::endl; + LARCV_DEBUG() << " pdg=" << part.PdgCode() << std::endl; + LARCV_DEBUG() << " motherid=" << part.Mother() << std::endl; + LARCV_DEBUG() << " process=" << part.Process() << " endprocess=" << part.EndProcess() << std::endl; + LARCV_DEBUG() << " num daughters=" << part.Daughters().size() << std::endl; + + if ( part.StatusCode()==1 ) { + int geant_trackid = geantid_offset + ifs; + LARCV_DEBUG() << " Stable Final State. Implied Geant4 ID = " << geant_trackid << std::endl; + ifs++; + + auto it_tid = tid_list.find(geant_trackid); + if (it_tid==tid_list.end() ) { + int nodeidx = node_v.size(); + int type = 3; // genie-final-state + int tid = geant_trackid; + int vidx = imcpart; + int pid = part.PdgCode(); + Node_t* mother = nullptr; + int mid = -1; + float energy = part.Momentum(0)[3]*1e3 - part.Mass()*1.0e3; // in GeV, convert to MeV + std::vector start { (float)part.Position(0)[0], + (float)part.Position(0)[1], + (float)part.Position(0)[2], + (float)part.Position(0)[3]}; + std::vector imgpos4(4,0); + _get_imgpos( start, imgpos4, sce, true ); + + //LARCV_DEBUG() << "Creating Mother node from GENIE final states: tid=" << tid << " type=" << 3 << std::endl; + Node_t fsnode( nodeidx, type, tid, vidx, pid, mother, mid, energy, "primary" ); + fsnode.start = start; + fsnode.imgpos4 = imgpos4; + fsnode.mtid = tid; + fsnode.aid = tid; + fsnode.origin = 1; // neutrino origin (from genie) + LARCV_DEBUG() << "Add Genie Final State Particle to initial List: tid=" << tid << " pdg=" << pid << std::endl; + node_v.emplace_back( std::move(fsnode) ); + } + else { + LARCV_DEBUG() << "Genie Final State Partial [tid=" << geant_trackid << "] Already in MCReco List" << std::endl; + } + } + imcpart++; + } + imctruth++; + } + + // try to connect primary neutrino origin nodes to mctruth info + //_adoptNeutrinoOrphans( &mctruth_v ); // sort Node_t object by geant track ID, relabel node IDs std::sort( node_v.begin(), node_v.end() ); @@ -245,6 +578,14 @@ namespace mctools { } } } + else if (node.type==3) { + // genie fs nodes + // connet to ROOT node as they are primary by definition + mothernode = &(node_v[0]); + } + else { + continue; + } if (mothernode) { // found mother, connect @@ -257,13 +598,92 @@ namespace mctools { }//end of node loop + if ( _cluster_neutrino_particles ) { + int nnu = _define_neutrino_interaction_nodes( track_v, shower_v ); + LARCV_INFO() << "Rearranged graph to include neutrino vertex nodes. Number of Nu Interactions: " << nnu << std::endl; + } //printAllNodeInfo(); //printGraph(); } /** - * locate Node_t in node_v using trackid (from geant4) + * @brief wrapper function to retrieve Node's corresponding mctrack object from larlite container + */ + const larlite::mctrack& + MCPixelPGraph::_retrieve_mctrackobject( const Node_t* node, + const larlite::event_mctrack& ev_track_v ) + { + try { + const larlite::mctrack& x = ev_track_v.at(node->vidx); + // to do: check that truth info matches? + return x; + } + catch ( std::exception& ex ) { + std::stringstream err; + err << "Error accessing event_mctrack container at index=" << node->vidx << " for node[idx]=nodeidx" << std::endl; + err << "Node info: " << std::endl; + err << strNodeInfo( *node ) << std::endl; + err << "Exception: " << ex.what() << std::endl; + throw std::runtime_error( err.str() ); + } + } + + /** + * @brief wrapper function to retrieve Node's corresponding mcshower object from larlite container + */ + const larlite::mcshower& MCPixelPGraph::_retrieve_mcshowerobject( const Node_t* node, + const larlite::event_mcshower& ev_shower_v ) + { + try { + const larlite::mcshower& x = ev_shower_v.at(node->vidx); + // to do: check that truth info matches? + return x; + } + catch ( std::exception& ex ) { + std::stringstream err; + err << "Error accessing event_mcshower container at index=" << node->vidx << " for node[idx]=nodeidx" << std::endl; + err << "Node info: " << std::endl; + err << strNodeInfo( *node ) << std::endl; + err << "Exception: " << ex.what() << std::endl; + throw std::runtime_error( err.str() ); + } + } + + /** + * @brief convenience function to retrieve Node's corresponding mctrack object from larlite top-level io interface + */ + const larlite::mctrack& MCPixelPGraph::_retrieve_mctrackobject( const Node_t* node, + larlite::storage_manager& ioll, + std::string producername ) + { + const larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack, producername ); + + return _retrieve_mctrackobject( node, *ev_mctrack ); + } + + /** + * @brief convenience function to retrieve Node's corresponding mcshower object from larlite top-level io interface + */ + const larlite::mcshower& MCPixelPGraph::_retrieve_mcshowerobject( const Node_t* node, + larlite::storage_manager& ioll, + std::string producername ) + { + const larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower, producername ); + + return _retrieve_mcshowerobject( node, *ev_mcshower ); + } + + + /** + * @brief locate Node_t in node_v using trackid (from geant4) + * + * we first search our vector of Node_t objects, which are sorted by track id. + * + * we then search the keys of the _shower_daughter2mother map, where the keys + * are track id of particles made by the initial shower. * * @return The node if found, nullptr if not found * @@ -277,17 +697,17 @@ namespace mctools { auto it_showerdaughter = _shower_daughter2mother.find( trackid ); if ( it_showerdaughter!=_shower_daughter2mother.end() ) { // found an id - LARCV_DEBUG() << " found map to mother: " << it_showerdaughter->second << std::endl; + //LARCV_DEBUG() << " found map to mother: " << it_showerdaughter->second << std::endl; dummy.tid = it_showerdaughter->second; } else { // still nope return nullptr; } - // try again + // with the mother shower's trackid, try to find the node again it = std::lower_bound( node_v.begin(), node_v.end(), dummy ); - if ( it!=node_v.end() ) - LARCV_DEBUG() << " mother id maps to existing node" << std::endl; + //if ( it!=node_v.end() ) + // LARCV_DEBUG() << " mother id maps to existing node" << std::endl; } if ( it==node_v.end() || it->tid!=dummy.tid ) { @@ -313,29 +733,67 @@ namespace mctools { * @param[in] node Note_t object to make info for. * */ - std::string MCPixelPGraph::strNodeInfo( const Node_t& node ) { + std::string MCPixelPGraph::strNodeInfo( const Node_t& node ) + { + + int hasmother = ( node.mother ) ? 1 : 0; + std::stringstream ss; //ss << "node[" << node.nodeidx << "," << &node << "] " ss << "node[" << node.nodeidx << "] " << " (type,vidx)=(" << node.type << "," << node.vidx << ") " << " origin=" << node.origin + << " p=" << node.process << " tid=" << node.tid - << " mid=" << node.mtid + << " mtid=" << node.mtid << " aid=" << node.aid << " pdg=" << node.pid << " KE=" << node.E_MeV << " MeV" - //<< " start=(" << node.start[0] << "," << node.start[1] << "," << node.start[2] << "," << node.start[3] << ")" - << " imgpos=(" << node.imgpos4[0] << "," << node.imgpos4[1] << "," << node.imgpos4[2] << "," << node.imgpos4[3] << ")" + //<< " xyzt= + //<< " tusec=" << node.start[3]*1.0e-3 << " us" //<< " (mid,mother)=(" << node.mid << "," << node.mother << ") " //<< " (mid,mother)=(" << node.mid << ") " + << " hasmother=" << hasmother << " ndaughters=" << node.daughter_v.size() - << " npixs=("; + << std::endl; + // additional positional info + if ( node.start.size()>=4 ) + ss << " start(x,y,z,t)=(" << node.start[0] << "," << node.start[1] << "," << node.start[2] << "," << node.start[3]*1.0e-3 << " us) " << std::endl; + + if ( node.first_edep_pos.size()>=4 ) + ss << " edep-tpcpos(x,y,z,t)=(" << node.first_edep_pos[0] << "," + << node.first_edep_pos[1] << "," + << node.first_edep_pos[2] << "," + << node.first_edep_pos[3]*1.0e-3 + << " us)" << std::endl; + + if ( node.imgpos4.size()>=4 ) + ss << " tpc-imgpos4(u,v,y,tick)=(" << node.imgpos4[0] << "," + << node.imgpos4[1] << "," + << node.imgpos4[2] << "," + << node.imgpos4[3] << " tick) " << std::endl; + + if (node.imgpos4_edep.size()>=4) + ss << " edep-imgpos4(u,v,y,tick)=(" << node.imgpos4_edep[0] << "," + << node.imgpos4_edep[1] << "," + << node.imgpos4_edep[2] << "," + << node.imgpos4_edep[3] << " tick) " + << std::endl; + + ss << " npixs=("; for ( size_t i=0; i0 ) branch += "-- "; + if ( visible_only ) { int nvis = 0; for ( auto const& pix_v : node->pix_vv ) @@ -397,7 +856,7 @@ namespace mctools { } /** - * internal method to scan the adc and truth images and fill pixel locations in the Node_t objects + * @brief scan the adc and truth images and associate them with the different particle Node_t * */ void MCPixelPGraph::_scanPixelData( const std::vector& adc_v, @@ -413,8 +872,11 @@ namespace mctools { // loop through nodes and setup pixel arrays for (auto& node : node_v ) { node.pix_vv.resize(_nplanes); + node.pixval_vv.resize(_nplanes); + node.pixsum_v.resize(_nplanes,0.0); for ( size_t p=0; p<_nplanes; p++ ) { node.pix_vv[p].clear(); + node.pixval_vv[p].clear(); } } @@ -431,6 +893,7 @@ namespace mctools { for ( size_t p=0; p<_nplanes; p++ ) { _unassigned_pixels_vv[p].clear(); + int num_neg_shower_ids = 0; auto const& meta = adc_v[p].meta(); const float threshold = threshold_v[p]; @@ -448,10 +911,11 @@ namespace mctools { std::cerr << __FILE__ << ":L" << __LINE__ << " error getting ADC pixel (" << r << "," << c << ")" << std::endl; continue; } - if ( adc=threshold ) + nabove_thresh[p]++; // above threshold, now lets find instance or ancestor int tid = 0; @@ -480,36 +944,52 @@ namespace mctools { continue; } - if ( tid<0 && (seg==(int)larcv::kROIEminus || seg==(int)larcv::kROIGamma) ) + // shower pixels have a negative track ID for some reason + // does this always occur? + if ( tid<0 && (seg==(int)larcv::kROIEminus || seg==(int)larcv::kROIGamma) ) { tid *= -1; + num_neg_shower_ids++; + } - if ( tid>0 || aid>0 ) - nabove_thresh_withlabel[p]++; - - if ( seg==(int)larcv::kROIEminus || seg==(int)larcv::kROIGamma ) { + bool has_id_label = false; + if ( tid>0 || aid>0 ) { + nabove_thresh_withlabel[p]++; + has_id_label = true; + } + + if ( aid>0 && (seg==(int)larcv::kROIEminus || seg==(int)larcv::kROIGamma) ) { shower_ancestor_ids.insert( aid ); } + if ( !has_id_label ) { + // can't truth associate this pixel to any node + continue; + } + Node_t* node = nullptr; if ( tid>0 ) { - // first we use the instance ID + // first we use the instance ID + // this implicitly uses the _shower_daughter2mother map we filled earlier. node = findTrackID( tid ); if ( node==nullptr && adc>10.0 ) - LARCV_DEBUG() << " no node for charge-pixel tid=" << tid << std::endl; + LARCV_DEBUG() << " no node for above threshold charge-pixel tid=" << tid << std::endl; // if ( node && node->tid!=tid ) // node = nullptr; // reset (what's this?) } // use ancestor if we could not find the node if ( !node && aid>0 ) { + // this implicitly uses the _shower_daughter2mother map we filled earlier. node = findTrackID( aid ); if ( node && node->tid!=aid ) node = nullptr; } if ( node ) { - + // if the address is not null, this means we found a particle node + // store the pixel. + // if ( node->tid!=tid && node->aid!=aid ) { // std::cout << "pixel assigned without matching tid or aid exactly: " // << " pix-tid=" << tid << " pix-aid=" << aid @@ -520,6 +1000,8 @@ namespace mctools { nassigned[p]++; node->pix_vv[p].push_back( tick ); node->pix_vv[p].push_back( wire ); + node->pixval_vv[p].push_back( adc ); + node->pixsum_v[p] += adc; } else { _unassigned_pixels_vv[p].push_back( tick ); @@ -531,7 +1013,7 @@ namespace mctools { }//end of loop over planes - // no make bounding boxes + // now make bounding boxes for ( auto& node : node_v ) { node.plane_bbox_twHW_vv.clear(); node.plane_bbox_twHW_vv.resize(_nplanes); @@ -583,7 +1065,7 @@ namespace mctools { } /** - * get pixels associated with node and its descendents + * @brief get pixels associated with node and its descendents * */ std::vector< std::vector > MCPixelPGraph::getPixelsFromParticleAndDaughters( int trackid ) { @@ -697,7 +1179,8 @@ namespace mctools { */ void MCPixelPGraph::_get_imgpos( std::vector& realpos4, std::vector& imgpos4, - larutil::SpaceChargeMicroBooNE& sce ) + larutil::SpaceChargeMicroBooNE& sce, + bool apply_sce ) { imgpos4.resize(4,0); @@ -707,17 +1190,25 @@ namespace mctools { for (int i=0; i<3; i++) { dpos[i] = realpos4[i]; } + + std::vector txyz(4,0); + if ( apply_sce ) { + std::vector offset = sce.GetPosOffsets( dpos[0], dpos[1], dpos[2] ); + dpos[0] = dpos[0] - offset[0] + 0.7; + dpos[1] = dpos[1] + offset[1]; + dpos[2] = dpos[2] + offset[2]; + } - std::vector offset = sce.GetPosOffsets( dpos[0], dpos[1], dpos[2] ); - std::vector txyz(4,0); - dpos[0] = dpos[0] - offset[0] + 0.7; - dpos[1] = dpos[1] + offset[1]; - dpos[2] = dpos[2] + offset[2]; for (int i=0; i<3; i++) { txyz[1+i] = realpos4[i]; } txyz[0] = realpos4[3]; - float tick = CrossingPointsAnaMethods::getTick( txyz, 4050.0, &sce ); + float tick = 0.; + if (apply_sce) + tick = CrossingPointsAnaMethods::getTick( txyz, 4050.0, &sce ); + else + tick = CrossingPointsAnaMethods::getTick( txyz, 4050.0, NULL ); + for (int i=0; i<3; i++) { imgpos4[i] = dpos[i]; } @@ -728,6 +1219,16 @@ namespace mctools { } + /** + * @brief makes a map from daughter track IDs to their mother (shower) track ID + * + * The info comes from the MCShowerObjecs themselves. They store daughter IDs + * in a vector accessed by mcshower::DaughterTrackID(). + * + * This map will let us map the track ids stored in each pixel of the + * the instance ID map to its original shower. + * + */ void MCPixelPGraph::_fill_shower_daughter2mother_map( const std::vector& mcsh_v ) { LARCV_DEBUG() << "daughter2mother fill" << std::endl; @@ -740,11 +1241,1354 @@ namespace mctools { std::sort( dlist.begin(), dlist.end() ); for (auto const& daughterid : dlist ) { _shower_daughter2mother[daughterid]= showerid; - LARCV_DEBUG() << " " << daughterid << " -> " << showerid << std::endl; + //LARCV_DEBUG() << " " << daughterid << " -> " << showerid << std::endl; } } LARCV_INFO() << "Num entries in daughter2mother map: " << _shower_daughter2mother.size() << std::endl; } + + int MCPixelPGraph::_define_neutrino_interaction_nodes( larlite::storage_manager& ioll ) + { + larlite::event_mctrack* ev_mctrack + = (larlite::event_mctrack*)ioll.get_data(larlite::data::kMCTrack,"mcreco"); + larlite::event_mcshower* ev_mcshower + = (larlite::event_mcshower*)ioll.get_data(larlite::data::kMCShower,"mcreco"); + + return _define_neutrino_interaction_nodes( *ev_mctrack, *ev_mcshower ); + } + + int MCPixelPGraph::_define_neutrino_interaction_nodes( const larlite::event_mctrack& ev_mctrack, + const larlite::event_mcshower& ev_mcshower ) + { + // first we collect nodes with neutrino origin + bool exclude_neutrons = false; + std::vector< Node_t* > _nu_primary_v = getNeutrinoPrimaryParticles( exclude_neutrons ); + LARCV_DEBUG() << "Number of nu primaries returned: " << _nu_primary_v.size() << std::endl; + + // if we have access to MCTruth info, we should use it to define vertex locations. + // otherwise we use some distance threshold. 0.3 mm, the pitch length? + std::map< int, std::set > _nu_collected_primaries_v; + _nu_vertices_v.clear(); + + + for ( auto& pnode : _nu_primary_v ) { + // we test the vertex for every primary + LARCV_DEBUG() << "Considering Nu Primary node[" << pnode->nodeidx << "] tid=" << pnode->tid << std::endl; + + std::vector start = {0,0,0,0}; + + if ( pnode->isTrackObject() ) { + auto const& track = _retrieve_mctrackobject( pnode, ev_mctrack ); + for (int i=0; i<4; i++) { + start[i] = track.Start().Position()[i]; + } + } + else if (pnode->isShowerObject()) { + auto const& shower = _retrieve_mcshowerobject( pnode, ev_mcshower ); + for (int i=0; i<4; i++) { + start[i] = shower.Start().Position()[i]; // creation point in geant4, for photon, not the same as conversion point where visible EM cascade begins + } + } + else if (pnode->isGenieFinalStateObject()) { + start = pnode->start; + } + else { + LARCV_DEBUG() << "Node Primary neither a shower nor track object: " << pnode->nodeidx << std::endl; + LARCV_DEBUG() << "node: " << strNodeInfo( *pnode ) << std::endl; + continue; + } + + bool found_vertex_match = false; + int idx_matched_vertex = -1; + float closest_match = 1e9; + std::vector pos = start; + + LARCV_DEBUG() << "Nu primary start: " << pos[0] << " " << pos[1] << " " << pos[2] << " " << pos[3] << std::endl; + + for ( int idx_vertex=0; idx_vertex<(int)_nu_vertices_v.size(); idx_vertex++ ) { + + auto& vertex = _nu_vertices_v.at(idx_vertex); + + // distance to existing vertex + float dist = 0.; + for (int i=0; i<3; i++) { + dist += ( pos[i]-vertex[i] )*( pos[i]-vertex[i] ); + } + dist = sqrt(dist); + + // update closest match + if ( dist < closest_match ) { + idx_matched_vertex = idx_vertex; + closest_match = dist; + + // qualifies as found? + if ( dist < _kNuVertexDistCutoff_cm ) { + found_vertex_match = true; + idx_matched_vertex = idx_vertex; + } + } + + }//end of loop over established nu vertices + LARCV_DEBUG() << "result of vertex search: closest=" << closest_match << " idx_matched=" << idx_matched_vertex << " found_match=" << found_vertex_match << std::endl; + + if ( !found_vertex_match ) { + // new neutrino vertex defined using position + LARCV_DEBUG() << "New vertex defined." << std::endl; + std::vector new_vertex = { (float)pos[0], (float)pos[1], (float)pos[2], (float)pos[3] }; + _nu_vertices_v.push_back( new_vertex ); + + int nu_vertex_id = (int)_nu_vertices_v.size()-1; // using position in _nu_vertices as an id number (should use a struct I know) + std::set nu_primary_set; + nu_primary_set.insert( pnode->nodeidx ); // add node index + _nu_collected_primaries_v[ nu_vertex_id ] = nu_primary_set; + } + else { + // found match + LARCV_DEBUG() << "Matched primary to existing vertex. IDX=" << idx_matched_vertex << std::endl; + auto it=_nu_collected_primaries_v.find( idx_matched_vertex ); + it->second.insert( pnode->nodeidx ); + } + + }//end of loop over neutrino primaries + + LARCV_DEBUG() << "Number of vertices defined: " << _nu_vertices_v.size() << std::endl; + + if ( _nu_vertices_v.size()==0 ) + return 0; + + // now that we have nu vertices, we need to define a new neutrino ancestor ID, then relabel ancestor IDs for daughters + long max_geant4_trackid = -1; + if ( _nu_vertices_v.size()>0 ) { + for (auto pnode : node_v) { + if ( pnode.tid>max_geant4_trackid ) { + max_geant4_trackid = pnode.tid; + } + } + } + LARCV_DEBUG() << "Starting with max trackid=" << max_geant4_trackid << " to assign nu vertex nodes" << std::endl; + std::set nu_attached_v; // gather list of indices that have been attached to neutrinos + std::vector< Node_t* > nu_pnode_v; + + for (int inuvtx=0; inuvtx<(int)_nu_vertices_v.size(); inuvtx++) { + + LARCV_DEBUG() << "Build nu vertex node and assign daughters. [NU VTX IDX=" << inuvtx << "]" << std::endl; + + // get the next node index + int nu_node_idx = (int)node_v.size(); + // need an acceptable fake trackID + long fake_trackid = max_geant4_trackid+1; + max_geant4_trackid++; + int type_id = 2; + int pid = -1; + int mtid = fake_trackid; + float energy = 0.0; + std::string proc = "nuvertex"; + Node_t nu_node( nu_node_idx, type_id, fake_trackid, inuvtx, + pid, _eventRootNode, + mtid, energy, proc ); + + nu_node.origin = 1; + nu_node.aid = fake_trackid; + nu_node.mtid = -1; + + // insert into node vector + node_v.emplace_back( std::move(nu_node) ); + + // get pointer + Node_t* pnu_node = &(node_v.at(nu_node_idx)); + nu_pnode_v.push_back( pnu_node ); + + // now we need to + // (1) change the nu primaries associated to this interaction + // to list their mother node to this node representing the neutrino interaction + // (2) relabel the ancestor ID of all primary neutrinos to this ID + auto it_prim = _nu_collected_primaries_v.find( inuvtx ); + if ( it_prim==_nu_collected_primaries_v.end() ) + continue; + + for ( auto& nodeidx : it_prim->second ) { + + LARCV_DEBUG() << "Add primary, node=" << nodeidx << ", to nu vertex[" << inuvtx << "]" << std::endl; + + LARCV_DEBUG() << " reassign mother to nu vertex pnode=" << pnu_node << std::endl; + Node_t* pnode_nuprim = &(node_v.at(nodeidx)); + pnode_nuprim->mother = pnu_node; + + // collect daughters + std::vector prim_daughters = getNodeAndDescendentsFromTrackID( pnode_nuprim->tid ); + LARCV_DEBUG() << "Collect descendents of nu primary node[ " << nodeidx << "] ndaughters=" << (int)prim_daughters.size()-1 << std::endl; + + // reset the ancestor id of all of these nodes to the new fake track ID for nu interaction + // note: the function above returns the node of the starting track id + for ( auto& pdnode : prim_daughters ) { + pdnode->aid = (int)fake_trackid; + } + + // add this primary to the daughter list of the nu node + pnu_node->daughter_idx_v.push_back( pnode_nuprim->nodeidx ); + pnu_node->daughter_v.push_back( pnode_nuprim ); + LARCV_DEBUG() << "Add to NuVertex node list of daughters: now " << pnu_node->daughter_v.size() << std::endl; + nu_attached_v.insert( pnode_nuprim->nodeidx ); + + // add its E + pnu_node->E_MeV += pnode_nuprim->E_MeV; + LARCV_DEBUG() << "Add to NuVertex energy: now " << pnu_node->E_MeV << " MeV" << std::endl; + }//end of loop over primary node + + nu_attached_v.insert( nu_node_idx ); + + }//end of loop over newly creatd neutrino vertices + + // now we have to redefine the root node's connections + _eventRootNode = &(node_v[0]); + std::vector all_prim_v = getPrimaryParticles(exclude_neutrons); + + _eventRootNode->daughter_idx_v.clear(); + _eventRootNode->daughter_v.clear(); + + + for (auto& pnode : all_prim_v ) { + auto it_attached = nu_attached_v.find( pnode->nodeidx ); + if ( it_attached==nu_attached_v.end() ) { + // not attached to neutrino vertex, so add to root node + _eventRootNode->daughter_idx_v.push_back( pnode->nodeidx ); + _eventRootNode->daughter_v.push_back( pnode ); + } + } + + // attach the nu vertex nodes + for (auto& pnode : nu_pnode_v ) { + _eventRootNode->daughter_idx_v.push_back( pnode->nodeidx ); + _eventRootNode->daughter_v.push_back( pnode ); + } + + return _nu_vertices_v.size(); + } + + void MCPixelPGraph::clear() + { + + _unassigned_pixels_vv.clear(); + node_v.clear(); + + _eventRootNode = nullptr; + _nu_vertices_v.clear(); + _shower_daughter2mother.clear(); + //_map_trackid_to_nu_ancestor_v.clear(); + + _nodeidx_to_photonlist_index_v.clear(); + _true_photon_v.clear(); + _true_photon_plane_trunkimg_vv.clear(); + _true_photon_plane_pixset_vv.clear(); + _true_photon_plane_pixsum_vv.clear(); + + _empty_photon_pointlist_v.clear(); + _empty_pixsum_v.clear(); + + // ev_adc = nullptr; + // ev_seg = nullptr; + // ev_ins = nullptr; + // ev_anc = nullptr; + + } + + void MCPixelPGraph::_adoptNeutrinoOrphans( const larlite::event_mctruth* ev_mctruth ) + { + // we scan our nodes without mother nodes that come from neutrinos (origin=1) + // if we have mctruth, we look to find mother. then we make new mother node and finish graph + + std::map > final_state_map; + int ifs = 0; + if ( ev_mctruth ) { + int imctruth=0; + for ( auto const& mctruth : *ev_mctruth ) { + //LARCV_DEBUG() << "MCTRUTH[" << imctruth << "] --------------" << std::endl; + int imcpart = 0; + for ( auto& part : mctruth.GetParticles() ) { + if ( part.StatusCode()==1 ) { + int geant_trackid = ifs+1; + ifs++; + std::vector index = {imctruth,imcpart}; + final_state_map[geant_trackid] = index; + } + imcpart++; + } + imctruth++; + }//end of mctruth map + }//end of if have mctruth + + std::set tid_list; + for ( auto& node : node_v ) + tid_list.insert(node.tid); + + std::map< int, int > newmom_tid_to_nodeidx; + + for ( auto& node : node_v ) { + // has mother or is good neutrino mother + if ( node.origin!=1 ) { + continue; + } + + // check if the mother node is not already in the node list + auto it_tid = tid_list.find( node.mtid ); + if ( it_tid!=tid_list.end() ) { + // has a mother in the list, no need to gather one from the mctruth or create one + continue; + } + + // check if we might have a mother now + auto it_newmom = newmom_tid_to_nodeidx.find( node.mtid ); + if ( it_newmom==newmom_tid_to_nodeidx.end() ) { + // try ancestor id + it_newmom = newmom_tid_to_nodeidx.find( node.aid ); + } + + if ( it_newmom!=newmom_tid_to_nodeidx.end() ) { + // connected to a new mom! + //int momtid = it_newmom->first; + int momidx = it_newmom->second; + auto& momnode = node_v.at(momidx); + //momnode.daughter_v.push_back( &node ); + //momnode.daughter_idx_v.push_back( node.nodeidx ); + //node.mother = &momnode; + LARCV_DEBUG() << "Connected primary orphan to a newly created mom! New mom tid=" << momnode.tid << std::endl; + continue; + } + + LARCV_DEBUG() << "node[" << node.nodeidx << "] with mtid=" << node.mtid << " looking to find mom or create it" << std::endl; + + bool created_mother = false; + if ( ev_mctruth ) { + // if we have the mctruth, try to look for missing mother id + std::vector momindex; + int mom_tid = -1; + + auto it_fs = final_state_map.find( node.mtid ); + if ( it_fs!=final_state_map.end() ) { + // has mother, create a node + momindex = it_fs->second; + mom_tid = node.mid; + } + else { + // try using ancestor + auto it_fsa = final_state_map.find( node.aid ); + auto it_tid2 = tid_list.find( node.aid ); + // create a mother if (1) found in final state list AND (2) not found in tid_list (i.e. existing node list) + if ( it_fsa!=final_state_map.end() && it_tid2==tid_list.end() ) { + momindex = it_fsa->second; + mom_tid = node.aid; + } + } + + if ( mom_tid<1 ) + continue; + + LARCV_DEBUG() << "Creating mother node from Genie FSI info. mom_tid=" << mom_tid << std::endl; + std::cin.get(); + + if ( momindex.size()==2 ) { + // create a mother node from mcparticle info + const larlite::mcpart& part = ev_mctruth->at(momindex[0]).GetParticle( momindex[1] ); + int nodeidx = node_v.size(); + int type = 3; // genie-final-state + int tid = mom_tid; + int vidx = momindex[1]; + int pid = part.PdgCode(); + Node_t* mother = &(node_v[0]); // the root node + int mid = 0; + int energy = part.Momentum(0)[3]; + std::vector start { (float)part.Position(0)[0], + (float)part.Position(0)[1], + (float)part.Position(0)[2], + (float)part.Position(0)[3]}; + LARCV_DEBUG() << "Creating Mother node from GENIE final states: tid=" << tid << " type=" << 3 << std::endl; + Node_t momnode( nodeidx, type, tid, vidx, pid, mother, mid, energy, "primary" ); + momnode.start = start; + momnode.imgpos4 = start; + momnode.mtid = tid; + momnode.aid = tid; + momnode.origin = 1; // neutrino origin (from genie) + + //momnode.daughter_v.push_back( &node ); + //momnode.daughter_idx_v.push_back( node.nodeidx ); + + newmom_tid_to_nodeidx[momnode.tid] = momnode.nodeidx; + tid_list.insert(tid); + + node_v.push_back( std::move(momnode) ); + + //node.mother = &(node_v.back()); + + // add new mom to root node + //Node_t& rootnode = node_v.front(); + //rootnode.daughter_v.push_back( &(node_v.back()) ); + //rootnode.daughter_idx_v.push_back( node_v.back().nodeidx ); + + created_mother = true; + + } + else { + LARCV_DEBUG() << "No mother created for this." << std::endl; + } + }//end of if has ev_mctruth + + if ( created_mother ) + continue; + + }//end of loop over nodes + + }//end of adopt orphans + + // std::vector MCPixelPGraph::makeTH2D( std::string hist_stem_name ) + // { + // // first get wire image into th2d + // std::vector< TH2D > hist_v; + // if ( ev_adc==nullptr ) + // return hist_v; + + // for ( auto& pmarker : vis_markers_v ) { + // delete pmarker; + // }; + // vis_markers_v.clear(); + + // for ( auto& plabel : vis_label_v ) { + // delete plabel; + // } + // vis_label_v.clear(); + + // if ( !cvis ) { + // cvis = new TCanvas("cmcpg","MCPixel PGraph Canvas",800,2400); + // cvis->Divide(1,3); + // } + + // hist_v = larcv::rootutils::as_th2d_v( ev_adc->as_vector(), hist_stem_name ); + + // // label the nodes + // for (auto& node : node_v ) { + + // for (int p=0; p<(int)hist_v.size(); p++) { + // cvis->cd(p+1); + // auto& hist = hist_v.at(p); + // //auto& meta = ev_adc->as_vector().at(p).meta(); + // std::cout << "Draw marker for node[" << node.nodeidx << "]-plane[" << p << "] wire=" << node.imgpos4_edep[p] << " tick=" << node.imgpos4_edep[3] << std::endl; + // TMarker* m = new TMarker( node.imgpos4_edep[p], node.imgpos4_edep[3], 20 ); + // //m->SetMarkerSize(3); + // m->SetMarkerColor(kMagenta); + // hist.Draw("colz"); + // m->Draw(); + // vis_markers_v.push_back( m ); + // } + + // } + // cvis->Update(); + // //std::cout << "[enter] to continue" << std::endl; + // //std::cin.get(); + // return hist_v; + // } + + /** + * @brief use larflow truth to make 3D spacepoints for a given particle + * + * we return all points formed by the intersections + * + * @return a vector of 3d positions + * + */ + std::vector< std::vector > + MCPixelPGraph::makeNode3DpointsFromLArFlowTruth( Node_t& node, + const std::vector& adc_v, + const std::vector& larflow_v ) + { + + // we want to make 3d points from the pixels we have for the shower. + std::vector< std::vector > pos_vv; + + // we use the plane-to-plane "flow" map to define 3d spacepoints + struct flowpt { + int source_plane; + int source_wire; + int target_plane; + int target_wire; + int source_trackid; + int target_trackid; + float src_pixval; + float pos[3]; + int posid[3]; + bool operator<( const flowpt& rhs ) const { + if (posid[0]rhs.posid[0]) + return false; + // neither so [0] must be equal + + if (posid[1]rhs.posid[1]) + return false; + + // neither [1] worked, so must be equal + if (posid[2] larfow image with index-0 (out of 6) contains the map from U pixels [0] mapped to pixels in the V plane (=1) + // targetmap[0][1] = 2 --> larfow image with index-1 (out of 6) contains the map from U pixels [0] mapped to pixels in the Y plane (=2) + // targetmap[1][0] = 0 --> larfow image with index-2 (out of 6) contains the map from V pixels [1] mapped to pixels in the U plane (=0) + // .. etc .. + int targetmap[3][2] = { {1,2}, + {0,2}, + {0,1} }; + + // std::cout << "adc images: " << adc_v.size() << std::endl; + // std::cout << "larflow images: " << larflow_v.size() << std::endl; + + std::set< flowpt > pt_v; + + // we loop over the pixels in the wire plane image that has been + // associated to this particle node + for (size_t p=0; p{ pt_info.pos[0], pt_info.pos[1], pt_info.pos[2] } ); + } + + return pos_vv; + } + + /** + * @brief Fix the location of where the start starts depositing energy + * + */ + std::vector MCPixelPGraph::fixingPhotonStartPoints( Node_t& node, + const std::vector& instance_v, + const std::vector& ancestor_v, + const std::vector& adc_v, + const std::vector& larflow_v ) + { + + // start of photon shower is not something accurately specified in the mcreco + // products. detprofile is provided in mcshower -- but i think its basically trash. + // instead we will use the instanceid + larflow truth to + // (1) build 3d spacepoints + // (2) find the closest spacepoint with evidence for ~MIP level deposits + // (3) place the position there + // the profile also provides a momentum, but I bet its garbage + + // this is only for photons (electrons have a good start point based on node.start) + std::vector shower_start_pt; + + if ( node.pid!=22 && abs(node.pid)!=11) { + // we only apply the following to energy deposits made by photons (pid=22) and electrons (pid=11) + // i.e. those producing EM showers + return shower_start_pt; + } + LARCV_INFO() << "================================================================" << std::endl; + LARCV_INFO() << " START " << std::endl; + + // we must collect and scan the instance IDs related to the shower + // int shower_aid = node.aid; + // int shower_mid = node.mid; + // int shower_tid = node.tid; + + // created 3d points of energy deposition from truth information saved in + // the larflow images. The larflow images encode 2D wire-intersections + // which provide (Y,Z) positions in the detector. The X position is + // inferred by the drift time relative to the beam trigger (at tick 3200). + // We only build 3d points coming from pixels associated with the node. + pointList pos_vv = makeNode3DpointsFromLArFlowTruth( node, adc_v, larflow_v ); + + + LARCV_INFO() << " Node[" << node.nodeidx << "] tid=" << node.tid << " pid=" << node.pid << std::endl; + LARCV_INFO() << " Number of spacepoints found from using larflow: " << pos_vv.size() << std::endl; + + // get the position where the photon was created + // note: not the same as the location where it first interacted + std::vector start_reco_pt = MCPos2ImageUtils::Get()->truepos_to_recopos( node.start[0], + node.start[1], + node.start[2], + node.start[3], + true, true ); + LARCV_INFO() << " Find closest cluster to start point: (" << start_reco_pt[0] << "," + << start_reco_pt[1] << ", " + << start_reco_pt[2] << ", " + << start_reco_pt[3] << " usec)" + << std::endl; + + // ensure the shower_start_pt vector has 4 components + // it will store (x,y,z,t) + shower_start_pt.resize(4); + + // we want to define the "trunk" cluster of the shower. + // first, convert the information into the form our clustering algorithm needs. + std::vector< std::vector > data_v; + // loop over the 3d points + for ( auto& pt : pos_vv ) { + //std::cout << "3d pt: (" << pt[0] << ", " << pt[1] << ", " << pt[2] << ") " << std::endl; + + // // calculate distance of 3d point to first energy deposition point + // float dist_edep = 0.; + // float dx = 0.; + // for (int v=0; v<3; v++) { + // dx = pt[v] - node.first_edep_pos[v]; + // dist_edep += dx*dx; + // } + // dist_edep = sqrt(dist_edep); + + // if ( dist_edep>photon_start_edep_radius_cm || pt.src_pixval xpt(4,0); + for (int v=0; v<3; v++) + xpt[v] = pt[v]; + // add time dim, but put in a useless value. + xpt[3] = 0.0; + data_v.push_back( xpt ); + + } + + // use our interface to DBScan + auto dbcluster_v = ublarcvapp::dbscan::DBScan::makeCluster3f( 0.3, 3, 50, data_v ); + LARCV_INFO() << "Clustering spacepoints. Num clusters=" << dbcluster_v.size() << std::endl; + + // loop through the cluster and find the closest one + // that qualifies in terms of size + int icluster = -1; + float closest_qualified_cluster_dist = 1.0e9; + std::vector closest_pixsum_v; + for (int i=0; i<(int)dbcluster_v.size()-1; i++) { + // (we skip the last noise cluster) + + // we calculate the pixel sum in each plane of each cluster. + // (this is costly -- if it starts to be too slow -- we will need some thresholding with easy measures) + int nhits = dbcluster_v.at(i).size(); + + // ignore small clusters + if ( nhits<5 ) + continue; + + // find closest distance to photon start/creation point + float cluster_min_dist_to_source = 1e9; + std::vector closest_pt(3,0.0); + + // extract the points for the given cluster (with index i) + pointList cluster_pt_v; + cluster_pt_v.reserve(nhits); + for (int ipt=0; ipt pixsum_v = getPlanePixelSumsFromPointList( cluster_pt_v, adc_v ); + + // another threshold: one plane must pass 5 MeV threshol + // using MeV = 0.00162*pixsum, from matt's conversion formula + int planes_passing = 0; + //std::cout << "cluster[" << i << "] dist-to-source=" << cluster_min_dist_to_source << " cm; plane MeV: ("; + for ( int p=0; p<(int)pixsum_v.size(); p++ ) { + float MeV = pixsum_v[p]*0.0162; + //std::cout << MeV; + // if ( p+15.0 ) { + planes_passing++; + } + } + if (planes_passing>=1 && cluster_min_dist_to_source<100.0) { + LARCV_INFO() << "cluster[" << i << "] npoints=" << nhits + << " dist-to-source=" << cluster_min_dist_to_source << " cm;" + << " plane MeV: (" << pixsum_v[0]*0.0162 << ", " + << pixsum_v[1]*0.0162 << ", " + << pixsum_v[2]*0.0162 << ")" + << " pt=(" << closest_pt[0] << "," << closest_pt[1] << "," << closest_pt[2] << ")" + << "nplanes passing: " << planes_passing << std::endl; + } + + // check if cluster passes 'detectability criterion' + // and, if so, is the closest cluster to the creation point of the photon + if ( planes_passing>=2 && cluster_min_dist_to_source < closest_qualified_cluster_dist ) { + closest_qualified_cluster_dist = cluster_min_dist_to_source; + icluster = i; + closest_pixsum_v = pixsum_v; + } + }//end of loop over clusters + + // container to store our trunk points + pointList trunk_pt_v; + + if ( icluster<0 ) { + // if we did not find a valid cluster, + // we count this shower as unreconstructable. + // we define a sentinal value for the starting energy deposition point + shower_start_pt = std::vector{ -1.0, -1.0, -1.0 }; + LARCV_DEBUG() << "No valid trunk cluster found." << std::endl; + } + else { + // a valid cluster is found. store the points for this cluster + auto& largest_cluster = dbcluster_v.at(icluster); + // we also find the min distance between a hit in the cluster + // and the starting reco point. + // this 3d point will serve as the true first energy deposit point + float mindist = 1.0e9; + for (int ipt=0; ipt<(int)largest_cluster.size(); ipt++) { + auto& xpt = data_v.at( largest_cluster.at(ipt) ); + float dist = 0; + for (int v=0; v<3; v++) { + float dx = xpt[v]-start_reco_pt[v]; + dist += dx*dx; + } + dist = sqrt(dist); + if ( distDriftVelocity()) + 3200; + mindist = dist; + } + trunk_pt_v.push_back( xpt ); + }//end of loop over largest cluster points (by index) + LARCV_DEBUG() << "Defining observable trunk using cluster[" << icluster << "] " << std::endl; + LARCV_DEBUG() << " npoints=" << largest_cluster.size() + << " plane MeV: (" << closest_pixsum_v[0]*0.0162 << ", " + << closest_pixsum_v[1]*0.0162 << ", " + << closest_pixsum_v[2]*0.0162 << ")" + << std::endl; + LARCV_DEBUG() << " minimum dist to shower startpt: " << mindist << " cm" << std::endl; + }//end of else a valid shower cluster found + + LARCV_INFO() << " startpt: (" << shower_start_pt[0] << "," + << shower_start_pt[1] << "," + << shower_start_pt[2] << "," + << shower_start_pt[3] << " ticks)" + << std::endl; + + // convert the first interaction point (x,y,z,t) position into + // image coordinates which are (u,v,y,tick) + float t_ns = (shower_start_pt[3]-3200)*0.5*1000.0+3050.0; // ticks to ns + std::vector shower_imgpos4 = MCPos2ImageUtils::Get()->to_imagepos( shower_start_pt[0], + shower_start_pt[1], + shower_start_pt[2], + t_ns ); + LARCV_INFO() << " shower imgpos: (" << shower_imgpos4[0] << ", " + << shower_imgpos4[1] << ", " + << shower_imgpos4[2] << ", " + << shower_imgpos4[3] << " ticks )" + << std::endl; + + // append the imgcoordantes to the out-going vector + for (int v=0; v<4; v++) + shower_start_pt.push_back( shower_imgpos4[v] ); + + // get an index for this true photon pointList + // we will use it to store information about this trunk + // and then retrieve it later. + int photon_point_list_index = (int)_true_photon_v.size(); + _nodeidx_to_photonlist_index_v[ node.nodeidx ] = photon_point_list_index; + + // making pixellist + const int nplanes = adc_v.size(); + bool valid_imagemask = true; + std::vector plane_pixsum_v( nplanes, 0.0 ); + std::vector< PixelSet_t > plane_pixsets_v = _getPlanePixelSetsAndPixelSums( trunk_pt_v, adc_v, plane_pixsum_v ); + if ( plane_pixsum_v.size()>=3 ) { + LARCV_INFO() << " pixel sums MeV: (" << plane_pixsum_v[0]*0.0162 << ", " + << plane_pixsum_v[1]*0.0162 << ", " + << plane_pixsum_v[2]*0.0162 << ") " + << std::endl; + } + + // making image masks + std::vector< larcv::Image2D > trunk_planeimg_v; + for (int p=0; p pix_row ) min_row = pix_row; + if ( max_row < pix_row ) max_row = pix_row; + if ( min_col > pix_col ) min_col = pix_col; + if ( max_col < pix_col ) max_col = pix_col; + } + // define the imagemeta to make the cropped larcv::Image2d object + auto const& meta = img.meta(); + int colcount = max_col-min_col+1; + int rowcount = max_row-min_row+1; + if (colcount<=0 or rowcount<=0) { + // invalid bounding box + colcount = 1; + rowcount = 1; + min_col = 0; + min_row = 0; + max_col = 1; + max_row = 1; + } + float width = float( colcount ); + float height = meta.pixel_height()*rowcount; + float origin_x = float( meta.pos_x( min_col ) ); + float origin_y = float( meta.pos_y( min_row ) ); + + LARCV_DEBUG() << "min (col,row)=(" << min_col << "," << min_row << ")" << std::endl; + LARCV_DEBUG() << "min (x,y)=(" << origin_x << "," << origin_y << ")" << std::endl; + larcv::ImageMeta trunk_meta( width, height, rowcount, colcount, origin_x, origin_y, meta.plane() ); + larcv::Image2D trunk_img( trunk_meta ); + trunk_img.paint(0.0); + + LARCV_DEBUG() << "Defined plane bounding box for trunk: \n" << trunk_meta.dump() << std::endl; + LARCV_DEBUG() << "original meta: \n " << meta.dump() << std::endl; + + // loop over the pixels and fill the mask + for (auto& pix : pixset ) { + int pix_row = pix.first; + int pix_col = pix.second; + //LARCV_DEBUG() << " set pixel (row,col)=(" << pix_row << ", " << pix_col << ") " << std::endl; + try { + trunk_img.set_pixel( pix_row-min_row, pix_col-min_col, 1.0 ); + } + catch (...) { + LARCV_ERROR() << "pixel (row,col)=(" << pix_row << "," << pix_col << ") is outside crop." << std::endl; + } + } + + // store the image in the plane container + trunk_planeimg_v.emplace_back( std::move(trunk_img) ); + + }//end of plane loop + + // store all of this stuff + _true_photon_v.emplace_back( std::move(trunk_pt_v) ); + _true_photon_plane_pixset_vv.emplace_back( std::move( plane_pixsets_v ) ); + _true_photon_plane_pixsum_vv.emplace_back( std::move( plane_pixsum_v ) ); + _true_photon_plane_trunkimg_vv.emplace_back( std::move( trunk_planeimg_v ) ); + + // make sure we didnt screw something up + if ( (int)_true_photon_v.size()!=(photon_point_list_index+1) ) { + LARCV_ERROR() << "index of new true photon 3d point list doesn't match the size of the container" << std::endl; + } + if ( _true_photon_v.size()!=_true_photon_plane_pixset_vv.size() ) + LARCV_ERROR() << "size of the true photon 3d point container is not in the pixset container" << std::endl; + if ( _true_photon_v.size()!=_true_photon_plane_pixsum_vv.size() ) + LARCV_ERROR() << "size of the true photon 3d point container is not in the pixel sum container" << std::endl; + if ( _true_photon_v.size()!=_true_photon_plane_trunkimg_vv.size() ) + LARCV_ERROR() << "size of the true photon 3d point container is not the same as the trunk image mask container" << std::endl; + + //std::cout << "========================================================END======" << std::endl; + + return shower_start_pt; + + } + + /** + * @brief Get the 3d points representing the true photon trunk + * + */ + const std::vector< std::vector >& MCPixelPGraph::getTruePhotonTrunk3DPoints( Node_t& node ) + { + + auto it_pointlist = _nodeidx_to_photonlist_index_v.find( node.nodeidx ); + if ( it_pointlist==_nodeidx_to_photonlist_index_v.end() ) { + std::cout << "[MCPixelPGraph::getTruePhotonTrunk3DPoints] [WARNING]: did not find photonlist for " + << " nodeidx=" << node.nodeidx + << " pdg=" << node.pid + << " trackid=" << node.tid << ". " + << "Returning emptry point list." + << std::endl; + _empty_photon_pointlist_v.clear(); + return _empty_photon_pointlist_v; + } + + size_t index = it_pointlist->second; + if ((int)index<0 || index>=_true_photon_v.size() ) { + std::stringstream msg; + msg << "Missing photon list index! node.trackid=" << node.tid + << " map.trackid=" << it_pointlist->first + << " map.index=" << it_pointlist->second + << std::endl; + throw std::runtime_error( msg.str() ); + } + + return _true_photon_v.at( it_pointlist->second ); + } + + /** + * @brief Get the 3d points representing the true photon trunk by Track ID + * + */ + const std::vector< std::vector >& MCPixelPGraph::getTruePhotonTrunk3DPoints( int trackid ) + { + Node_t* pnode = findTrackID( trackid ); + if ( pnode==nullptr ) { + std::cout << "[MCPixelPGraph::getTruePhotonTrunk3DPoints] [WARNING]: did not particle Node for " + << " trackid=" << pnode->tid << ". " + << "Returning emptry point list." + << std::endl; + _empty_photon_pointlist_v.clear(); + return _empty_photon_pointlist_v; + } + + return getTruePhotonTrunk3DPoints( *pnode ); + } + + /** + * @brief Get the sum of pixel values around the trunk plane + */ + std::vector MCPixelPGraph::getTruePhotonTrunkPlanePixelSums( int trackid ) + { + // we want to sum using a kernel (3x3) pixels. We want to avoid double counting, + // so we must mark which pixels we used + Node_t* pnode = findTrackID( trackid ); + if ( pnode==nullptr ) { + LARCV_ERROR() << ": did not find particle Node pointer for " + << " trackid=" << pnode->tid << ". " + << "Returning emptry point list." + << std::endl; + _empty_photon_pointlist_v.clear(); + return _empty_pixsum_v; + } + + auto it_pixsum = _nodeidx_to_photonlist_index_v.find( pnode->nodeidx ); + if ( it_pixsum==_nodeidx_to_photonlist_index_v.end() ) { + LARCV_NORMAL() << "Did not find true photon trunk info for trackid=" << trackid << std::endl; + for ( auto& it_x : _nodeidx_to_photonlist_index_v ) { + pnode = &(node_v.at( it_x.first )); + LARCV_NORMAL() << " [nodeidx=" << it_x.first << ", trackid=" << pnode->tid << "] photonlist index=" << it_x.second << std::endl; + } + LARCV_ERROR() << "Stopping on error" << std::endl; + } + + std::vector pixsum_v = _true_photon_plane_pixsum_vv.at( it_pixsum->second ); + return pixsum_v; + + }// end of MCPixelPGraph::getTruePhotonTrunkPlanePixelSums( int trackid ) + + /** + * @brief Get the sum of pixel values around the trunk plane + */ + const std::vector< MCPixelPGraph::PixelSet_t >& + MCPixelPGraph::getTruePhotonTrunkPlanePixelSets( int trackid ) + { + // we want to sum using a kernel (3x3) pixels. We want to avoid double counting, + // so we must mark which pixels we used + Node_t* pnode = findTrackID( trackid ); + if ( pnode==nullptr ) { + std::cout << "[MCPixelPGraph::getTruePhotonTrunk3DPoints] [WARNING]: did not find particle Node pointer for " + << " trackid=" << pnode->tid << ". " + << "Returning emptry point list." + << std::endl; + _empty_pixelset_v.clear(); + return _empty_pixelset_v; + } + + auto it_pixsum = _nodeidx_to_photonlist_index_v.find( pnode->nodeidx ); + if ( it_pixsum==_nodeidx_to_photonlist_index_v.end() ) { + LARCV_ERROR() << "Did not find true photon trunk info for trackid=" << trackid << std::endl; + } + + return _true_photon_plane_pixset_vv.at( it_pixsum->second ); + } + + /** + * @brief Get larcv image containing trunk pixels + */ + const std::vector< larcv::Image2D >& + MCPixelPGraph::getTruePhotonTrunkPlaneImage2DMasks( int trackid ) + { + + Node_t* pnode = findTrackID( trackid ); + if ( pnode==nullptr ) { + LARCV_WARNING() << "[MCPixelPGraph::getTruePhotonTrunkPlanePixelSets] " + << " [WARNING]: did not find particle Node pointer for " + << " trackid=" << pnode->tid << ". " + << "Returning emptry point list." + << std::endl; + _empty_imagemask_v.clear(); + return _empty_imagemask_v; + } + + auto it_pixsum = _nodeidx_to_photonlist_index_v.find( pnode->nodeidx ); + if ( it_pixsum==_nodeidx_to_photonlist_index_v.end() ) { + LARCV_ERROR() << "Did not find true photon trunk info for trackid=" << trackid << std::endl; + } + + return _true_photon_plane_trunkimg_vv.at( it_pixsum->second ); + + } + + /** + * @brief Does the calculation to get the pixels in each plane corresponding to 3D points of true shower trunk + */ + std::vector + MCPixelPGraph::getPlanePixelSumsFromPointList( const std::vector< std::vector >& pointlist, + const std::vector< larcv::Image2D >& adc_v ) + { + std::vector pixsum_v; + + const int nplanes = adc_v.size(); + const int dpix = 1; + std::vector out_of_imgpix(nplanes,0); + // allocate space to the pixsum + pixsum_v.resize(nplanes,0); + + for (int p=0; p > _pix_used; + + // loop over 3d points + for (int ipt=0; ipt<(int)pointlist.size(); ipt++) { + auto& pt = pointlist.at(ipt); + // get the point in the image + std::vector imgpos4 = MCPos2ImageUtils::Get()->to_imagepos( pt[0], + pt[1], + pt[2], + 0.0 ); + + //std::cout << " pt[" << ipt << "] imgpos4: (" << imgpos4[0] << ", " << imgpos4[1] << ", " << imgpos4[2] << ", " << imgpos4[3] << ")" << std::endl; + + // sum over 3x3 kernel + // first find center pixel + int row_center = 0; + int col_center = 0; + try { + row_center = img.meta().row( imgpos4[3] ); + col_center = img.meta().col( imgpos4[p] ); + } + catch (...) { + // out of image tick + out_of_imgpix[p]++; + continue; + } + + for (int dr=-dpix; dr<=dpix; dr++) { + for (int dc=-dpix; dc<=dpix; dc++) { + int r = row_center + dr; + int c = col_center + dc; + + auto it_pix = _pix_used.find( std::pair(r,c) ); + if ( it_pix==_pix_used.end() ) { + // did not find it in the set, so add it as part of the sum + float pixval = 0.0; + try { + pixval = img.pixel( r, c ); + _pix_used.insert( std::pair(r,c) ); + pixsum_v[p] += pixval; + } + catch (...) { + // probably out of image + out_of_imgpix[p]++; + continue; + } + } + + }//end of dc loop + }//end of dr loop + + }//end of point loop + + //std::cout << "PixelSum[" << p << "]: sum=" << pixelsum_v[p] << " npixels=" << _pix_used.size() << " out_of_img=" << out_of_imgpix[p] << std::endl; + }//end of plane loop + + return pixsum_v; + }// end of MCPixelPGraph::getPlanePixelSumsFromPointList + + /** + * @brief Get set of pixels in each plane from a 3D point list + * + */ + std::vector< MCPixelPGraph::PixelSet_t > + MCPixelPGraph::_getPlanePixelSetsAndPixelSums( const MCPixelPGraph::pointList& pt_v, + const std::vector& adc_v, + std::vector& pixsum_v ) + { + + + std::vector< MCPixelPGraph::PixelSet_t > plane_pixsets_v; + const int nplanes = adc_v.size(); + const int dpix = 2; + + std::vector out_of_imgpix(nplanes,0); + pixsum_v.resize(nplanes,0.0); + for (int p=0; p imgpos4 = MCPos2ImageUtils::Get()->to_imagepos( pt[0], + pt[1], + pt[2], + 0.0 ); + //std::cout << " pt[" << ipt << "] imgpos4: (" << imgpos4[0] << ", " << imgpos4[1] << ", " << imgpos4[2] << ", " << imgpos4[3] << ")" << std::endl; + + // sum over 3x3 kernel + // first find center pixel + int row_center = 0; + int col_center = 0; + try { + row_center = img.meta().row( imgpos4[3] ); + col_center = img.meta().col( imgpos4[p] ); + } + catch (...) { + // out of image tick + out_of_imgpix[p]++; + continue; + } + + for (int dr=-dpix; dr<=dpix; dr++) { + for (int dc=-dpix; dc<=dpix; dc++) { + int r = row_center + dr; + int c = col_center + dc; + + auto it_pix = _pix_used.find( std::pair(r,c) ); + if ( it_pix==_pix_used.end() ) { + // did not find it in the set, so add it as part of the sum + float pixval = 0.0; + try { + pixval = img.pixel( r, c ); + _pix_used.insert( std::pair(r,c) ); + pixsum_v[p] += pixval; + } + catch (...) { + // probably out of image + out_of_imgpix[p]++; + continue; + } + } + }//end of dc loop + }//end of dr loop + }//end of point loop + + //std::cout << "PixelSum[" << p << "]: sum=" << pixelsum_v[p] << " npixels=" << _pix_used.size() << " out_of_img=" << out_of_imgpix[p] << std::endl; + plane_pixsets_v.emplace_back( std::move( _pix_used ) ); + + }//end of plane loop + + return plane_pixsets_v; + } + + /** + * @brief Get the position the particle first deposited energy + * + */ + std::vector MCPixelPGraph::getParticleEDepPos( const int& trackid ) + { + std::vector edep_pos; + Node_t* pnode = findTrackID( trackid ); + if (pnode==nullptr) { + std::stringstream msg; + msg << "[MCPixelPGraph::getParticleEDepPos] [ERROR] : Could not find a particle node for trackid=" << trackid << std::endl; + throw std::runtime_error( msg.str() ); + } + edep_pos.resize(3,0); + for (int v=0; v<3; v++) { + edep_pos[v] = pnode->first_edep_pos[v]; + } + return edep_pos; + } + + /** + * @brief get ancestor ID for track ID by querying info in Nodes + */ + int MCPixelPGraph::getAncestorID( int trackid ) { + Node_t* pnode = findTrackID(trackid); + if (pnode==nullptr) + return -1; + return pnode->aid; + } + + /** + * @brief get ancestor ID for track ID by querying info in Nodes + */ + int MCPixelPGraph::getShowerMotherID( int trackid ) { + auto it = _shower_daughter2mother.find( trackid ); + if ( it!=_shower_daughter2mother.end() ) { + return it->second; + } + return -1; + } + + /** + * @brief get particle ID for track ID by querying info in Nodes + */ + int MCPixelPGraph::getParticleID( int trackid ) { + Node_t* pnode = findTrackID(trackid); + if (pnode==nullptr) + return -1; + return pnode->pid; + } + + } } diff --git a/ublarcvapp/MCTools/MCPixelPGraph.h b/ublarcvapp/MCTools/MCPixelPGraph.h index 2919c68..efa4dbc 100644 --- a/ublarcvapp/MCTools/MCPixelPGraph.h +++ b/ublarcvapp/MCTools/MCPixelPGraph.h @@ -7,12 +7,19 @@ #include "larcv/core/Base/larcv_base.h" #include "larcv/core/DataFormat/IOManager.h" #include "larcv/core/DataFormat/Image2D.h" +#include "larcv/core/DataFormat/EventImage2D.h" // larlite -#include "DataFormat/storage_manager.h" -#include "DataFormat/mcshower.h" -#include "DataFormat/mctrack.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" + +// ROOT +#include "TH2D.h" +#include "TText.h" +#include "TMarker.h" +#include "TCanvas.h" /** * Determine particle graph. Collect pixels for each particle. @@ -28,7 +35,26 @@ namespace mctools { MCPixelPGraph() : larcv::larcv_base("MCPixelPGraph"), - adc_tree("wire") + node_v(), + _unassigned_pixels_vv(), + _eventRootNode(nullptr), + _nplanes(3), + photon_start_edep_radius_cm(10.0), + photon_start_pixval_threshold(30.0), + photon_start_min_cluster_size(10), + ev_mctrack(nullptr), + ev_mcshower(nullptr), + ev_mctruth(nullptr), + ev_adc(nullptr), + ev_seg(nullptr), + ev_ins(nullptr), + ev_anc(nullptr), + _kNuVertexDistCutoff_cm(0.3), + _nu_vertices_v(), + _cluster_neutrino_particles(false), + cvis(nullptr), + _shower_daughter2mother(), + adc_tree("wire") {}; virtual ~MCPixelPGraph() {}; @@ -38,6 +64,7 @@ namespace mctools { const std::vector& segment_v, const std::vector& instance_v, const std::vector& ancestor_v, + const std::vector& larflow_v, const larlite::event_mcshower& shower_v, const larlite::event_mctrack& track_v, const larlite::event_mctruth& mctruth_v ); @@ -46,12 +73,14 @@ namespace mctools { void buildgraphonly( const larlite::event_mcshower& shower_v, const larlite::event_mctrack& track_v, const larlite::event_mctruth& mctruth_v ); + + void set_cluster_neutrino_particles( bool doit ) { _cluster_neutrino_particles=doit; }; struct Node_t { int nodeidx; // position in node_v - int type; // track=0, shower=1 + int type; // track=0, shower=1, nu-vertex=2, genie_fs=3 int vidx; // position in mcshower or mctrack vector int tid; // geant4 track-ID int aid; // ancestor geant4 trackid @@ -60,13 +89,22 @@ namespace mctools { Node_t* mother; // pointer to Mother Node_t int mid; // mother nodeidx float E_MeV; // energy + std::string process; // creating process std::vector daughter_idx_v; // daughter node indices in node_v std::vector daughter_v; // pointer to daughters std::vector< std::vector > pix_vv; // pixels in each plane. pixels stored in (tick,wire) coordinates - std::vector start; //< (x,y,z,t) before sce - std::vector imgpos4; //< (x,y,z,tick) after sce + std::vector< std::vector > pixval_vv; // pixel values in each plane, aligned with pix_vv + std::vector pixsum_v; // sum of pixel values in each plane + std::vector start; //< (x,y,z,t) before sce, true start of particle + std::vector first_edep_pos; //< (x,y,z,t) before sce, first step that leaves edep in cryostat + std::vector first_tpc_pos; //< (x,y,z,t) before sce, first step inside the TPC, visible in the image + std::vector first_img_pos; //< (x,y,z,t) before sce, first step inside the TPC, visible in the image + std::vector imgpos4; //< (x,y,z,tick) after sce // the image position corresponding to first_tpc_pos + std::vector imgpos4_edep; //< (x,y,z,tick) after sce // the image position corresponding to the first_edep_pos + std::vector imgpos4_start; //< (x,y,z,tick) after sce // the image position corresponding to the start pos after SCE std::vector< std::vector > plane_bbox_twHW_vv; /// bounding box for pixels in each plane - int origin; + + int origin; // 1=neutrino, 2=cosmic, 0=unassigned, -1=unassigned Node_t() : nodeidx(-1), @@ -74,25 +112,48 @@ namespace mctools { vidx(-1), tid(-1), aid(-1), + mtid(-1), pid(-1), mother(nullptr), mid(-1), E_MeV(-1.0), - start({0,0,0}), + process("null"), + start({0,0,0,0}), + first_edep_pos({0,0,0,0}), + first_tpc_pos({0,0,0,0}), + first_img_pos({0,0,0,0}), + imgpos4({0,0,0,0}), + imgpos4_edep({0,0,0,0}), + imgpos4_start({0,0,0,0}), origin(-1) - {}; + { + daughter_v.clear(); + }; - Node_t(int _nodeidx, int _type, int _tid, int _vidx, int _pid, Node_t* _mother=nullptr, int _mid=-1, float _energy=-1.0 ) + Node_t(int _nodeidx, int _type, int _tid, int _vidx, + int _pid, + Node_t* _mother=nullptr, + int _mid=-1, + float _energy=-1.0, + std::string proc="null") : nodeidx(_nodeidx), type(_type), vidx(_vidx), tid(_tid), + aid(-1), + mtid(-1), pid(_pid), mother(_mother), mid(_mid), E_MeV(_energy), + process(proc), start({0,0,0,0}), + first_edep_pos({0,0,0,0}), + first_tpc_pos({0,0,0,0}), + first_img_pos({0,0,0,0}), imgpos4({0,0,0,0}), + imgpos4_edep({0,0,0,0}), + imgpos4_start({0,0,0,0}), origin(-1) {}; @@ -100,17 +161,46 @@ namespace mctools { if ( tid < rhs.tid ) return true; return false; }; + + bool isTrackObject() const { + if ( type==0 ) + return true; + return false; + }; + + bool isShowerObject() const { + if (type==1) + return true; + return false; + }; + + bool isNuVertexObject() const { + if (type==2) + return true; + return false; + }; + + bool isGenieFinalStateObject() const { + if (type==3) + return true; + return false; + }; + }; // list of nodes std::vector< Node_t > node_v; std::vector< std::vector > _unassigned_pixels_vv; + Node_t* _eventRootNode; // number of planes size_t _nplanes; // set when _scanPixelData is run // search methods Node_t* findTrackID( int trackid ); + int getAncestorID( int trackid ); + int getShowerMotherID( int trackid ); + int getParticleID( int trackid ); // print info methods void printAllNodeInfo(); @@ -129,8 +219,72 @@ namespace mctools { std::vector getPrimaryParticles( bool exclude_neutrons=true ); std::vector getNeutrinoPrimaryParticles( bool exclude_neutrons=true ); std::vector getNeutrinoParticles( bool exclude_neutrons=true ); + + // get positions + std::vector getParticleEDepPos( const int& trackid ); + + // make visualization of nu particles + // std::vector< TH2D > makeTH2D( std::string hist_stem_name ); + + std::vector< std::vector > + makeNode3DpointsFromLArFlowTruth( Node_t& node, + const std::vector& adc_v, + const std::vector& larflow_v ); + + // clear the state + void clear(); + + public: + + // variables and functions for making better quantities to + // characterize true photons + typedef std::vector< std::vector > pointList; ///< just a redefinition for convenience + typedef std::set< std::pair > PixelSet_t; ///< just a redefinition for convenience + typedef std::vector< larcv::Image2D > ImageSet_t; ///< just a redefinition for convenience protected: + float photon_start_edep_radius_cm; + float photon_start_pixval_threshold; + int photon_start_min_cluster_size; + std::vector< pointList > _true_photon_v; /// list of 3d points representing the shower trunk + std::vector< std::vector > _true_photon_plane_trunkimg_vv; + std::vector< std::vector< PixelSet_t > > _true_photon_plane_pixset_vv; + std::vector< std::vector< float > > _true_photon_plane_pixsum_vv; + std::map< int, int > _nodeidx_to_photonlist_index_v; /// map from node.nodeidx to index in _true_photon_v + pointList _empty_photon_pointlist_v; + std::vector _empty_pixsum_v; + std::vector< MCPixelPGraph::PixelSet_t > _empty_pixelset_v; + std::vector< larcv::Image2D > _empty_imagemask_v; + + std::vector fixingPhotonStartPoints( Node_t& node, + const std::vector& instance_v, + const std::vector& ancestor_v, + const std::vector& adc_v, + const std::vector& larflow_v ); + std::vector getPlanePixelSumsFromPointList( const std::vector< std::vector >& pointlist, + const std::vector& adc_v ); + std::vector< MCPixelPGraph::PixelSet_t > _getPlanePixelSetsAndPixelSums( const MCPixelPGraph::pointList& pt_v, + const std::vector& adc_v, + std::vector& pixsum_v ); + + //* Functions to get info about the true photon trunk energy deposits and pixels */ + public: + const pointList& getTruePhotonTrunk3DPoints( Node_t& node ); + const pointList& getTruePhotonTrunk3DPoints( int trackid ); + std::vector getTruePhotonTrunkPlanePixelSums( int trackid ); + const std::vector< MCPixelPGraph::PixelSet_t >& getTruePhotonTrunkPlanePixelSets( int trackid ); + const std::vector< larcv::Image2D >& getTruePhotonTrunkPlaneImage2DMasks( int trackid ); + + protected: + + larlite::event_mctrack* ev_mctrack; + larlite::event_mcshower* ev_mcshower; + larlite::event_mctruth* ev_mctruth; + larcv::EventImage2D* ev_adc; + larcv::EventImage2D* ev_seg; + larcv::EventImage2D* ev_ins; + larcv::EventImage2D* ev_anc; + larcv::EventImage2D* ev_larflow; void _recursivePrintGraph( Node_t* node, int& depth, bool visible_only=true ); void _scanPixelData( const std::vector& adc_v, @@ -140,12 +294,35 @@ namespace mctools { const std::vector threshold_v ); void _get_imgpos( std::vector& realpos4, std::vector& imgpos4, - larutil::SpaceChargeMicroBooNE& sce ); + larutil::SpaceChargeMicroBooNE& sce, + bool apply_sce=true ); + + void _adoptNeutrinoOrphans( const larlite::event_mctruth* ev_mctruth ); + + virtual const larlite::mctrack& _retrieve_mctrackobject( const Node_t* node, const larlite::event_mctrack& ev_track_v ); + virtual const larlite::mcshower& _retrieve_mcshowerobject( const Node_t* node, const larlite::event_mcshower& ev_shower_v ); + virtual const larlite::mctrack& _retrieve_mctrackobject( const Node_t* node, larlite::storage_manager& ioll, std::string producername="mcreco" ); + virtual const larlite::mcshower& _retrieve_mcshowerobject( const Node_t* node, larlite::storage_manager& ioll, std::string producername="mcreco" ); + + float _kNuVertexDistCutoff_cm; + std::vector< std::vector > _nu_vertices_v; + //std::map< int, int > _map_trackid_to_nu_ancestor_v; + bool _cluster_neutrino_particles; + int _define_neutrino_interaction_nodes( larlite::storage_manager& ioll ); + int _define_neutrino_interaction_nodes( const larlite::event_mctrack& ev_track_v, const larlite::event_mcshower& ev_shower_v ); + + // visualization + TCanvas* cvis; + std::vector< TMarker* > vis_markers_v; + std::vector< TText* > vis_label_v; + public: std::map _shower_daughter2mother; void _fill_shower_daughter2mother_map( const std::vector& mcsh_v ); + + public: diff --git a/ublarcvapp/MCTools/MCPixelPMap.cxx b/ublarcvapp/MCTools/MCPixelPMap.cxx new file mode 100644 index 0000000..555e59f --- /dev/null +++ b/ublarcvapp/MCTools/MCPixelPMap.cxx @@ -0,0 +1,52 @@ + +#include "MCPixelPMap.h" + +#include "larcv/core/DataFormat/EventImage2D.h" + + +namespace ublarcvapp { +namespace mctools { + + void MCPixelPMap::buildmap( larcv::IOManager& iolcv, larlite::storage_manager& ioll ){ + ublarcvapp::mctools::MCPixelPGraph mcpg; + mcpg.set_adc_treename(adc_tree); + mcpg.buildgraph(iolcv, ioll); + buildmap(iolcv, mcpg); + } + + void MCPixelPMap::buildmap( larcv::IOManager& iolcv, ublarcvapp::mctools::MCPixelPGraph& pixGraph ){ + + larcv::EventImage2D* ev_adc = (larcv::EventImage2D*)iolcv.get_data( larcv::kProductImage2D, adc_tree ); + const auto& adc_v = ev_adc->Image2DArray(); + + for( const auto& node: pixGraph.node_v ){ + for( size_t p=0; p < adc_v.size(); p++ ){ + for( unsigned int iP = 0; iP < node.pix_vv[p].size()/2; iP++ ) { + int row = (node.pix_vv[p][2*iP] - 2400)/6; + int col = node.pix_vv[p][2*iP+1]; + PixLoc_t pixLoc( (int)p, row, col ); + PixPart_t pixPart( node.nodeidx, node.tid, node.pid ); + if(pixMap.count(pixLoc) == 0){ + PixCont_t pixCont( adc_v[p].pixel(row, col) ); + pixCont.particles.push_back(pixPart); + pixMap[pixLoc] = pixCont; + } + else{ pixMap[pixLoc].particles.push_back(pixPart); } + } + } + } + + } + + MCPixelPMap::PixCont_t MCPixelPMap::getPixContent(int plane, int row, int col){ + PixLoc_t pixLoc(plane, row, col); + if(pixMap.count(pixLoc) > 0){ return pixMap.at(pixLoc); } + else{ + PixCont_t pixCont; + return pixCont; + } + } + +} +} + diff --git a/ublarcvapp/MCTools/MCPixelPMap.h b/ublarcvapp/MCTools/MCPixelPMap.h new file mode 100644 index 0000000..9e31e20 --- /dev/null +++ b/ublarcvapp/MCTools/MCPixelPMap.h @@ -0,0 +1,78 @@ +#ifndef __MCPIXELPDICT_H__ +#define __MCPIXELPDICT_H__ + +#include +#include +#include + +#include "larcv/core/Base/larcv_base.h" +#include "larcv/core/DataFormat/IOManager.h" +#include "larlite/DataFormat/storage_manager.h" +#include "MCPixelPGraph.h" + +namespace ublarcvapp { +namespace mctools { + + class MCPixelPMap : public larcv::larcv_base { + + public: + + MCPixelPMap() : larcv::larcv_base("MCPixelPMap"), adc_tree("wire") {}; + virtual ~MCPixelPMap() {}; + + struct PixLoc_t { + int plane, row, col; + PixLoc_t() : plane(-1), row(-1), col(-1) {}; + PixLoc_t(int _plane, int _row, int _col) : plane(_plane), row(_row), col(_col) {}; + bool operator==( const PixLoc_t& rhs ) const { + if(plane == rhs.plane && row == rhs.row && col == rhs.col) return true; + return false; + }; + bool operator<( const PixLoc_t& rhs ) const { + if(plane < rhs.plane) return true; + if(plane > rhs.plane) return false; + if(row < rhs.row) return true; + if(row > rhs.row) return false; + if(col < rhs.col) return true; + return false; + } + }; + + struct PixPart_t { + int nodeidx; + int tid; + int pdg; + PixPart_t() : nodeidx(-1), tid(-1), pdg(0) {}; + PixPart_t(int _nodeidx, int _tid, int _pdg) : nodeidx(_nodeidx), tid(_tid), pdg(_pdg) {}; + bool operator>(const PixPart_t& rhs) const { + if(nodeidx > rhs.nodeidx) return true; + return false; + } + }; + + struct PixCont_t { + float pixI; + std::vector particles; + PixCont_t() : pixI(0.) {}; + PixCont_t(float _pixI) : pixI(_pixI) {}; + bool operator>(const PixCont_t& rhs) const { + if(pixI > rhs.pixI) return true; + return false; + } + }; + + void buildmap( larcv::IOManager& iolcv, larlite::storage_manager& ioll ); + void buildmap( larcv::IOManager& iolcv, ublarcvapp::mctools::MCPixelPGraph& pixGraph ); + + PixCont_t getPixContent(int plane, int row, int col); + + std::map pixMap; + std::string adc_tree; + void set_adc_treename( std::string name ) { adc_tree = name; }; + + }; + +} +} + +#endif diff --git a/ublarcvapp/MCTools/MCPos2ImageUtils.cxx b/ublarcvapp/MCTools/MCPos2ImageUtils.cxx new file mode 100644 index 0000000..d578201 --- /dev/null +++ b/ublarcvapp/MCTools/MCPos2ImageUtils.cxx @@ -0,0 +1,162 @@ +#include "MCPos2ImageUtils.h" + +#include "larlite/LArUtil/DetectorProperties.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/InvalidWireError.h" + +namespace ublarcvapp { +namespace mctools { + + MCPos2ImageUtils* MCPos2ImageUtils::_p_singleton = nullptr; + + std::vector< std::vector > + MCPos2ImageUtils::getRecoSpacepoints( const larlite::mctrack& track, + bool apply_t0_shift, + bool apply_sce ) + { + std::vector< std::vector > reco( track.size() ); + + size_t istep = 0; + for (auto const& step : track ) { + reco[istep] = truepos_to_recopos( step.X(), step.Y(), step.Z(), step.T(), + apply_t0_shift, apply_sce ); + istep++; + } + + + return reco; + } + + std::vector + MCPos2ImageUtils::truepos_to_recopos( const float x_cm, const float y_cm, const float z_cm, + const float t_ns, + bool apply_t0_shift, + bool apply_sce ) + { + + float v_cm_per_us = ::larutil::LArProperties::GetME()->DriftVelocity(); + float us_per_tick = (::larutil::DetectorProperties::GetME()->SamplingRate()*1.0e-3); // value return is in MHz, convert to usec + float trig_g4time_ns = 4050.0; + float dx_trigger_cm = 0.0; + + // std::cout << "v_cm_per_us: " << v_cm_per_us << std::endl; + // std::cout << "us_per_tick: " << us_per_tick << std::endl; + + std::vector pos_sce = { x_cm, y_cm, z_cm }; + if ( apply_sce ) { + pos_sce = get_sce_shifted_pos( x_cm, y_cm, z_cm ); + } + + std::vector reco_pos = pos_sce; + + if ( apply_t0_shift ) { + // apparent depth from the anode due to relative time to trigger + dx_trigger_cm = (t_ns - trig_g4time_ns)*1.0e-3*v_cm_per_us; + } + reco_pos[0] += dx_trigger_cm; + + // append the tick the charge should appear at + //float tick = larutil::DetectorProperties::GetME()->ConvertXToTicks( reco_pos[0], 0 ); // this is busted + float tickx = (reco_pos[0])/v_cm_per_us/us_per_tick + 3200.0; + + //std::cout << "tick=" << tick << " vs " << tickx << std::endl; + reco_pos.push_back( tickx ); + + return reco_pos; + } + + + /** + * @brief get the reco position due to electric field non-uniformity + * + */ + std::vector + MCPos2ImageUtils::get_sce_shifted_pos( const float x, const float y, const float z ) + { + + std::vector out = { x, y, z }; + + if ( psce ) { + std::vector sce_shift = psce->GetPosOffsets( x, y, z ); + out[0] += -sce_shift[0]+0.7; + out[1] += sce_shift[1]; + out[2] += sce_shift[2]; + } + + return out; + } + + std::vector MCPos2ImageUtils::to_imagepos( const float x, const float y, const float z, const float t_ns ) + { + + float v_cm_per_us = ::larutil::LArProperties::GetME()->DriftVelocity(); + float us_per_tick = (::larutil::DetectorProperties::GetME()->SamplingRate()*1.0e-3); // value return is in MHz, convert to usec + + TVector3 vpos; + vpos[0] = x; + vpos[1] = y; + vpos[2] = z; + //std::cout << "vpos: " << vpos[0] << " " << vpos[1] << " " << vpos[2] << std::endl; + + // make sure pos is inside the tpc + + + std::vector imgpos(4,0); + if ( std::fabs(vpos[1])>116.5 || vpos[2]<0.0 || vpos[2]>1036.0 ) + return imgpos; + + for (int p=0; p<3; p++) { + float wire = 0; + try { + wire = (float)larutil::Geometry::GetME()->NearestWire( vpos, p ); + } + catch (larutil::InvalidWireError& err) { + wire = err.better_wire_number; + } + imgpos[p] = wire; + } + float tickx = vpos[0]/v_cm_per_us/us_per_tick + 3200.0; + imgpos[3] = tickx; + + return imgpos; + } + + std::vector MCPos2ImageUtils::truepos_to_imagepos( const float x, const float y, const float z, + const float t_ns, bool apply_sce ) + { + std::vector recopos = truepos_to_recopos( x, y, z, t_ns, true, apply_sce ); + TVector3 vpos; + vpos[0] = recopos[0]; + vpos[1] = recopos[1]; + vpos[2] = recopos[2]; + //std::cout << "vpos: " << vpos[0] << " " << vpos[1] << " " << vpos[2] << std::endl; + + std::vector imgpos(4,0); + if ( std::fabs(vpos[1])>116.5 || vpos[2]<0.0 || vpos[2]>1036.0 ) + return imgpos; + + imgpos[3] = recopos[3]; // should be the tick + + //const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + //float tick = ( recopos[3]-4.050 )/0.5 + recopos[0]/cm_per_tick + 3200.0; + for (int p=0; p<3; p++) { + float wire = 0.0; + try { + wire = (float)larutil::Geometry::GetME()->NearestWire( vpos, p ); + } + catch (larutil::InvalidWireError& err) { + wire = err.better_wire_number; + } + imgpos[p] = wire; + } + + return imgpos; + } + + + + + +} +} diff --git a/ublarcvapp/MCTools/MCPos2ImageUtils.h b/ublarcvapp/MCTools/MCPos2ImageUtils.h new file mode 100644 index 0000000..abae03c --- /dev/null +++ b/ublarcvapp/MCTools/MCPos2ImageUtils.h @@ -0,0 +1,85 @@ +#ifndef __UBLARCVAPP_MCTOOLS_MCPOS2IMAGEUTILS_H__ +#define __UBLARCVAPP_MCTOOLS_MCPOS2IMAGEUTILS_H__ + +#include + +#include "larcv/core/Base/larcv_base.h" +#include "larcv/core/DataFormat/ImageMeta.h" + +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/mctrack.h" + +namespace ublarcvapp { +namespace mctools { + + /** + * The purpose of this singleton class is to standardize common conversions between + * the detector geometry and image locations. + * This requires detector-specific information that includes the geometry, drift direction, + * electronics clock, field, and space-charge effect. + * This utility class specializes in the conversions that involve MC values and objects. + * + * This is an attempt to standardize tools for later use across SBN. + * + */ + + class MCPos2ImageUtils : public larcv::larcv_base { + + public: + + // default constructor + MCPos2ImageUtils() + : larcv::larcv_base("MCPos2ImageUtils"), + psce(nullptr) + { + psce = new larutil::SpaceChargeMicroBooNE; + }; + + + // default destructor + virtual ~MCPos2ImageUtils() { + delete psce; + }; + + static MCPos2ImageUtils* Get() { + if ( !_p_singleton ) { + _p_singleton = new MCPos2ImageUtils; + } + return _p_singleton; + }; + + + std::vector< std::vector > + getRecoSpacepoints( const larlite::mctrack& track, + bool apply_t0_shift=true, + bool apply_sce=true ); + + std::vector + truepos_to_recopos( const float x_cm, const float y_cm, const float z_cm, + const float t_ns, + bool apply_t0_shift=true, + bool apply_sce=true ); + + std::vector + get_sce_shifted_pos( const float x, const float y, const float z ); + + + std::vector + truepos_to_imagepos( const float x, const float y, const float z, + const float t_ns, bool apply_sce ); + + std::vector + to_imagepos( const float x, const float y, const float z, const float t_ns ); + + larutil::SpaceChargeMicroBooNE* psce; + + private: + + static MCPos2ImageUtils* _p_singleton; + + }; + +} +} + +#endif diff --git a/ublarcvapp/MCTools/NeutrinoPixelFilter.cxx b/ublarcvapp/MCTools/NeutrinoPixelFilter.cxx index 3e0b7d5..afbba5e 100644 --- a/ublarcvapp/MCTools/NeutrinoPixelFilter.cxx +++ b/ublarcvapp/MCTools/NeutrinoPixelFilter.cxx @@ -4,9 +4,9 @@ #include "larcv/core/DataFormat/IOManager.h" #include "larcv/core/DataFormat/EventImage2D.h" -// #include "DataFormat/storage_manager.h" -// #include "DataFormat/mctrack.h" -// #include "DataFormat/mcshower.h" +// #include "larlite/DataFormat/storage_manager.h" +// #include "larlite/DataFormat/mctrack.h" +// #include "larlite/DataFormat/mcshower.h" namespace ublarcvapp { namespace mctools { diff --git a/ublarcvapp/MCTools/NeutrinoVertex.cxx b/ublarcvapp/MCTools/NeutrinoVertex.cxx index d88c5d7..920818f 100644 --- a/ublarcvapp/MCTools/NeutrinoVertex.cxx +++ b/ublarcvapp/MCTools/NeutrinoVertex.cxx @@ -1,7 +1,7 @@ #include "NeutrinoVertex.h" -#include "LArUtil/Geometry.h" -#include "DataFormat/mctruth.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/DataFormat/mctruth.h" #include "crossingPointsAnaMethods.h" diff --git a/ublarcvapp/MCTools/NeutrinoVertex.h b/ublarcvapp/MCTools/NeutrinoVertex.h index 4eeca9b..5b373d3 100644 --- a/ublarcvapp/MCTools/NeutrinoVertex.h +++ b/ublarcvapp/MCTools/NeutrinoVertex.h @@ -10,8 +10,8 @@ #include // larlite/core -#include "DataFormat/storage_manager.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" namespace ublarcvapp { namespace mctools { diff --git a/ublarcvapp/MCTools/TruthShowerTrunkSCE.h b/ublarcvapp/MCTools/TruthShowerTrunkSCE.h index 999da9a..1e86ec2 100644 --- a/ublarcvapp/MCTools/TruthShowerTrunkSCE.h +++ b/ublarcvapp/MCTools/TruthShowerTrunkSCE.h @@ -2,9 +2,9 @@ #define __UBLARCVAPP_MCTOOLS_TRUTHSHOWERTRUNKSCE_H__ #include -#include "LArUtil/SpaceChargeMicroBooNE.h" -#include "DataFormat/mcshower.h" -#include "DataFormat/track.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/mcshower.h" +#include "larlite/DataFormat/track.h" #include "larcv/core/Base/larcv_base.h" namespace ublarcvapp { diff --git a/ublarcvapp/MCTools/TruthTrackSCE.cxx b/ublarcvapp/MCTools/TruthTrackSCE.cxx index 1350f7d..310141b 100644 --- a/ublarcvapp/MCTools/TruthTrackSCE.cxx +++ b/ublarcvapp/MCTools/TruthTrackSCE.cxx @@ -164,9 +164,15 @@ namespace mctools { const std::vector& pt ) { + if ( pt.size()<3 ) + throw std::runtime_error("TruthTrackSCE::pointLineDistance. test pt length < 3"); + if ( linept1.size()<3 ) + throw std::runtime_error("TruthTrackSCE::pointLineDistance. linept1 length < 3"); + if ( linept2.size()<3 ) + throw std::runtime_error("TruthTrackSCE::pointLineDistance. linept2 length < 3"); - std::vector d1(3); - std::vector d2(3); + std::vector d1(3,0); + std::vector d2(3,0); float len1 = 0.; float linelen = 0.; @@ -185,7 +191,7 @@ namespace mctools { } // cross-product - std::vector d1xd2(3); + std::vector d1xd2(3,0); 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]; diff --git a/ublarcvapp/MCTools/TruthTrackSCE.h b/ublarcvapp/MCTools/TruthTrackSCE.h index 93ce8e5..a463bd4 100644 --- a/ublarcvapp/MCTools/TruthTrackSCE.h +++ b/ublarcvapp/MCTools/TruthTrackSCE.h @@ -2,9 +2,9 @@ #define __UBLARCVAPP_MCTOOLS_TRUTHTRACK_SCE_H__ #include -#include "LArUtil/SpaceChargeMicroBooNE.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/track.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/track.h" #include "larcv/core/Base/larcv_base.h" namespace ublarcvapp { diff --git a/ublarcvapp/MCTools/crossingPointsAnaMethods.cxx b/ublarcvapp/MCTools/crossingPointsAnaMethods.cxx index 7fa714b..75696f6 100644 --- a/ublarcvapp/MCTools/crossingPointsAnaMethods.cxx +++ b/ublarcvapp/MCTools/crossingPointsAnaMethods.cxx @@ -6,11 +6,11 @@ #include "TTree.h" #include "TLorentzVector.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/trigger.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/trigger.h" // ublarcvapp #include "ublarcvapp/UBWireTool/UBWireTool.h" @@ -60,6 +60,30 @@ namespace mctools { return tick; } + /** + * @brief get TPC tick from MC step info. do not apply drift time. + * + */ + float CrossingPointsAnaMethods::getTrueTick( const std::vector& step, const float trig_time, + const larutil::SpaceChargeMicroBooNE* psce ) { + // Function returns the tick time of a MC step point + // if SCE pointer is null, we do not correct for the space charge + + std::vector dpos(3,0); + if ( psce ) { + std::vector pos_offset = psce->GetPosOffsets( step[1], step[2], step[3] ); + dpos[0] = step[1] - pos_offset[0] + 0.7; + } + else { + dpos[0] = step[1]; + } + + const float cm_per_tick = ::larutil::LArProperties::GetME()->DriftVelocity()*0.5; + float tick = ( step[0]*1.0e-3 - (trig_time-4050.0) )/0.5 + 3200.0; + + return tick; + } + /** * doesTrackCrossImageBoundary * diff --git a/ublarcvapp/MCTools/crossingPointsAnaMethods.h b/ublarcvapp/MCTools/crossingPointsAnaMethods.h index 9084367..6b5fa8c 100644 --- a/ublarcvapp/MCTools/crossingPointsAnaMethods.h +++ b/ublarcvapp/MCTools/crossingPointsAnaMethods.h @@ -4,8 +4,8 @@ #include // larlite -#include "DataFormat/mctrack.h" -#include "DataFormat/mctrajectory.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/mctrajectory.h" class TTree; @@ -34,6 +34,10 @@ namespace mctools { static float getTick( const larlite::mcstep& step, const float trig_time=4050.0, const larutil::SpaceChargeMicroBooNE* psce=nullptr ); + static float getTrueTick( const std::vector& step, const float trig_time=4050.0, + const larutil::SpaceChargeMicroBooNE* psce=nullptr ); + + static int doesTrackCrossImageBoundary( const larlite::mctrack& track, const larcv::ImageMeta& meta, const float trig_time, const larutil::SpaceChargeMicroBooNE* psce=nullptr ); diff --git a/ublarcvapp/MCTools/test/run_flashmatcher.py b/ublarcvapp/MCTools/test/run_flashmatcher.py new file mode 100644 index 0000000..877dea0 --- /dev/null +++ b/ublarcvapp/MCTools/test/run_flashmatcher.py @@ -0,0 +1,109 @@ +import os,sys,argparse + +parser = argparse.ArgumentParser("Test FlashMatcher") +parser.add_argument("-ill", "--input-larlite",required=True,type=str,help="Input larlite mcinfo file") +# want another argument for opflash file +parser.add_argument("-opreco", "--input-opreco",required=True,type=str,help="Input larlite opreco file") +#parser.add_argument("-iv", "--input-voxelfile",required=True,type=str,help="Input voxeldata file") +parser.add_argument("-out", "--output_file",required=True,type=str,help="Output file") +#parser.add_argument("-ilcv","--input-larcv",required=True,type=str,help="Input LArCV file") +#parser.add_argument("-adc", "--adc",type=str,default="wire",help="Name of tree with Wire ADC values [default: wire]") +#parser.add_argument("-tb", "--tick-backward",action='store_true',default=False,help="Input LArCV data is tick-backward [default: false]") + +args = parser.parse_args() + +import ROOT as rt +from larlite import larlite +#from larcv import larcv +from ublarcvapp import ublarcvapp + +rt.gROOT.ProcessLine( "gErrorIgnoreLevel = 3002;" ) + +""" +test script that demos the Flash Matcher class. +""" + +rt.gStyle.SetOptStat(0) + +ioll = larlite.storage_manager( larlite.storage_manager.kREAD ) +ioll.add_in_filename( args.input_larlite ) +ioll.open() + +opio = larlite.storage_manager( larlite.storage_manager.kREAD ) +opio.add_in_filename( args.input_opreco ) +opio.open() + +f = TFile(args.input_voxelfile,"READ") +print("passed tfile part") + +#voxio = larlite.storage_manager( larlite.storage_manager.kREAD ) +#voxio.add_in_filename( args.input_voxelfile ) +#voxio.open() + +outio = larlite.storage_manager( larlite.storage_manager.kWRITE ) +outio.set_out_filename( args.output_file ) +outio.open() + +nentries = ioll.get_entries() +print("Number of entries: ",nentries) + +print("Start loop.") +fmutil = ublarcvapp.mctools.FlashMatcher() +#vtxutil = ublarcvapp.mctools.NeutrinoVertex +#print(vtxutil) +print(fmutil) + +#c = rt.TCanvas("c","c",1200,1800) +#c.Divide(1,3) + +listy = [] + +# for testing: +#match = fmutil.matchTicks( 100.0, [], 1 ) +#print(match) + +print("isCosmic from ctor: ",fmutil.isCosmic) + +fmutil.initialize( voxio ) + +#for ientry in range( nentries ): +for ientry in range( 3 ): + + print() + print("==========================") + print("===[ EVENT ",ientry," ]===") + ioll.go_to(ientry) + opio.go_to(ientry) + + numTracks = fmutil.numTracks( ioll ) + + for i in range( 0, numTracks, 1 ): + track_tick = fmutil.grabTickFromMCTrack( ioll, i ) + + producer = fmutil.producer + isCosmic = fmutil.isCosmic + + print("Now isCosmic has been set to: ",fmutil.isCosmic) + print(producer) + + op_tick = fmutil.grabTickFromOpflash( opio ) + # fmtrack_tick = vtxutil.getImageCoords( ioll ) + print("track_tick",track_tick) + print("op_tick",op_tick) + # iolcv.read_entry(ientry) + match = fmutil.matchTicks( track_tick, op_tick ) + print("Found match: ", match ) + + if match != -999.999 and match != 999.999 and track_tick != -999.997: + fmutil.process( ioll ) + + if isCosmic == 1 and match != -999.999 and track_tick != -999.997: + listy.append( match - track_tick ) + +print("list: ", listy) + +fmutil.finalize() + +print("size of list: ", len(listy)) + +print("=== FIN ==") diff --git a/ublarcvapp/MCTools/test/run_mcparticlegraph.py b/ublarcvapp/MCTools/test/run_mcparticlegraph.py new file mode 100755 index 0000000..60a65e9 --- /dev/null +++ b/ublarcvapp/MCTools/test/run_mcparticlegraph.py @@ -0,0 +1,85 @@ +#!/bin/env python3 +from __future__ import print_function +import os,sys,argparse + +parser = argparse.ArgumentParser("Test MCPixelPGraph") +parser.add_argument("-ill", "--input-larlite",required=True,type=str,help="Input larlite file") +#parser.add_argument("-adc", "--adc",type=str,default="wire",help="Name of tree with Wire ADC values [default: wire]") +#parser.add_argument("-tb", "--tick-backward",action='store_true',default=False,help="Input LArCV data is tick-backward [default: false]") +parser.add_argument("-d", "--debug", action='store_true', default=False, help="Run in debug mode") +parser.add_argument('-n', "--nentries", required=False, type=int, default=-1, help="number of entries to run") +parser.add_argument('-e', "--entry", required=False, type=int, default=-1, help="start at given entry number") +#parser.add_argument('-v', "--vis", required=False, default=False, action='store_true', help="if flag provided, will visualize event") +parser.add_argument("--cluster-nu",action='store_true',default=False, help="Cluster Nu particles together.") +parser.add_argument('-p','--pause',action='store_true',default=False, help="If provided, pause after each entry") +args = parser.parse_args() + +import ROOT as rt +#from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp + +""" +test script that demos the MCPixelPGraph class. +""" + +rt.gStyle.SetOptStat(0) + +ioll = larlite.storage_manager( larlite.storage_manager.kREAD ) +ioll.set_data_to_read( "mctrack", "mcreco" ) # larlite.data.kMCTrack +ioll.set_data_to_read( "mcshower", "mcreco" ) # larlite.data.kMCShower +ioll.set_data_to_read( "mctruth", "generator" ) # larlite.data.kMCTruth +ioll.add_in_filename( args.input_larlite ) +ioll.open() + +tot_nentries = ioll.get_entries() +start_entry = 0 +print("Number of entries: ",tot_nentries) +if args.entry > 0: + start_entry = args.entry +if args.nentries>0: + nentries = args.nentries +else: + nentries = tot_nentries +end_entry = start_entry + nentries +if end_entry>tot_nentries: + end_entry = tot_nentries + +print("Start loop.") + +mcpg = ublarcvapp.mctools.MCParticleGraph() +if args.debug: + mcpg.set_verbosity( "debug" ) +else: + mcpg.set_verbosity( "info" ) + + +for ientry in range( start_entry, end_entry ): + + print() + print("="*80) + print("===[ EVENT ",ientry," ]===") + ioll.go_to(ientry) + + mcpg.clear() + if args.cluster_nu: + mcpg.cluster_nu_particles( True ) + else: + mcpg.cluster_nu_particles( False ) + + mcpg.buildgraph( ioll ) + + print("================================================",flush=True) + print("PARSING PARTICLE GRAPH ONLY: No pixel matching ",flush=True) + print("================================================",flush=True) + #print("ALL NODE INFO [NO NU] --------------------------") + #mcpg.printAllNodeInfo() + #print(" -----------------------------------------------") + print("CONSTRUCTED PARTICLE GRAPH",flush=True) + mcpg.printGraph(0,False) + #print("====================================================",flush=True) + if args.pause: + print("[ENTER] to continue",flush=True) + input() + +#print("=== FIN ==",flush=True) diff --git a/ublarcvapp/MCTools/test/run_mcpixelpgraph.py b/ublarcvapp/MCTools/test/run_mcpixelpgraph.py old mode 100644 new mode 100755 index 3a3750f..a0ccc95 --- a/ublarcvapp/MCTools/test/run_mcpixelpgraph.py +++ b/ublarcvapp/MCTools/test/run_mcpixelpgraph.py @@ -1,11 +1,16 @@ +#!/bin/env python3 +from __future__ import print_function import os,sys,argparse parser = argparse.ArgumentParser("Test MCPixelPGraph") parser.add_argument("-ill", "--input-larlite",required=True,type=str,help="Input larlite file") -parser.add_argument("-ilcv","--input-larcv",required=True,type=str,help="Input LArCV file") +parser.add_argument("-ilcv","--input-larcv",required=False,default=None,type=str,help="Input LArCV file") parser.add_argument("-adc", "--adc",type=str,default="wire",help="Name of tree with Wire ADC values [default: wire]") parser.add_argument("-tb", "--tick-backward",action='store_true',default=False,help="Input LArCV data is tick-backward [default: false]") parser.add_argument("-d", "--debug", action='store_true', default=False, help="Run in debug mode") +parser.add_argument('-n', "--nentries", required=False, type=int, default=-1, help="number of entries to run") +parser.add_argument('-e', "--entry", required=False, type=int, default=-1, help="start at given entry number") +parser.add_argument('-v', "--vis", required=False, default=False, action='store_true', help="if flag provided, will visualize event") args = parser.parse_args() import ROOT as rt @@ -20,58 +25,252 @@ rt.gStyle.SetOptStat(0) ioll = larlite.storage_manager( larlite.storage_manager.kREAD ) +ioll.set_data_to_read( "mctrack", "mcreco" ) # larlite.data.kMCTrack +ioll.set_data_to_read( "mcshower", "mcreco" ) # larlite.data.kMCShower +ioll.set_data_to_read( "mctruth", "generator" ) # larlite.data.kMCTruth ioll.add_in_filename( args.input_larlite ) ioll.open() -if args.tick_backward: - iolcv = larcv.IOManager( larcv.IOManager.kREAD, "larcv", larcv.IOManager.kTickBackward ) +if args.input_larcv is not None: + # has larcv images to get pixels + HAS_LARCV = True + if args.tick_backward: + iolcv = larcv.IOManager( larcv.IOManager.kREAD, "larcv", larcv.IOManager.kTickBackward ) + else: + iolcv = larcv.IOManager( larcv.IOManager.kREAD, "larcv", larcv.IOManager.kTickForward ) + iolcv.add_in_file( args.input_larcv ) + iolcv.reverse_all_products() + iolcv.initialize() else: - iolcv = larcv.IOManager( larcv.IOManager.kREAD, "larcv", larcv.IOManager.kTickForward ) -iolcv.add_in_file( args.input_larcv ) -iolcv.reverse_all_products() -iolcv.initialize() + HAS_LARCV = False + +print("HAS LARCV: ",HAS_LARCV) +tot_nentries = ioll.get_entries() +start_entry = 0 +print("Number of entries: ",tot_nentries) +if args.entry > 0: + start_entry = args.entry +if args.nentries>0: + nentries = args.nentries +else: + nentries = tot_nentries +end_entry = start_entry + nentries +if end_entry>tot_nentries: + end_entry = tot_nentries + -nentries = iolcv.get_n_entries() -print "Number of entries: ",nentries -#nentries = 10 -print "Start loop." +print("Start loop.") mcpg = ublarcvapp.mctools.MCPixelPGraph() +mcpg_nu = ublarcvapp.mctools.MCPixelPGraph() if args.debug: - mcpg.set_verbosity( larcv.msg.kDEBUG ) + mcpg.set_verbosity( "debug" ) + mcpg_nu.set_verbosity( "debug" ) else: - mcpg.set_verbosity( larcv.msg.kINFO ) -mcpg.set_adc_treename( args.adc ) + mcpg.set_verbosity( "info" ) + mcpg_nu.set_verbosity( "info" ) -tmp = rt.TFile("temp.root","recreate") +if HAS_LARCV: + mcpg.set_adc_treename( args.adc ) -c = rt.TCanvas("c","c",1200,1800) -c.Divide(1,3) +if HAS_LARCV: + tmp = rt.TFile("temp.root","recreate") + c = rt.TCanvas("c","c",2100,1500) + c.Divide(3,3) -for ientry in xrange( nentries ): +#print("[ENTER] to Start") +#input() - print - print "==========================" - print "===[ EVENT ",ientry," ]===" +for ientry in range( start_entry, end_entry ): + + print() + print("==========================") + print("===[ EVENT ",ientry," ]===") ioll.go_to(ientry) - iolcv.read_entry(ientry) - ev_adc = iolcv.get_data( larcv.kProductImage2D, args.adc ) - print "number of images: ",ev_adc.Image2DArray().size() - adc_v = ev_adc.Image2DArray() - for p in xrange(adc_v.size()): - print " image[",p,"] ",adc_v[p].meta().dump() + mcpg.clear() + mcpg_nu.clear() + mcpg.set_cluster_neutrino_particles(False) + mcpg_nu.set_cluster_neutrino_particles(True) + + if not HAS_LARCV: + mcpg.buildgraphonly( ioll ) + mcpg_nu.buildgraphonly( ioll ) + else: + print("HAS_LARCV: get images needed for pixel matching to particles") + #mcpg.buildgraphonly( ioll ) + iolcv.read_entry(ientry) + ev_adc = iolcv.get_data( "image2d", args.adc ) + ev_instance = iolcv.get_data( "image2d", "instance" ) + ev_ancestor = iolcv.get_data( "image2d", "ancestor" ) + ev_segment = iolcv.get_data( "image2d", "segment" ) + ev_larflow = iolcv.get_data( "image2d", "larflow" ) + print("number of adc images: ",ev_adc.Image2DArray().size()) + print("number of larflow images: ",ev_larflow.Image2DArray().size()) + adc_v = ev_adc.Image2DArray() + for p in range(adc_v.size()): + print(" image[",p,"] ",adc_v[p].meta().dump()) + mcpg_nu.buildgraph( iolcv, ioll ) + + + photon_starts = {} + if HAS_LARCV and False: + for i in range(mcpg_nu.node_v.size()): + node = mcpg_nu.node_v.at(i) + if node.pid!=22: + continue; + print("running fixingPhotonStartPoints(...) for photons") + gamma_start = mcpg_nu.fixingPhotonStartPoints( node, + ev_instance.as_vector(), + ev_ancestor.as_vector(), + ev_adc.as_vector(), + ev_larflow.as_vector() ) + photon_starts[node.tid] = gamma_start + + ## isolate events + stop = False + for inode in range(mcpg_nu.node_v.size()): + node = mcpg_nu.node_v.at(inode) + if node.pid in [22]: + npixsum = 0 + for p in range(node.pix_vv.size()): + npixsum += node.pix_vv[p].size() + if npixsum>1: + stop=True + print("STOP TO VISUALIZE/DUMP MCPG INFO: ",stop) + if not stop: + continue + + if args.debug or args.vis: + if not HAS_LARCV: + print("================================================") + print("PARSING PARTICLE GRAPH ONLY: No pixel matching ") + print("================================================") + print("ALL NODE INFO [NO NU] --------------------------") + mcpg.printAllNodeInfo() + print(" -----------------------------------------------") + print("CONSTRUCTED PARTICLE GRAPH [NO NU]") + mcpg.printGraph(0,False) + print("====================================================") + else: + #print("================================================") + #print("PARSING PARTICLE GRAPH ONLY: No pixel matching ") + #print("================================================") + #print("CONSTRUCTED PARTICLE GRAPH [NO NU]") + #mcpg.printGraph(0,False) + #print("====================================================") + + print("====================================================") + print("CONSTRUCTED PARTICLE GRAPH [WITH NU VERTEX GROUPING]") + mcpg_nu.printGraph(0,False) + print("====================================================") + - # make histogram - hist_v = larcv.rootutils.as_th2d_v( adc_v, "hentry%d"%(ientry) ) - for ih in xrange(adc_v.size()): - h = hist_v[ih] - h.GetZaxis().SetRangeUser(0,100) + # make histogram + marker_v = [] + #hist_v = mcpg_nu.makeTH2D( "hentry%d"%(ientry) ) + hist_v = larcv.rootutils.as_th2d_v( ev_adc.as_vector(), "hentry%d"%(ientry) ) + segment_v = larcv.rootutils.as_th2d_v( ev_segment.as_vector(), "hsegment%d"%(ientry)) + instance_v = larcv.rootutils.as_th2d_v( ev_instance.as_vector(), "hinstance%d"%(ientry)) + ancestor_v = larcv.rootutils.as_th2d_v( ev_ancestor.as_vector(), "hancestor%d"%(ientry)) - mcpg.buildgraph( iolcv, ioll ) - mcpg.printAllNodeInfo() - #mcpg.printGraph() + c.cd() + c.Draw() + c.Update() + for ih in range(hist_v.size()): + c.cd(3*ih+0+1) + h = hist_v[ih] + h.Draw("colz") + h.GetZaxis().SetRangeUser(0,300) + + for i in range(mcpg_nu.node_v.size()): + node = mcpg_nu.node_v.at(i) + if node.pid not in [22,11,-11]: + continue + + x = node.imgpos4_edep[ih] + y = node.imgpos4_edep[3] + #print("nodeidx=",node.nodeidx," pid=",node.pid," (x,y)=",(x,y)) + m = rt.TMarker( x, y, 4 ) + m.SetNDC(False) + m.SetMarkerColor(rt.kMagenta) + m.Draw() + marker_v.append(m) + + if node.tid in photon_starts: + x2 = photon_starts[node.tid][4+ih] + y2 = photon_starts[node.tid][4+3] + m2 = rt.TMarker( x2, y2, 4 ) + m2.SetNDC(False) + m2.SetMarkerColor(rt.kRed) + m2.Draw() + marker_v.append(m2) + + x4 = node.imgpos4_start[ih] + y4 = node.imgpos4_start[3] + m4 = rt.TMarker( x4, y4, 5 ) + m4.SetNDC(False) + m4.SetMarkerSize(3) + m4.SetMarkerColor(rt.kBlack) + #print("x4: ",(x4,y4)) + m4.Draw() + marker_v.append(m4) + + #if ih==2: + # print("DUMP PIXELS [plane=2] for node.nodeidx=",node.nodeidx," trackid=",node.tid," pid=",node.pid) + pix_v = node.pix_vv.at(ih) + npix = int(pix_v.size()/2) + for ipix in range(npix): + xbin = int(pix_v.at(2*ipix+1)) + ybin = int((pix_v.at(2*ipix)-2400)/6.0) + #if ih==2: + # print(" ipix[",ipix,"]: (xbin,ybin)=",(xbin,ybin)," (wire,tick)=",(pix_v.at(2*ipix+1),pix_v.at(2*ipix))) + ancestor_v[ih].SetBinContent( xbin+1, ybin+1, node.tid ) + + c.cd(3*ih+1+1) + #instance_v[ih].SetTitle("instance plane[%d]"%(ih)) + #instance_v[ih].Draw("colz") + segment_v[ih].SetTitle("segment plane[%d]"%(ih)) + segment_v[ih].Draw("colz") + c.cd(3*ih+2+1) + ancestor_v[ih].SetTitle("ancestor plane[%d]"%(ih)) + ancestor_v[ih].Draw("colz") + + c.Update() + # end of loop + c.Update() + + # draw individual showers + cpart = [] + for inode in range(mcpg_nu.node_v.size()): + node = mcpg_nu.node_v.at(inode) + if node.pid not in [22]: + continue + print("Make canvas of particle, tid=",node.tid) + pixsum_v = mcpg_nu.getTruePhotonTrunkPlanePixelSums( node.tid ) + if pixsum_v.size()>=3: + print(" pixelsum (",pixsum_v[0],", ",pixsum_v[1],", ",pixsum_v[2],")") + meta = ev_adc.as_vector().at(2).meta() + hpart = rt.TH2D("hnode%d"%(inode),"",3456,0,3456,1008,2400,2400+6*1008) + pix_v = node.pix_vv.at(ih) + npix = int(pix_v.size()/2) + for ipix in range(npix): + xbin = int(pix_v.at(2*ipix+1)) + ybin = int((pix_v.at(2*ipix)-2400)/6.0) + hpart.SetBinContent( xbin+1, ybin+1, 1 ) + cpt = rt.TCanvas("cnode%d"%(inode),"node[%d] pid[%d] tid[%d]"%(inode,node.pid,node.tid),800,600) + hpart.GetZaxis().SetRangeUser(0,10.0) + hpart.Draw("colz") + cpt.Update() + cpart.append(cpt) + cpart.append(hpart) + + + print("[ENTER] to continue") + input() + if True: + continue #primaries = mcpg.getPrimaryParticles() primaries = mcpg.node_v @@ -79,14 +278,14 @@ # get primary electron, make tgraph of pixels graph_v = [] bbox_v = [] - for i in xrange(primaries.size()): + for i in range(primaries.size()): node = primaries.at(i) - print "primary pid[",node.pid,"]" + print("primary pid[",node.pid,"]") if node.pid in [11,2212,13,-13,22,211,-211]: - print " making tgraph for pid=",node.pid + print(" making tgraph for pid=",node.pid) e_v = [] bb_v = [] - for p in xrange(3): + for p in range(3): if node.pix_vv[p].size()==0: e_v.append(None) bb_v.append(None) @@ -100,7 +299,7 @@ bb.SetFillStyle(0) bb.SetLineWidth(2) - for j in xrange( node.pix_vv[p].size()/2 ): + for j in range( node.pix_vv[p].size()/2 ): g.SetPoint(j, node.pix_vv[p][2*j+1], node.pix_vv[p][2*j] ) # wire, tick g.SetMarkerStyle(20) g.SetMarkerSize(0.5) @@ -130,10 +329,10 @@ graph_v.append(e_v) bbox_v.append(bb_v) - print "num graphs: ",len(graph_v) + print("num graphs: ",len(graph_v)) #draw canvas - for p in xrange(3): + for p in range(3): c.cd(p+1) hist_v[p].Draw("colz") for e_v in graph_v: @@ -144,8 +343,8 @@ bb_v[p].Draw() c.Update() - print "[enter to continue]" + print("[enter to continue]") raw_input() -print "=== FIN ==" +print("=== FIN ==") diff --git a/ublarcvapp/MCTools/test/test_mcpixellabelmaker.py b/ublarcvapp/MCTools/test/test_mcpixellabelmaker.py new file mode 100644 index 0000000..0a1f1da --- /dev/null +++ b/ublarcvapp/MCTools/test/test_mcpixellabelmaker.py @@ -0,0 +1,43 @@ +import os,sys +import argparse + +parser = argparse.ArgumentParser("Test MCPixelLabelMaker") +parser.add_argument("-i", "--input-dlmerged",required=True,type=str,help="Input larlite file") +parser.add_argument('-e', "--entry", required=True, type=int, default=0, help="entry to process") +parser.add_argument("-adc", "--adc", required=False, type=str,default="wiremc",help="Name of tree with Wire ADC values [default: wiremc]") +parser.add_argument("-tb", "--tick-backward",action='store_true',default=False,help="Input LArCV data is tick-backward [default: false]") +parser.add_argument("-d", "--debug", action='store_true', default=False, help="Run in debug mode") +#parser.add_argument('-n', "--nentries", required=False, type=int, default=-1, help="number of entries to run") +parser.add_argument('-o', "--output-hdf5", required=False, default="", help="if provided, will save data to HDF5 file") +args = parser.parse_args() + + +from larlite import larlite +from larcv import larcv +from ublarcvapp import ublarcvapp + +#input_dlmerged = "/mnt/ddrive/data/ub_on_tufts/corsika_bnb_nu_pi0/dlmerged_coriska_bnb_nu_pi0_fileno000001.root" +input_dlmerged = args.input_dlmerged + +ENTRY=args.entry + +mcpixel_label_maker = ublarcvapp.mctools.MCPixelLabelMaker() +mcpixel_label_maker.set_verbosity(1) + +ioll = larlite.storage_manager( larlite.storage_manager.kREAD ) +ioll.add_in_filename( input_dlmerged ) +ioll.set_verbosity(2) +ioll.open() + +iolcv = larcv.IOManager( larcv.IOManager.kREAD, "larcv" ) +iolcv.add_in_file( input_dlmerged ) +iolcv.set_verbosity(2) +iolcv.initialize() + +ioll.go_to(ENTRY) +iolcv.read_entry(ENTRY) + +mcpixel_label_maker.process( ioll, iolcv, args.adc ) + +if args.output_hdf5!="": + mcpixel_label_maker.export_as_hdf(args.output_hdf5) diff --git a/ublarcvapp/MCTools/test/vis_mcpixellabels.py b/ublarcvapp/MCTools/test/vis_mcpixellabels.py new file mode 100644 index 0000000..99169d8 --- /dev/null +++ b/ublarcvapp/MCTools/test/vis_mcpixellabels.py @@ -0,0 +1,198 @@ +import os,sys +import argparse +import h5py +import numpy as np +import dash +import dash_core_components as dcc +import dash_html_components as html +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +parser = argparse.ArgumentParser("Visualize HDF5 output from MCPixelLabels") +parser.add_argument("-i", "--input",required=True,type=str,help="Input HDF5 file.") +parser.add_argument("-c", "--colorby", default='trackid', help="Color mode. Option: [instance,ancestor,edep]") +parser.add_argument("-p", "--pos-mode", default="true",type=str,help="Position mode. Option: [true,reco]") +args = parser.parse_args() + +import lardly +from lardly.detectoroutline import DetectorOutline + +fh5 = h5py.File(args.input, 'r') + +opacity=0.8 +marker_size=1.0 + +colorby_options = ['edep','trackid'] +pos_mode_options = ['true','reco'] + +if args.colorby not in colorby_options: + print("Color Mode option given is invalid. Options: ",colorby_options) + sys.exit(0) + +if args.pos_mode not in pos_mode_options: + print("Position Mode option invalid. Options: ",pos_mode_options) + sys.exit(0) + +#colorby = 'edep' +#colorby = 'trackid' +#colorby = 'hasmatch' +colorby = args.colorby +pos_var = args.pos_mode +#pos_var='true' +#pos_var='reco' + +#NMAX_RECO_PTS=50000 +NMAX_RECO_PTS=-1 + +triplet_truth = fh5['mcpixel_labels'] + +columns = ['pos_x','pos_y','pos_z', + 'pos_x_reco','pos_y_reco','pos_z_reco', + 'edep', + 'trackid', + 'pid', + 'aid', + 'origin', + 'uwire', + 'vwire', + 'ywire', + 'tick', + 'row'] + +data = {} + +for col in columns: + npts = len(triplet_truth[col]) + data[col] = np.array( triplet_truth[col], dtype=np.float32 ) + if len(data[col].shape)==1: + data[col] = data[col].reshape((npts,1)) + print(col,": ",data[col].shape) + +detdata = DetectorOutline() +customdata = np.concatenate( [data['pid'],data['trackid'],data['aid'],data['edep']],axis=1 ) +print("customdata: ",customdata.shape) + +hovertemplate = """ +x: %{x:.1f}
+y: %{y:.1f}
+z: %{z:.1f}
+PID: %{customdata[0]:d}
+TID: %{customdata[1]:d}
+AID: %{customdata[2]:d}
+edep: %{customdata[3]:.3f}, %{customdata[4]:.3f} , %{customdata[5]:.3f} MeV
+""" + +if pos_var=='reco': + pos_var_x = 'pos_x_reco' + pos_var_y = 'pos_y_reco' + pos_var_z = 'pos_z_reco' +else: + pos_var_x = 'pos_x' + pos_var_y = 'pos_y' + pos_var_z = 'pos_z' + +source = data + +simch_plots = [] +if colorby=='edep': + simch_plot = { + "type":"scatter3d", + "x":source[pos_var_x][:,0], + "y":source[pos_var_y][:,0], + "z":source[pos_var_z][:,0], + "mode":"markers", + "name":f"edep", + "hovertemplate":hovertemplate, + "customdata":customdata, + "marker":{"color":source['edep'][:,0],"opacity":opacity,"size":marker_size,'colorscale':'Viridis','cmin':0.0,'cmax':5.0} + } + simch_plots.append(simch_plot) +elif colorby=='trackid': + itrackid = source['trackid'][:,0].astype(np.int64) + unique_ids = np.unique( itrackid ) + for tid in unique_ids: + if tid<=0: + continue + mask = itrackid==tid + xcolor = np.random.randint(0,255,3) + scolor=f'rgba({xcolor[0]},{xcolor[1]},{xcolor[2]},1)' + simch_plot = { + "type":"scatter3d", + "x":source[pos_var_x][mask[:],0], + "y":source[pos_var_y][mask[:],0], + "z":source[pos_var_z][mask[:],0], + "mode":"markers", + "name":f"tid[{tid}]", + "hovertemplate":hovertemplate, + "customdata":customdata[mask[:],:], + "marker":{"color":scolor,"opacity":opacity,"size":marker_size} + } + simch_plots.append(simch_plot) +# elif colorby=='hasmatch': +# simch_plot = { +# "type":"scatter3d", +# "x":source['pos_x'][:,0], +# "y":source['pos_y'][:,0], +# "z":source['pos_z'][:,0], +# "mode":"markers", +# "name":f"recopts", +# "marker":{"color":source['hasmatch'][:,0],"opacity":opacity,"size":marker_size,'colorscale':'Viridis'}, +# #"marker":{"color":"rgba(220,220,220,1)","opacity":opacity,"size":marker_size} +# } +# simch_plots.append( simch_plot ) +else: + print("unknown color option: ",colorby) + sys.exit(0) + +traces = detdata.getlines()+simch_plots + +app = dash.Dash( + __name__, + meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}], +) + +server = app.server + +axis_template = { + "showbackground": True, + "backgroundcolor": "#141414", + "gridcolor": "rgb(255, 255, 255)", + "zerolinecolor": "rgb(255, 255, 255)", +} + +plot_layout = { + "title": "", + "height":800, + "margin": {"t": 0, "b": 0, "l": 0, "r": 0}, + "font": {"size": 12, "color": "white"}, + "showlegend": False, + "plot_bgcolor": "#141414", + "paper_bgcolor": "#141414", + "scene": { + "xaxis": axis_template, + "yaxis": axis_template, + "zaxis": axis_template, + "aspectratio": {"x": 1, "y": 1, "z": 4}, + "camera": {"eye": {"x": 2, "y": 2, "z": 2}, + "up":dict(x=0, y=1, z=0)}, + "annotations": [], + }, +} + +app.layout = html.Div( [ + html.Div( [ + dcc.Graph( + id="det3d", + figure={ + "data": traces, + "layout": plot_layout, + }, + config={"editable": True, "scrollZoom": False}, + )], + className="graph__container"), + ] ) + +app.run_server(debug=True) + + + diff --git a/ublarcvapp/ParticleToPixelUtils/CMakeLists.txt b/ublarcvapp/ParticleToPixelUtils/CMakeLists.txt new file mode 100644 index 0000000..c17f6c3 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/CMakeLists.txt @@ -0,0 +1,63 @@ +set(MODULE_NAME ParticleToPixelUtils) + +# library name +set(LIBNAME LArCVApp_${MODULE_NAME}) + +# Collect the headers +set( HEADERS + TrackToSpacePoints.h + ShowerToSpacePoints.h +) + +# Add sources +add_library( ${LIBNAME} SHARED + TrackToSpacePoints.cxx + ShowerToSpacePoints.cxx +) + +# larlite libraries +set(LARLITE_LIBS_USED ${LARLITE_LIBS} ) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} +) + +target_link_libraries( ${LIBNAME} PUBLIC + ${LARLITE_LIBS_USED} + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreProcessor + LArCVApp_UBPhotonLib +) + +# includes for ROOT dictionary gen +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} +) + +# Generate the dictionary +ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) + +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) + +# install libraries and headers +install(TARGETS ${LIBNAME} + EXPORT ublarcvapp + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include/ublarcvapp/${MODULE_NAME}) + +# install files needed for dictionary use in CINT/CLING/PYTHON +install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/lib${LIBNAME}_rdict.pcm + ${CMAKE_CURRENT_BINARY_DIR}/lib${LIBNAME}.rootmap + DESTINATION lib ) \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/LinkDef.h b/ublarcvapp/ParticleToPixelUtils/LinkDef.h new file mode 100644 index 0000000..d7eb936 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/LinkDef.h @@ -0,0 +1,26 @@ +/** \defgroup ParticleToPixelUtils + * + * \brief Utilities for converting particle representations to pixel/space point data + * + * cint script to generate libraries and python bindings. + * Declare namespace & classes you defined + * pragma statement: order matters! Google it ;) + * + */ +#ifdef __CINT__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ namespace ublarcvapp; +#pragma link C++ namespace ublarcvapp::pixelutils; + +#pragma link C++ class ublarcvapp::pixelutils::TrackToSpacePoints+; +#pragma link C++ class ublarcvapp::pixelutils::TrackToSpacePoints::SpacePointCharge+; +#pragma link C++ class ublarcvapp::pixelutils::TrackToSpacePoints::PixelData+; +#pragma link C++ class ublarcvapp::pixelutils::ShowerToSpacePoints+; +#pragma link C++ class ublarcvapp::pixelutils::ShowerToSpacePoints::SpacePointCharge+; +#pragma link C++ class ublarcvapp::pixelutils::ShowerToSpacePoints::PixelData+; + +#endif \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/README.md b/ublarcvapp/ParticleToPixelUtils/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.cxx b/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.cxx new file mode 100644 index 0000000..ba17301 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.cxx @@ -0,0 +1,154 @@ +#include "ShowerToSpacePoints.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include + +namespace ublarcvapp { +namespace pixelutils { + + ShowerToSpacePoints::ShowerToSpacePoints() + : _use_charge_weighting(false), + _npix_processed(0), + _total_charge(0.0) + { + } + + ShowerToSpacePoints::~ShowerToSpacePoints() + { + } + + std::vector + ShowerToSpacePoints::convertShower( + const larlite::larflowcluster& cluster, + const std::vector& adc_v, + const float threshold, + const int dcol, + const int drow) + { + std::vector spacepoints; + + // Reset counters + _npix_processed = 0; + _total_charge = 0.0; + _pixel_map.clear(); + + // Check we have 3 planes + if (adc_v.size() != 3) { + std::cerr << "ShowerToSpacePoints: Expected 3 planes, got " + << adc_v.size() << std::endl; + return spacepoints; + } + + // Get detector parameters + const float driftv = larutil::LArProperties::GetME()->DriftVelocity(); + const float usec_per_tick = 0.5; + float max_tick = adc_v.front().meta().max_y(); + float min_tick = adc_v.front().meta().min_y(); + + // Keep track of processed pixels to avoid double counting + std::set> processed_pixels; // (plane,row,col) + + // Process each larflow3dhit in the cluster + for (size_t ihit = 0; ihit < cluster.size(); ihit++) { + + const larlite::larflow3dhit& hit = cluster[ihit]; + + // Get 3D position from the larflow3dhit + // larflow3dhit inherits from std::vector which stores [x,y,z] + if (hit.size() < 3) { + std::cerr << "ShowerToSpacePoints: larflow3dhit " << ihit + << " has insufficient position data" << std::endl; + continue; + } + + TVector3 pos(hit[0], hit[1], hit[2]); // x,y,z stored in vector + + // Calculate tick from X position + float tick = hit[0]/driftv/usec_per_tick + 3200; + + if (tick < min_tick || tick > max_tick) + continue; + + int row = adc_v.front().meta().row(tick); + + // Collect charge from each wire plane + _pixel_map[ihit] = std::set(); + + for (int p = 0; p < 3; p++) { + int wire = larutil::Geometry::GetME()->WireCoordinate(pos, p); + + // Collect charge from neighboring pixels + for (int dr = -abs(drow); dr <= abs(drow); dr++) { + int r = row + dr; + if (r < 0 || r >= (int)adc_v[p].meta().rows()) + continue; + + for (int dc = -abs(dcol); dc <= abs(dcol); dc++) { + int c = wire + dc; + if (c < 0 || c >= (int)adc_v[p].meta().cols()) + continue; + + float pixval = adc_v[p].pixel(r, c); + if (pixval > threshold) { + // Check if we've already processed this pixel + auto pixel_key = std::make_tuple(p, r, c); + if (processed_pixels.find(pixel_key) == processed_pixels.end()) { + processed_pixels.insert(pixel_key); + _pixel_map[ihit].insert(PixelData(p, r, c, pixval)); + _npix_processed++; + _total_charge += pixval; + } + } + } + } + } + } + + // Now create space points from the collected pixel data + for (size_t ihit = 0; ihit < cluster.size(); ihit++) { + + // Skip hits with no associated pixels + if (_pixel_map.find(ihit) == _pixel_map.end() || _pixel_map[ihit].empty()) + continue; + + const larlite::larflow3dhit& hit = cluster[ihit]; + SpacePointCharge sp; + + // Use the original 3D position or charge-weighted position + // Get position from the std::vector base class + TVector3 original_pos(hit[0], hit[1], hit[2]); + + if (_use_charge_weighting && !_pixel_map[ihit].empty()) { + sp.position = getChargeWeightedPosition(_pixel_map[ihit], adc_v, original_pos); + } else { + sp.position = original_pos; + } + + // Calculate total charge and per-plane charges + sp.charge = 0; + sp.plane_charges.resize(3, 0); + + for (const auto& pix : _pixel_map[ihit]) { + sp.charge += pix.value; + sp.plane_charges[pix.plane] += pix.value; + } + + spacepoints.push_back(sp); + } + + return spacepoints; + } + + TVector3 ShowerToSpacePoints::getChargeWeightedPosition( + const std::set& pixels, + const std::vector& adc_v, + const TVector3& original_position) + { + // For now, just return the original position + // In the future, could implement charge-weighted averaging + // across the pixels to refine the position + return original_position; + } + +} +} \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.h b/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.h new file mode 100644 index 0000000..214c40f --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/ShowerToSpacePoints.h @@ -0,0 +1,122 @@ +#ifndef __UBLARCVAPP_PIXELUTILS_SHOWERTOSPACEPOINTS_H__ +#define __UBLARCVAPP_PIXELUTILS_SHOWERTOSPACEPOINTS_H__ + +#include +#include +#include + +#include "larcv/core/DataFormat/Image2D.h" +#include "larlite/DataFormat/larflowcluster.h" +#include "larlite/DataFormat/larflow3dhit.h" + +#include "TVector3.h" + +namespace ublarcvapp { +namespace pixelutils { + + /** + * @class ShowerToSpacePoints + * @brief Convert larlite::larflowcluster (shower representation) to space points with charge + * + * This class takes a larflowcluster which contains a collection of larflow3dhit objects + * (3D space points), projects them into the 2D wire plane images, and collects the + * charge from neighboring pixels. + */ + class ShowerToSpacePoints { + public: + + /** + * @struct SpacePointCharge + * @brief Container for 3D position with associated charge information + */ + struct SpacePointCharge { + TVector3 position; ///< 3D position in detector coordinates (cm) + float charge; ///< Total charge from neighboring pixels + std::vector plane_charges; ///< Charge on each plane [U,V,Y] + + SpacePointCharge() : charge(0) { plane_charges.resize(3, 0); } + }; + + /** + * @struct PixelData + * @brief Container for pixel location and charge + */ + struct PixelData { + int plane; + int row; + int col; + float value; + + PixelData() : plane(0), row(0), col(0), value(0) {} // Default constructor for ROOT + + PixelData(int p, int r, int c, float v) + : plane(p), row(r), col(c), value(v) {} + + // For std::set uniqueness + bool operator<(const PixelData& other) const { + if (plane != other.plane) return plane < other.plane; + if (row != other.row) return row < other.row; + return col < other.col; + } + }; + + ShowerToSpacePoints(); + ~ShowerToSpacePoints(); + + /** + * @brief Convert a larflowcluster (shower) to space points with charge + * + * @param cluster The larflowcluster containing larflow3dhit objects + * @param adc_v Vector of ADC images for each plane + * @param threshold ADC threshold for pixel selection + * @param dcol Column window around projected point + * @param drow Row window around projected point + * @return Vector of SpacePointCharge objects + */ + std::vector convertShower( + const larlite::larflowcluster& cluster, + const std::vector& adc_v, + const float threshold = 10.0, + const int dcol = 3, + const int drow = 3); + + /** + * @brief Get the number of pixels processed in the last conversion + */ + int getNumPixelsProcessed() const { return _npix_processed; } + + /** + * @brief Get the total charge collected in the last conversion + */ + float getTotalCharge() const { return _total_charge; } + + /** + * @brief Get the pixel data collected for each space point + * @return Map from space point index to set of pixels + */ + const std::map>& getPixelMap() const { return _pixel_map; } + + /** + * @brief Set whether to use charge-weighted position calculation + */ + void setUseChargeWeighting(bool use) { _use_charge_weighting = use; } + + protected: + + /** + * @brief Calculate charge-weighted position from pixel data + */ + TVector3 getChargeWeightedPosition(const std::set& pixels, + const std::vector& adc_v, + const TVector3& original_position); + + bool _use_charge_weighting; + int _npix_processed; + float _total_charge; + std::map> _pixel_map; ///< Map from hit index to pixels + }; + +} +} + +#endif \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.cxx b/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.cxx new file mode 100644 index 0000000..723f391 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.cxx @@ -0,0 +1,355 @@ +#include "TrackToSpacePoints.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include + +namespace ublarcvapp { +namespace pixelutils { + + TrackToSpacePoints::TrackToSpacePoints() + : _use_charge_weighting(false), + _npix_processed(0), + _total_charge(0.0) + { + } + + TrackToSpacePoints::~TrackToSpacePoints() + { + } + + std::vector + TrackToSpacePoints::convertTrack( + const larlite::track& track, + const std::vector& adc_v, + const float threshold, + const int dcol, + const int drow, + const float minstepsize, + const float maxstepsize) + { + std::vector spacepoints; + + // Reset counters + _npix_processed = 0; + _total_charge = 0.0; + + int npts = track.NumberTrajectoryPoints(); + if (npts <= 1) { + std::cerr << "TrackToSpacePoints: Cannot process track with only " + << npts << " point(s)" << std::endl; + return spacepoints; + } + + // Check we have 3 planes + if (adc_v.size() != 3) { + std::cerr << "TrackToSpacePoints: Expected 3 planes, got " + << adc_v.size() << std::endl; + return spacepoints; + } + + const float driftv = larutil::LArProperties::GetME()->DriftVelocity(); + const float usec_per_tick = 0.5; + float max_tick = adc_v.front().meta().max_y(); + float min_tick = adc_v.front().meta().min_y(); + + // Keep track of processed pixels to avoid double counting + std::set> processed_pixels; // (plane,row,col) + + // Map from 3D position to pixel data + std::map> step_pixels; // step index -> pixels + std::map step_positions; // step index -> 3D position + + int global_step = 0; + + // Loop over track segments + for (int ipt = 0; ipt < npts-1; ipt++) { + + TVector3 start = track.LocationAtPoint(ipt); + TVector3 end = track.LocationAtPoint(ipt+1); + TVector3 dir = end - start; + + double segsize = dir.Mag(); + + int nsteps = 1; + if (segsize > minstepsize) { + nsteps = segsize/maxstepsize + 1; + } + + float stepsize = segsize/float(nsteps); + + // Step along the segment + for (int istep = 0; istep <= nsteps; istep++) { + + // Get 3D position along track + TVector3 pos = start + istep*(stepsize/segsize)*dir; + step_positions[global_step] = pos; + + // Calculate tick from X position + int tick = pos[0]/driftv/usec_per_tick + 3200; + + if (tick < min_tick || tick > max_tick) + continue; + + int row = adc_v.front().meta().row(tick); + + // Project into each wire plane + for (int p = 0; p < 3; p++) { + int wire = larutil::Geometry::GetME()->WireCoordinate(pos, p); + + // Collect charge from neighboring pixels + for (int dr = -abs(drow); dr <= abs(drow); dr++) { + int r = row + dr; + if (r < 0 || r >= (int)adc_v[p].meta().rows()) + continue; + + for (int dc = -abs(dcol); dc <= abs(dcol); dc++) { + int c = wire + dc; + if (c < 0 || c >= (int)adc_v[p].meta().cols()) + continue; + + float pixval = adc_v[p].pixel(r, c); + if (pixval > threshold) { + // Check if we've already processed this pixel + auto pixel_key = std::make_tuple(p, r, c); + if (processed_pixels.find(pixel_key) == processed_pixels.end()) { + processed_pixels.insert(pixel_key); + step_pixels[global_step].insert(PixelData(p, r, c, pixval)); + _npix_processed++; + _total_charge += pixval; + } + } + } + } + } + + global_step++; + } + } + + // Now create space points from the collected pixel data + for (auto& step_entry : step_pixels) { + int step_idx = step_entry.first; + auto& pixels = step_entry.second; + + if (pixels.empty()) + continue; + + SpacePointCharge sp; + + // Use the track position or charge-weighted position + if (_use_charge_weighting && !pixels.empty()) { + sp.position = getChargeWeightedPosition(pixels, adc_v); + } else { + sp.position = step_positions[step_idx]; + } + + // Calculate total charge and per-plane charges + sp.charge = 0; + sp.plane_charges.resize(3, 0); + + int tick_sum = 0; + int tick_count = 0; + std::vector wire_nums(3, -1); + + for (const auto& pix : pixels) { + sp.charge += pix.value; + sp.plane_charges[pix.plane] += pix.value; + + if (pix.plane == 0 && wire_nums[0] == -1) { + wire_nums[0] = pix.col; + } else if (pix.plane == 1 && wire_nums[1] == -1) { + wire_nums[1] = pix.col; + } else if (pix.plane == 2 && wire_nums[2] == -1) { + wire_nums[2] = pix.col; + } + + tick_sum += pix.row; + tick_count++; + } + + // Set wire numbers and average tick + sp.wire_u = wire_nums[0]; + sp.wire_v = wire_nums[1]; + sp.wire_y = wire_nums[2]; + sp.tick = (tick_count > 0) ? tick_sum / tick_count : -1; + + spacepoints.push_back(sp); + } + + return spacepoints; + } + + std::set + TrackToSpacePoints::getUniquePixels( + const larlite::track& track, + const std::vector& adc_v, + const float threshold, + const int dcol, + const int drow, + const float minstepsize, + const float maxstepsize) + { + std::set unique_pixels; + + int npts = track.NumberTrajectoryPoints(); + if (npts <= 1) { + return unique_pixels; + } + + const float driftv = larutil::LArProperties::GetME()->DriftVelocity(); + const float usec_per_tick = 0.5; + float max_tick = adc_v.front().meta().max_y(); + float min_tick = adc_v.front().meta().min_y(); + + // Loop over track segments + for (int ipt = 0; ipt < npts-1; ipt++) { + + TVector3 start = track.LocationAtPoint(ipt); + TVector3 end = track.LocationAtPoint(ipt+1); + TVector3 dir = end - start; + + double segsize = dir.Mag(); + + int nsteps = 1; + if (segsize > minstepsize) { + nsteps = segsize/maxstepsize + 1; + } + + float stepsize = segsize/float(nsteps); + + // Step along the segment + for (int istep = 0; istep <= nsteps; istep++) { + + // Get 3D position along track + TVector3 pos = start + istep*(stepsize/segsize)*dir; + + // Calculate tick from X position + int tick = pos[0]/driftv/usec_per_tick + 3200; + + if (tick < min_tick || tick > max_tick) + continue; + + int row = adc_v.front().meta().row(tick); + + // Project into each wire plane + for (int p = 0; p < 3; p++) { + int wire = larutil::Geometry::GetME()->WireCoordinate(pos, p); + + // Collect charge from neighboring pixels + for (int dr = -abs(drow); dr <= abs(drow); dr++) { + int r = row + dr; + if (r < 0 || r >= (int)adc_v[p].meta().rows()) + continue; + + for (int dc = -abs(dcol); dc <= abs(dcol); dc++) { + int c = wire + dc; + if (c < 0 || c >= (int)adc_v[p].meta().cols()) + continue; + + float pixval = adc_v[p].pixel(r, c); + if (pixval > threshold) { + unique_pixels.insert(PixelData(p, r, c, pixval)); + } + } + } + } + } + } + + return unique_pixels; + } + + TVector3 TrackToSpacePoints::getChargeWeightedPosition( + const std::set& pixels, + const std::vector& adc_v) + { + if (pixels.empty()) { + return TVector3(0, 0, 0); + } + + // Group pixels by plane + std::map> plane_pixels; + for (const auto& pix : pixels) { + plane_pixels[pix.plane].push_back(&pix); + } + + // Calculate charge-weighted wire position for each plane + std::map weighted_wires; + std::map total_charges; + float weighted_tick = 0; + float total_tick_charge = 0; + + for (const auto& plane_pair : plane_pixels) { + int plane = plane_pair.first; + float weighted_wire = 0; + float plane_charge = 0; + + for (const auto* pix : plane_pair.second) { + weighted_wire += pix->col * pix->value; + plane_charge += pix->value; + weighted_tick += pix->row * pix->value; + total_tick_charge += pix->value; + } + + if (plane_charge > 0) { + weighted_wires[plane] = weighted_wire / plane_charge; + total_charges[plane] = plane_charge; + } + } + + if (total_tick_charge > 0) { + weighted_tick /= total_tick_charge; + } + + // Convert weighted wire/tick to 3D position + // This is approximate - ideally would use proper 3D reconstruction + float x = tickToX(weighted_tick); + + // Average Y,Z from different plane projections + float y_sum = 0, z_sum = 0; + int n_planes = 0; + + for (const auto& wire_pair : weighted_wires) { + int plane = wire_pair.first; + float wire = wire_pair.second; + float y, z; + wireTickToYZ(plane, wire, weighted_tick, y, z); + y_sum += y; + z_sum += z; + n_planes++; + } + + if (n_planes > 0) { + y_sum /= n_planes; + z_sum /= n_planes; + } + + return TVector3(x, y_sum, z_sum); + } + + float TrackToSpacePoints::tickToX(int tick) const + { + const float driftv = larutil::LArProperties::GetME()->DriftVelocity(); + const float usec_per_tick = 0.5; + return (tick - 3200) * driftv * usec_per_tick; + } + + void TrackToSpacePoints::wireTickToYZ(int plane, int wire, int tick, + float& y, float& z) const + { + // Get 3D position from wire/tick + // This is a simplified version - in practice would use proper geometry + auto const* geom = larutil::Geometry::GetME(); + + // Get wire start and end points + double xyz_start[3], xyz_end[3]; + geom->WireEndPoints(plane, wire, xyz_start, xyz_end); + + // For now, use wire midpoint for Y,Z + // A more sophisticated approach would use the tick info + y = (xyz_start[1] + xyz_end[1]) / 2.0; + z = (xyz_start[2] + xyz_end[2]) / 2.0; + } + +} +} \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.h b/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.h new file mode 100644 index 0000000..0584a5e --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/TrackToSpacePoints.h @@ -0,0 +1,170 @@ +#ifndef __UBLARCVAPP_PARTICLETOPIXELUTILS_TRACKTOSPACEPOINTS_H__ +#define __UBLARCVAPP_PARTICLETOPIXELUTILS_TRACKTOSPACEPOINTS_H__ + +#include +#include +#include + +#include "larlite/DataFormat/track.h" +#include "larcv/core/DataFormat/Image2D.h" +#include "TVector3.h" + +namespace ublarcvapp { +namespace pixelutils { + + /** + * @brief Convert larlite tracks to collections of 3D space points with charge + * + * This class takes a larlite::track object and extracts charge information + * from ADC images at the projected 2D positions along the track. + * It produces a collection of 3D space points with associated charge values. + */ + class TrackToSpacePoints { + public: + + /** + * @brief Structure to hold a 3D space point with charge information + */ + struct SpacePointCharge { + TVector3 position; ///< 3D position in detector coordinates (cm) + float charge; ///< Total charge from neighboring pixels + int wire_u; ///< U-plane wire number + int wire_v; ///< V-plane wire number + int wire_y; ///< Y-plane wire number + int tick; ///< Time tick + std::vector plane_charges; ///< Charge on each plane [U,V,Y] + + SpacePointCharge() + : position(0,0,0), charge(0), + wire_u(-1), wire_v(-1), wire_y(-1), tick(-1), + plane_charges(3,0) {}; + ~SpacePointCharge() {}; + }; + + /** + * @brief Structure to hold pixel coordinate and value + */ + struct PixelData { + int plane; ///< Wire plane index + int row; ///< Row (tick) index + int col; ///< Column (wire) index + float value; ///< Pixel ADC value + + PixelData(int p, int r, int c, float v) + : plane(p), row(r), col(c), value(v) {} + + // Define comparison operator for set + bool operator<(const PixelData& other) const { + if (plane != other.plane) return plane < other.plane; + if (row != other.row) return row < other.row; + return col < other.col; + } + }; + + /** + * @brief Constructor + */ + TrackToSpacePoints(); + + /** + * @brief Destructor + */ + ~TrackToSpacePoints(); + + /** + * @brief Convert a track to space points with charge + * @param track The larlite::track to convert + * @param adc_v Vector of ADC images for each wire plane + * @param threshold ADC threshold for pixel selection + * @param dcol Column window around projected wire position + * @param drow Row window around projected tick position + * @param minstepsize Minimum step size along track (cm) + * @param maxstepsize Maximum step size along track (cm) + * @return Vector of SpacePointCharge objects along the track + */ + std::vector convertTrack( + const larlite::track& track, + const std::vector& adc_v, + const float threshold = 10.0, + const int dcol = 3, + const int drow = 3, + const float minstepsize = 0.3, + const float maxstepsize = 0.5 + ); + + /** + * @brief Get all unique pixels touched by the track projection + * @param track The larlite::track to analyze + * @param adc_v Vector of ADC images for each wire plane + * @param threshold ADC threshold for pixel selection + * @param dcol Column window around projected wire position + * @param drow Row window around projected tick position + * @param minstepsize Minimum step size along track (cm) + * @param maxstepsize Maximum step size along track (cm) + * @return Set of unique PixelData objects + */ + std::set getUniquePixels( + const larlite::track& track, + const std::vector& adc_v, + const float threshold = 10.0, + const int dcol = 3, + const int drow = 3, + const float minstepsize = 0.3, + const float maxstepsize = 0.5 + ); + + /** + * @brief Get charge-weighted average position for a collection of pixels + * @param pixels Set of pixels with charge values + * @param adc_v Vector of ADC images for wire/tick to position conversion + * @return Charge-weighted 3D position + */ + TVector3 getChargeWeightedPosition( + const std::set& pixels, + const std::vector& adc_v + ); + + /** + * @brief Set whether to use charge weighting for space point positions + * @param use_weighting If true, use charge-weighted positions + */ + void setUseChargeWeighting(bool use_weighting) { _use_charge_weighting = use_weighting; } + + /** + * @brief Get total number of pixels processed in last conversion + */ + int getNumPixelsProcessed() const { return _npix_processed; } + + /** + * @brief Get total charge collected in last conversion + */ + float getTotalCharge() const { return _total_charge; } + + private: + + bool _use_charge_weighting; ///< Use charge-weighted positions + int _npix_processed; ///< Number of pixels processed + float _total_charge; ///< Total charge collected + + /** + * @brief Convert tick to X position + * @param tick Time tick + * @return X position in cm + */ + float tickToX(int tick) const; + + /** + * @brief Convert wire and tick to Y,Z position + * @param plane Wire plane index + * @param wire Wire number + * @param tick Time tick + * @param y Output Y position + * @param z Output Z position + */ + void wireTickToYZ(int plane, int wire, int tick, float& y, float& z) const; + }; + +} +} + +#endif diff --git a/ublarcvapp/ParticleToPixelUtils/test/.gitignore b/ublarcvapp/ParticleToPixelUtils/test/.gitignore new file mode 100644 index 0000000..aab52d9 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/.gitignore @@ -0,0 +1 @@ +*.png \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/test/complete_track_light_example.py b/ublarcvapp/ParticleToPixelUtils/test/complete_track_light_example.py new file mode 100644 index 0000000..334b338 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/complete_track_light_example.py @@ -0,0 +1,595 @@ +#!/usr/bin/env python3 +""" +Complete example demonstrating the full workflow from track to light detection estimation. + +This script shows: +1. How to create realistic larlite::track objects from line segments +2. How to create realistic ADC images with proper geometry and charge deposits +3. How to use TrackToSpacePoints to convert tracks to space points with charge +4. How to convert charge to photons using LAr physics +5. How to use PhotonVisibilityEstimator to predict light detection + +Usage: + python3 complete_track_light_example.py [--input-file file.root] [--save-output] +""" + +import ROOT as rt +from ROOT import std +import numpy as np +import argparse +import sys + +# Load libraries +from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp + +def create_track_from_segments(segments): + """ + Create a larlite::track from a list of 3D line segments + + Args: + segments: List of tuples [(start_point, end_point), ...] + where each point is (x, y, z) in cm + + Returns: + larlite::track object + """ + track = larlite.track() + + print(f"Creating track from {len(segments)} line segments") + + for i, (start, end) in enumerate(segments): + # Add start point (skip if it's the same as previous end point) + if i == 0: + pos = rt.TVector3(start[0], start[1], start[2]) + track.add_vertex(pos) + + # Add end point + pos = rt.TVector3(end[0], end[1], end[2]) + track.add_vertex(pos) + + # Calculate direction vector + dx = end[0] - start[0] + dy = end[1] - start[1] + dz = end[2] - start[2] + direction = rt.TVector3(dx, dy, dz) + if direction.Mag() > 0: + direction = direction.Unit() + + track.add_direction(direction) + + print(f" Segment {i}: ({start[0]:.1f}, {start[1]:.1f}, {start[2]:.1f}) -> " + f"({end[0]:.1f}, {end[1]:.1f}, {end[2]:.1f})") + + return track + +def create_realistic_adc_images(): + """ + Create realistic ADC Image2D objects with proper MicroBooNE geometry + + Returns: + std::vector with U, V, Y plane images + """ + print("Creating realistic ADC images with MicroBooNE geometry...") + + # MicroBooNE detector parameters + tick_period = 0.5 # microseconds per tick + rows = 6048 # time ticks + + # Wire plane parameters + cols_u = 2400 # U plane wires + cols_v = 2400 # V plane wires + cols_y = 3456 # Y plane wires (collection plane) + + # Create image metadata + # ImageMeta(width, height, row_count, col_count, origin_x, origin_y, plane) + wire_pitch = 0.3 # cm + time_pitch = 0.5 # μs per tick + + width_u = cols_u * wire_pitch + width_v = cols_v * wire_pitch + width_y = cols_y * wire_pitch + height = rows * time_pitch + + meta_u = larcv.ImageMeta(width_u, height, rows, cols_u, 0.0, 0.0, 0) + meta_v = larcv.ImageMeta(width_v, height, rows, cols_v, 0.0, 0.0, 1) + meta_y = larcv.ImageMeta(width_y, height, rows, cols_y, 0.0, 0.0, 2) + + # Create empty images + img_u = larcv.Image2D(meta_u) + img_v = larcv.Image2D(meta_v) + img_y = larcv.Image2D(meta_y) + + # Fill with small baseline noise + for img in [img_u, img_v, img_y]: + img.paint(0.0) + # Add realistic noise + for row in range(0, rows, 50): # Sample every 50 ticks for speed + for col in range(0, img.meta().cols(), 20): # Sample every 20 wires + noise = np.random.normal(0, 2.0) # 2 ADC RMS noise + if noise > 0: + img.set_pixel(row, col, noise) + + print(f" U plane: {cols_u} wires x {rows} ticks") + print(f" V plane: {cols_v} wires x {rows} ticks") + print(f" Y plane: {cols_y} wires x {rows} ticks") + + # Package into vector + adc_images = std.vector('larcv::Image2D')() + adc_images.push_back(img_u) + adc_images.push_back(img_v) + adc_images.push_back(img_y) + + return adc_images + +def get_image_and_tracks_from_rootfile(rootfile): + """ + Get Image2D and track examples for testing TrackToSpacePoints + and the resulting photon prediction using ublarcvapp.ubphotonlib.PhotonVisibilityEstimator. + The test file is a merged file containing TTrees with larcv objects and + the reconstructed tracks. + """ + print(f"Loading data from {rootfile}") + + # Load larcv images + ioman = larcv.IOManager(larcv.IOManager.kREAD) + ioman.add_in_file(rootfile) + ioman.initialize() + + # Get the first entry + ioman.read_entry(0) + + ev_img = ioman.get_data(larcv.kProductImage2D, "wire") + imglist = [] + for p in range(3): + img = ev_img.as_vector().at(p) + imglist.append(img) + print(f" Plane {p}: {img.meta().cols()} x {img.meta().rows()} pixels") + + # Convert to std::vector for TrackToSpacePoints + adc_images = std.vector('larcv::Image2D')() + for img in imglist: + adc_images.push_back(img) + + # Get the opflash data + ioll = larlite.storage_manager(larlite.storage_manager.kREAD) + ioll.add_in_filename(rootfile) + ioll.open() + ioll.go_to(0) + + # Get opflash data using correct casting + ev_opflash = ioll.get_data(larlite.data.kOpFlash,"simpleFlashBeam") + opflashes = std.vector('larlite::opflash')() + + # Try to access the opflash data + print(f" Found {ev_opflash.size()} optical flashes") + for iopflash in range(ev_opflash.size()): + opflashes.push_back(ev_opflash.at(iopflash)) + + # Get the larlite::track examples from the reco file + reco_tfile = rt.TFile(rootfile, 'open') + recotree = reco_tfile.Get("KPSRecoManagerTree") + trackout_v = std.vector('larlite::track')() + + if not recotree: + print("Warning: Could not find KPSRecoManagerTree in file") + return adc_images, trackout_v, opflashes + + recotree.GetEntry(0) + + # Check if we have the expected structure + if not hasattr(recotree, 'nuvetoed_v') or recotree.nuvetoed_v.size() == 0: + print("Warning: No nuvetoed_v found in tree") + return adc_images, [] + + nuvtx = recotree.nuvetoed_v.at(0) + + print(f" Found {nuvtx.track_v.size()} tracks") + + + for itrack in range(nuvtx.track_v.size()): + track = nuvtx.track_v.at(itrack) + if track.NumberTrajectoryPoints()>=2: + trackout_v.push_back(track) + print(f" Track {itrack}: {track.NumberTrajectoryPoints()} points, length {track.Length():.1f} cm") + + ioman.finalize() + reco_tfile.Close() + + return adc_images, trackout_v, opflashes + +def add_track_charge_to_images(adc_images, track, charge_per_cm=1000.0): + """ + Add realistic charge deposits along a track path in the ADC images + + Args: + adc_images: Vector of larcv::Image2D objects + track: larlite::track object + charge_per_cm: Average charge deposition per cm of track + """ + print(f"Adding track charge deposits ({charge_per_cm} ADC/cm)...") + + # MicroBooNE parameters + drift_velocity = 0.1114 # cm/μs (at 273V/cm) + tick_period = 0.5 # μs/tick + x_offset = 3200 # ticks (approximate trigger offset) + + # Wire geometry (simplified - in reality would use larutil::Geometry) + wire_pitch = 0.3 # cm + + n_deposits = 0 + total_charge = 0 + + # Step along track and add charge + n_points = track.NumberTrajectoryPoints() + if n_points < 2: + return + + for i in range(n_points - 1): + start_pos = track.LocationAtPoint(i) + end_pos = track.LocationAtPoint(i + 1) + + # Calculate segment length + dx = end_pos.X() - start_pos.X() + dy = end_pos.Y() - start_pos.Y() + dz = end_pos.Z() - start_pos.Z() + segment_length = np.sqrt(dx*dx + dy*dy + dz*dz) + + if segment_length < 0.1: # Skip very short segments + continue + + # Number of charge deposits along this segment + n_steps = max(1, int(segment_length / 0.2)) # Every 2mm + + for step in range(n_steps): + t = float(step) / n_steps + x = start_pos.X() + t * dx + y = start_pos.Y() + t * dy + z = start_pos.Z() + t * dz + + # Convert 3D position to wire/tick coordinates + tick = x / drift_velocity / tick_period + x_offset + + # Simplified wire calculation (in reality would use proper geometry) + # U plane: 30° from vertical + # V plane: -30° from vertical + # Y plane: vertical (collection) + wire_u = int((y * np.cos(np.pi/6) + z * np.sin(np.pi/6)) / wire_pitch + 1200) + wire_v = int((-y * np.cos(np.pi/6) + z * np.sin(np.pi/6)) / wire_pitch + 1200) + wire_y = int(z / wire_pitch + 1700) + + row = int(tick) + + # Add charge if within image bounds + wires = [wire_u, wire_v, wire_y] + for plane in range(3): + img = adc_images[plane] + col = wires[plane] + + if (0 <= row < img.meta().rows() and + 0 <= col < img.meta().cols()): + + # Calculate charge for this step + step_length = segment_length / n_steps + base_charge = charge_per_cm * step_length + + # Add some fluctuation (Landau-like) + charge = np.random.gamma(2.0, base_charge/2.0) + + # Spread charge over neighboring pixels (diffusion effect) + for dr in range(-1, 2): + for dc in range(-2, 3): + r = row + dr + c = col + dc + if (0 <= r < img.meta().rows() and + 0 <= c < img.meta().cols()): + + # Gaussian spread + weight = np.exp(-(dr*dr + dc*dc) / 2.0) + pixel_charge = charge * weight * 0.1 # 10% of charge per pixel + + current_val = img.pixel(r, c) + img.set_pixel(r, c, current_val + pixel_charge) + + if plane == 2: # Count only Y plane for statistics + n_deposits += 1 + total_charge += pixel_charge + + print(f" Added {n_deposits} charge deposits") + print(f" Total charge: {total_charge:.0f} ADC counts") + +def convert_charge_to_photons(adc_charge): + """ + Convert ADC charge to number of scintillation photons + + Args: + adc_charge: Charge in ADC counts + + Returns: + Number of scintillation photons + """ + # LAr physics parameters (approximate) + adc_per_electron = 200.0 # ADC counts per electron + mev_per_electron = 23.6e-6 # Ionization energy in LAr (MeV) + photons_per_mev = 24000.0 # Scintillation photons per MeV at 500V/cm + recombination_factor = 0.7 # Fraction surviving recombination + + # Convert: ADC -> electrons -> energy -> photons + n_electrons = adc_charge / adc_per_electron + energy_mev = n_electrons * mev_per_electron + n_photons = energy_mev * photons_per_mev * (1.0 - recombination_factor) + + return n_photons + +def main(): + parser = argparse.ArgumentParser(description="Complete track to light estimation example") + parser.add_argument("--input-file", help="ROOT file with track/image data (optional)") + parser.add_argument("--save-output", action="store_true", help="Save output plots") + parser.add_argument("--charge-per-cm", type=float, default=2000.0, + help="Charge deposition per cm (ADC)") + args = parser.parse_args() + + print("Complete Track to Light Estimation Example") + print("="*60) + + opflashes = None # Initialize for synthetic data case + + if args.input_file: + # Use real data from ROOT file + print(f"\nUsing real data from: {args.input_file}") + adc_images, tracks, opflashes = get_image_and_tracks_from_rootfile(args.input_file) + + if len(tracks) == 0: + print("No tracks found in input file, exiting") + return + + # Use the first track for demonstration + track = tracks[0] + track_start = track.LocationAtPoint(0) + print(f"\nUsing track 0: {track.NumberTrajectoryPoints()} points, length {track.Length():.1f} cm") + print(f"Start: ({track_start.X():.1f}, {track_start.Y():.1f}, {track_start.Z():.1f})") + print(f"End: ({track.End().X():.1f}, {track.End().Y():.1f}, {track.End().Z():.1f})") + + # Show information about optical flashes + print(f"\nFound {len(opflashes)} optical flashes") + if len(opflashes) > 0: + flash = opflashes[0] # Use first flash for comparison + print(f"Using flash 0: Total PE = {flash.TotalPE():.1f}, Time = {flash.Time():.1f} μs") + + else: + # Use synthetic data + print("\nUsing synthetic data (use --input-file for real data)") + + # Define track as series of line segments + track_segments = [ + # Start outside detector, enter from top + ((10.0, 100.0, 200.0), (50.0, 80.0, 300.0)), # entering + ((50.0, 80.0, 300.0), (90.0, 50.0, 450.0)), # through detector + ((90.0, 50.0, 450.0), (130.0, 20.0, 600.0)), # continuing + ((130.0, 20.0, 600.0), (170.0, -10.0, 750.0)), # exiting + ] + + # Step 1: Create track from line segments + print("\n1. Creating track from line segments...") + track = create_track_from_segments(track_segments) + + print(f"Track created with {track.NumberTrajectoryPoints()} trajectory points") + print(f"Track length: {track.Length():.1f} cm") + + # Step 2: Create realistic ADC images + print("\n2. Creating ADC images...") + adc_images = create_realistic_adc_images() + + # Step 3: Add charge deposits along track + print("\n3. Adding charge deposits...") + add_track_charge_to_images(adc_images, track, args.charge_per_cm) + + # Convert track to space points + print("\nConverting track to space points...") + converter = ublarcvapp.pixelutils.TrackToSpacePoints() + converter.setUseChargeWeighting(False) + + spacepoints = converter.convertTrack( + track, adc_images, + threshold=10.0, # ADC threshold + dcol=3, # wire window + drow=3, # tick window + minstepsize=0.3, # min step (cm) + maxstepsize=0.5 # max step (cm) + ) + + print(f"Generated {len(spacepoints)} space points") + print(f"Pixels processed: {converter.getNumPixelsProcessed()}") + print(f"Total charge collected: {converter.getTotalCharge():.0f} ADC") + + # Convert to photon sources + print("\nConverting charge to photons...") + photon_estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + total_photons_added = 0 + for i, sp in enumerate(spacepoints): + if sp.charge > 5.0: # Only use points with significant charge + n_photons = convert_charge_to_photons(sp.charge) + + photon_estimator.addPhotonSource( + sp.position.X(), + sp.position.Y(), + sp.position.Z(), + n_photons + ) + total_photons_added += n_photons + + if i < 5: # Print first few + print(f" Point {i}: ({sp.position.X():.1f}, {sp.position.Y():.1f}, {sp.position.Z():.1f}) " + f"charge={sp.charge:.1f} -> {n_photons:.0f} photons") + + print(f"Added {photon_estimator.getNumSources()} photon sources") + print(f"Total emitted photons: {total_photons_added:.0f}") + + # Estimate light detection + print("\nEstimating light detection...") + + # Calculate with trilinear interpolation + photons_per_pmt = photon_estimator.calculateDetectedPhotons(use_trilinear=True) + total_detected = photon_estimator.getTotalDetectedPhotons(True) + efficiency = photon_estimator.getCollectionEfficiency(True) + + print(f"Total detected photons: {total_detected:.1f}") + print(f"Collection efficiency: {efficiency:.4f} ({efficiency*100:.2f}%)") + + # Show detection by PMT + print("\nPhotons detected by PMT:") + for pmt in range(32): + pe = photons_per_pmt[pmt] + if pe > 0.5: + print(f" PMT {pmt:2d}: {pe:7.1f} PE") + + # Create visualization + if args.save_output: + print("\nCreating visualization...") + + # PMT response histogram (predicted) + h_pmt_pred = rt.TH1F("h_pmt_pred", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred.SetBinContent(pmt+1, photons_per_pmt[pmt]) + + # PMT response histogram (observed) - only if we have opflash data + h_pmt_obs = None + if opflashes is not None and len(opflashes) > 0: + flash = opflashes[0] # Use first flash + h_pmt_obs = rt.TH1F("h_pmt_obs", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + + # Fill observed PMT response and normalize to 1 + total_obs_pe = 0 + pe_per_pmt = [] + for pmt in range(32): + pe = flash.PE(pmt) + pe_per_pmt.append(pe) + total_obs_pe += pe + + # Normalize observed data so total = 1 for shape comparison + if total_obs_pe > 0: + for pmt in range(32): + normalized_pe = pe_per_pmt[pmt] / total_obs_pe + h_pmt_obs.SetBinContent(pmt+1, normalized_pe) + + print(f"\nObserved flash: Total PE = {total_obs_pe:.1f}") + print("Observed PE per PMT (>0.1):") + for pmt in range(32): + if pe_per_pmt[pmt] > 0.1: + print(f" PMT {pmt:2d}: {pe_per_pmt[pmt]:6.1f} PE") + + # Normalize predicted data to 1 for shape comparison + if total_detected > 0: + for pmt in range(32): + normalized_pred = photons_per_pmt[pmt] / total_detected + h_pmt_pred.SetBinContent(pmt+1, normalized_pred) + + # Track 3D visualization + h_track_xz = rt.TH2F("h_track_xz", "Track Projection X-Z;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + h_track_yz = rt.TH2F("h_track_yz", "Track Projection Y-Z;Y [cm];Z [cm]", + 100, -50, 150, 100, 100, 800) + + # Fill track histograms + for i in range(track.NumberTrajectoryPoints()): + pos = track.LocationAtPoint(i) + h_track_xz.Fill(pos.X(), pos.Z()) + h_track_yz.Fill(pos.Y(), pos.Z()) + + # Space points with charge + h_charge_xz = rt.TH2F("h_charge_xz", "Space Points with Charge;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + for sp in spacepoints: + if sp.charge > 0: + h_charge_xz.Fill(sp.position.X(), sp.position.Z(), sp.charge) + + # Create canvas with space for comparison plot + canvas = rt.TCanvas("c1", "Track Light Estimation", 1600, 1000) + canvas.Divide(3, 3) + + canvas.cd(1) + h_track_xz.SetMarkerStyle(20) + h_track_xz.SetMarkerColor(rt.kBlue) + h_track_xz.Draw("P") + rt.gPad.SetTitle("Track Path (X-Z)") + + canvas.cd(2) + h_track_yz.SetMarkerStyle(20) + h_track_yz.SetMarkerColor(rt.kBlue) + h_track_yz.Draw("P") + rt.gPad.SetTitle("Track Path (Y-Z)") + + canvas.cd(3) + h_charge_xz.SetMarkerStyle(20) + h_charge_xz.SetMarkerColor(rt.kRed) + h_charge_xz.Draw("COLZ") + rt.gPad.SetTitle("Charge Deposits") + + # PMT comparison plot + canvas.cd(4) + h_pmt_pred.SetLineColor(rt.kRed) + h_pmt_pred.SetLineWidth(2) + h_pmt_pred.SetTitle("PMT Response Comparison;PMT ID;Normalized Response") + max_val = h_pmt_pred.GetMaximum() + + if h_pmt_obs is not None: + h_pmt_obs.SetLineColor(rt.kBlue) + h_pmt_obs.SetLineWidth(2) + max_val = max(max_val, h_pmt_obs.GetMaximum()) + h_pmt_pred.GetYaxis().SetRangeUser(0, max_val * 1.1) + h_pmt_pred.Draw("HIST") + h_pmt_obs.Draw("HIST SAME") + + # Add legend + legend = rt.TLegend(0.6, 0.7, 0.89, 0.89) + legend.AddEntry(h_pmt_pred, "Predicted", "l") + legend.AddEntry(h_pmt_obs, "Observed", "l") + legend.Draw() + else: + h_pmt_pred.SetFillColor(rt.kCyan-9) + h_pmt_pred.Draw("HIST") + + canvas.cd(5) + # Individual predicted PMT response (unnormalized) + h_pmt_pred_raw = rt.TH1F("h_pmt_pred_raw", "Predicted PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred_raw.SetBinContent(pmt+1, photons_per_pmt[pmt]) + h_pmt_pred_raw.SetFillColor(rt.kGreen-9) + h_pmt_pred_raw.Draw("HIST") + + canvas.cd(6) + if h_pmt_obs is not None: + # Individual observed PMT response (unnormalized) + h_pmt_obs_raw = rt.TH1F("h_pmt_obs_raw", "Observed PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_obs_raw.SetBinContent(pmt+1, pe_per_pmt[pmt]) + h_pmt_obs_raw.SetFillColor(rt.kYellow-9) + h_pmt_obs_raw.Draw("HIST") + + # Summary text + canvas.cd(7) + text = rt.TText() + text.SetTextSize(0.08) + text.DrawText(0.1, 0.9, f"Track Length: {track.Length():.1f} cm") + text.DrawText(0.1, 0.8, f"Space Points: {len(spacepoints)}") + text.DrawText(0.1, 0.7, f"Total Charge: {converter.getTotalCharge():.0f} ADC") + text.DrawText(0.1, 0.6, f"Emitted Photons: {total_photons_added:.0f}") + text.DrawText(0.1, 0.5, f"Detected Photons: {total_detected:.0f}") + text.DrawText(0.1, 0.4, f"Efficiency: {efficiency*100:.2f}%") + if opflashes is not None and len(opflashes) > 0: + text.DrawText(0.1, 0.3, f"Observed PE: {total_obs_pe:.0f}") + text.DrawText(0.1, 0.2, f"Flash Time: {flash.Time():.1f} us") + + canvas.SaveAs("complete_track_light_example.png") + print("Saved visualization to complete_track_light_example.png") + + print("\nExample completed successfully!") + +if __name__ == "__main__": + main() diff --git a/ublarcvapp/ParticleToPixelUtils/test/test_nucand_light_example.py b/ublarcvapp/ParticleToPixelUtils/test/test_nucand_light_example.py new file mode 100644 index 0000000..6a4cbed --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/test_nucand_light_example.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python3 +""" +Complete example demonstrating the full workflow from shower to light detection estimation. + +This script shows: +1. How to create realistic larlite::shower objects from line segments +2. How to create realistic ADC images with proper geometry and charge deposits +3. How to use showerToSpacePoints to convert showers to space points with charge +4. How to convert charge to photons using LAr physics +5. How to use PhotonVisibilityEstimator to predict light detection + +Usage: + python3 complete_shower_light_example.py [--input-file file.root] [--save-output] +""" + +import ROOT as rt +from ROOT import std +import numpy as np +import argparse +import sys +rt.gStyle.SetOptStat(0) + +# Load libraries +from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp + + +def get_data_from_rootfile(rootfile): + """ + Get Image2D and shower examples for testing showerToSpacePoints + and the resulting photon prediction using ublarcvapp.ubphotonlib.PhotonVisibilityEstimator. + The test file is a merged file containing TTrees with larcv objects and + the reconstructed showers. + """ + print(f"Loading data from {rootfile}") + + # Load larcv images + ioman = larcv.IOManager(larcv.IOManager.kREAD) + ioman.add_in_file(rootfile) + ioman.initialize() + + # Get the first entry + ioman.read_entry(0) + + ev_img = ioman.get_data(larcv.kProductImage2D, "wire") + imglist = [] + for p in range(3): + img = ev_img.as_vector().at(p) + imglist.append(img) + print(f" Plane {p}: {img.meta().cols()} x {img.meta().rows()} pixels") + + # Convert to std::vector for showerToSpacePoints + adc_images = std.vector('larcv::Image2D')() + for img in imglist: + adc_images.push_back(img) + + # Get the opflash data + ioll = larlite.storage_manager(larlite.storage_manager.kREAD) + ioll.add_in_filename(rootfile) + ioll.open() + ioll.go_to(0) + + # Get opflash data using correct casting + ev_opflash = ioll.get_data(larlite.data.kOpFlash,"simpleFlashBeam") + opflashes = std.vector('larlite::opflash')() + + # Try to access the opflash data + print(f" Found {ev_opflash.size()} optical flashes") + for iopflash in range(ev_opflash.size()): + opflashes.push_back(ev_opflash.at(iopflash)) + + # Get the larlite::shower examples from the reco file + reco_tfile = rt.TFile(rootfile, 'open') + recotree = reco_tfile.Get("KPSRecoManagerTree") + trackout_v = std.vector('larlite::track')() + showerout_v = std.vector('larlite::larflowcluster')() + + if not recotree: + print("Warning: Could not find KPSRecoManagerTree in file") + return adc_images, showerout_v, opflashes + + recotree.GetEntry(0) + + # Check if we have the expected structure + if not hasattr(recotree, 'nuvetoed_v') or recotree.nuvetoed_v.size() == 0: + print("Warning: No nuvetoed_v found in tree") + return adc_images, [] + + nuvtx = recotree.nuvetoed_v.at(0) + + print(f" Found {nuvtx.shower_v.size()} showers") + + # Get showers + for ishower in range(nuvtx.shower_v.size()): + shower = nuvtx.shower_v.at(ishower) + if shower.size()>=2: + showerout_v.push_back(shower) + print(f" Shower {ishower}: {shower.size()} points") + + # Get track + for itrack in range(nuvtx.track_v.size()): + track = nuvtx.track_v.at(itrack) + if track.NumberTrajectoryPoints()>=2: + trackout_v.push_back(track) + print(f" Track {itrack}: {track.NumberTrajectoryPoints()} points, length {track.Length():.1f} cm") + + ioman.finalize() + reco_tfile.Close() + + return adc_images, trackout_v, showerout_v, opflashes + + +def convert_charge_to_photons(adc_charge): + """ + Convert ADC charge to number of scintillation photons + + Args: + adc_charge: Charge in ADC counts + + Returns: + Number of scintillation photons + """ + # LAr physics parameters (approximate) + adc_per_electron = 200.0 # ADC counts per electron + mev_per_electron = 23.6e-6 # Ionization energy in LAr (MeV) + photons_per_mev = 24000.0 # Scintillation photons per MeV at 500V/cm + recombination_factor = 0.7 # Fraction surviving recombination + + # Convert: ADC -> electrons -> energy -> photons + n_electrons = adc_charge / adc_per_electron + energy_mev = n_electrons * mev_per_electron + n_photons = energy_mev * photons_per_mev * (1.0 - recombination_factor) + + return n_photons + +def get_shower_opdet_predicts( showers, adc_images ): + + # Loop over shower container + total_photons_added = 0 + shower_predictions = [] + for ishower in range( showers.size() ): + shower = showers.at(ishower) + + # make a spacepoint converter for this shower + converter = ublarcvapp.pixelutils.ShowerToSpacePoints() + converter.setUseChargeWeighting(False) + spacepoints = converter.convertShower( + shower, adc_images, + threshold=10.0, # ADC threshold + dcol=3, # wire window + drow=3 # tick window + ) + + # Make photon estimator + photon_estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + for i, sp in enumerate(spacepoints): + if sp.charge > 5.0: # Only use points with significant charge + n_photons = convert_charge_to_photons(sp.charge) + + photon_estimator.addPhotonSource( + sp.position.X(), + sp.position.Y(), + sp.position.Z(), + n_photons + ) + total_photons_added += n_photons + print(f"shower[{ishower}] num photons predicted = {n_photons}") + photons_per_pmt = photon_estimator.calculateDetectedPhotons(use_trilinear=True) + shower_predictions.append( photons_per_pmt ) + + print(f'Number of total photons: {total_photons_added}') + + return shower_predictions + +def get_track_opdet_predicts( tracks, adc_images ): + + # Loop over track container + total_photons_added = 0 + track_predictions = [] + for itrack in range( tracks.size() ): + track = tracks.at(itrack) + + # make a spacepoint converter for this track + converter = ublarcvapp.pixelutils.TrackToSpacePoints() + converter.setUseChargeWeighting(False) + spacepoints = converter.convertTrack( + track, adc_images, + threshold=10.0, # ADC threshold + dcol=3, # wire window + drow=3, # tick window + minstepsize=0.3, # min step (cm) + maxstepsize=0.5 # max step (cm) + ) + + # Make photon estimator + photon_estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + for i, sp in enumerate(spacepoints): + #if sp.charge > 5.0: # Only use points with significant charge + n_photons = convert_charge_to_photons(sp.charge) + + photon_estimator.addPhotonSource( + sp.position.X(), + sp.position.Y(), + sp.position.Z(), + n_photons + ) + total_photons_added += n_photons + print(f"track[{itrack}] num photons predicted = {n_photons}") + photons_per_pmt = photon_estimator.calculateDetectedPhotons(use_trilinear=True) + track_predictions.append( photons_per_pmt ) + + print(f'Number of total photons: {total_photons_added}') + + return track_predictions + +def make_showers_visualization( tracks, showers, adc_images, offset=1 ): + # shower 3D visualization + h_shower_xz = rt.TH2F("h_shower_xz", "shower Projection X-Z;X [cm];Z [cm]", + 100, 0, 256.0, 100, 0, 1036.0) + h_shower_yz = rt.TH2F("h_shower_yz", "shower Projection Y-Z;Y [cm];Z [cm]", + 100, -120, 120.0, 100, 0, 1036.0) + + # Fill shower histogram + for ishower in range( showers.size()): + shower = showers.at(ishower) + for i in range(shower.size()): + hit = shower.at(i) + h_shower_xz.Fill(hit[0],hit[2], offset+ishower) + h_shower_yz.Fill(hit[1],hit[2], offset+ishower) + + return h_shower_xz, h_shower_yz + +def main(): + parser = argparse.ArgumentParser(description="Complete shower to light estimation example") + parser.add_argument("--input-file", required=True, help="ROOT file with shower/image data (optional)") + parser.add_argument("--save-output", action="store_true", help="Save output plots") + parser.add_argument("--charge-per-cm", type=float, default=2000.0, + help="Charge deposition per cm (ADC)") + args = parser.parse_args() + + print("Complete Shower to Light Estimation Example") + print("="*60) + + opflashes = None # Initialize for synthetic data case + + + # Use real data from ROOT file + print(f"\nUsing real data from: {args.input_file}") + adc_images, tracks, showers, opflashes = get_data_from_rootfile( args.input_file ) + + # Show information about optical flashes + print(f"\nFound {len(opflashes)} optical flashes") + if len(opflashes) > 0: + flash = opflashes[0] # Use first flash for comparison + print(f"Using flash 0: Total PE = {flash.TotalPE():.1f}, Time = {flash.Time():.1f} μs") + + + # Convert shower to space points + shower_pmt_preds = get_shower_opdet_predicts( showers, adc_images) + + # Convert track to space points + track_pmt_preds = get_track_opdet_predicts( tracks, adc_images) + + # Create visualization + if args.save_output: + fudge_factor = 1000.0 + print("\nCreating visualization...") + import lardly + from lardly.ubdl.pmtpos import getOpChannelFromOpDet, getOpDetFromOpChannel + + # PMT response histogram (predicted) + # We make a histogram for each object and then a stack histogram as well + h_pmt_pred_stack = rt.THStack("h_pmt_pred_stack", "Total Neutrino Candidate OpDet Prediction") + hists_v = [] + colors = [] + iprong = 0 + tlen = rt.TLegend(0.7,0.7,0.9,0.9) + for itrack, photons_per_pmt in enumerate( track_pmt_preds ): + h_pmt_pred = rt.TH1F(f"h_pmt_pred_track{itrack}", "PMT Light Response;PMT ID;Normalized Response", 32, 0, 32) + + rand_rgb = np.random.randint(0,255,3) + rand_tcolor = rt.TColor.GetColor( rand_rgb[0], rand_rgb[1], rand_rgb[2] ) + pesum = 0.0 + for pmt in range(32): + h_pmt_pred.SetBinContent(pmt+1, photons_per_pmt[pmt]*fudge_factor) + pesum += photons_per_pmt[pmt]*fudge_factor + xcolor = int(51+(iprong//5)+10*(iprong%5)) + print(f"xcolor[{iprong}]: {xcolor}") + h_pmt_pred.SetFillColor( xcolor ) + h_pmt_pred.SetFillStyle(3001) + h_pmt_pred.SetLineColor( xcolor ) + h_pmt_pred_stack.Add( h_pmt_pred ) + tlen.AddEntry( h_pmt_pred, f"Track[{itrack}]: {pesum:0.2f}" ) + hists_v.append( h_pmt_pred ) + colors.append( xcolor ) + iprong += 1 + + for ishower, photons_per_pmt in enumerate( shower_pmt_preds ): + h_pmt_pred = rt.TH1F(f"h_pmt_pred_shower{ishower}", "PMT Light Response;Optical Channel;Normalized Response", 32, 0, 32) + + rand_rgb = np.random.randint(0,255,3) + rand_tcolor = rt.TColor.GetColor( rand_rgb[0], rand_rgb[1], rand_rgb[2] ) + pesum = 0.0 + for pmt in range(32): + # predictions are indexed by opchannel + opchid = pmt + #opdetid = getOpDetFromOpChannel(opchid) + h_pmt_pred.SetBinContent(opchid+1, photons_per_pmt[pmt]*fudge_factor) + pesum += photons_per_pmt[pmt]*fudge_factor + xcolor = int(51+(iprong//5)+10*(iprong%5)) + print(f"xcolor[{iprong}]: {xcolor}") + h_pmt_pred.SetFillColor( xcolor ) + h_pmt_pred.SetFillStyle(3001) + h_pmt_pred_stack.Add( h_pmt_pred ) + tlen.AddEntry( h_pmt_pred, f"Shower[{ishower}]: {pesum:0.2f}" ) + hists_v.append( h_pmt_pred ) + colors.append( xcolor ) + iprong += 1 + + # PMT response histogram (observed) - only if we have opflash data + h_pmt_obs = None + if opflashes is not None and len(opflashes) > 0: + flash = opflashes[0] # Use first flash + h_pmt_obs = rt.TH1F("h_pmt_obs", "PMT Light Response;PMT ID;Normalized Response", 32, 0, 32) + h_pmt_obs.SetLineColor( rt.kBlack ) + h_pmt_obs.SetLineWidth( 2 ) + + # Fill observed PMT response and normalize to 1 + total_obs_pe = 0 + pe_per_pmt = [] + for pmt in range(32): + # opflash is indexed by channel? + #opdetid = getOpDetFromOpChannel( pmt ) + opchanid = pmt + pe = flash.PE(opchanid) + # opflash is indexed by opdet and pmt pred by channel? + #opchid = getOpChannelFromOpDet( pmt ) + #pe = flash.PE(opchid) + pe_per_pmt.append(pe) + total_obs_pe += pe + + # Normalize observed data so total = 1 for shape comparison + if total_obs_pe > 0: + for pmt in range(32): + normalized_pe = pe_per_pmt[pmt] / total_obs_pe + #h_pmt_obs.SetBinContent(pmt+1, normalized_pe) + h_pmt_obs.SetBinContent(pmt+1, pe_per_pmt[pmt]) + + print(f"\nObserved flash: Total PE = {total_obs_pe:.1f}") + print("Observed PE per PMT (>0.1):") + for pmt in range(32): + if pe_per_pmt[pmt] > 0.1: + print(f" PMT {pmt:2d}: {pe_per_pmt[pmt]:6.1f} PE") + + + # # shower 3D visualization + # h_shower_xz = rt.TH2F("h_shower_xz", "shower Projection X-Z;X [cm];Z [cm]", + # 100, 0, 200, 100, 100, 800) + # h_shower_yz = rt.TH2F("h_shower_yz", "shower Projection Y-Z;Y [cm];Z [cm]", + # 100, -50, 150, 100, 100, 800) + + # # Fill shower histograms + # for i in range(shower.size()): + # hit = shower.at(i) + # h_shower_xz.Fill(hit[0],hit[2]) + # h_shower_yz.Fill(hit[1],hit[2]) + + # # Space points with charge + # h_charge_xz = rt.TH2F("h_charge_xz", "Space Points with Charge;X [cm];Z [cm]", + # 100, 0, 200, 100, 100, 800) + # for sp in spacepoints: + # if sp.charge > 0: + # h_charge_xz.Fill(sp.position.X(), sp.position.Z(), sp.charge) + + # Create canvas with space for comparison plot + canvas = rt.TCanvas("c1", "shower Light Estimation", 1600, 1000) + #canvas.Divide(3, 3) + + canvas.cd(1) + pred_max = h_pmt_pred_stack.GetMaximum() + obs_max = h_pmt_obs.GetMaximum() + + if pred_max>obs_max: + h_pmt_pred_stack.Draw("hist") + else: + h_pmt_obs.Draw("hist") + + h_pmt_pred_stack.Draw("histsame") + h_pmt_obs.Draw("E1same") + tlen.Draw() + + # canvas.cd(2) + # h_shower_yz.SetMarkerStyle(20) + # h_shower_yz.SetMarkerColor(rt.kBlue) + # h_shower_yz.Draw("P") + # rt.gPad.SetTitle("shower Path (Y-Z)") + + # canvas.cd(3) + # h_charge_xz.SetMarkerStyle(20) + # h_charge_xz.SetMarkerColor(rt.kRed) + # h_charge_xz.Draw("COLZ") + # rt.gPad.SetTitle("Charge Deposits") + + # # PMT comparison plot + # canvas.cd(4) + # h_pmt_pred.SetLineColor(rt.kRed) + # h_pmt_pred.SetLineWidth(2) + # h_pmt_pred.SetTitle("PMT Response Comparison;PMT ID;Normalized Response") + # max_val = h_pmt_pred.GetMaximum() + + # if h_pmt_obs is not None: + # h_pmt_obs.SetLineColor(rt.kBlue) + # h_pmt_obs.SetLineWidth(2) + # max_val = max(max_val, h_pmt_obs.GetMaximum()) + # h_pmt_pred.GetYaxis().SetRangeUser(0, max_val * 1.1) + # h_pmt_pred.Draw("HIST") + # h_pmt_obs.Draw("HIST SAME") + + # # Add legend + # legend = rt.TLegend(0.6, 0.7, 0.89, 0.89) + # legend.AddEntry(h_pmt_pred, "Predicted", "l") + # legend.AddEntry(h_pmt_obs, "Observed", "l") + # legend.Draw() + # else: + # h_pmt_pred.SetFillColor(rt.kCyan-9) + # h_pmt_pred.Draw("HIST") + + # canvas.cd(5) + # # Individual predicted PMT response (unnormalized) + # h_pmt_pred_raw = rt.TH1F("h_pmt_pred_raw", "Predicted PMT Response;PMT ID;Photoelectrons", + # 32, 0, 32) + # for pmt in range(32): + # h_pmt_pred_raw.SetBinContent(pmt+1, photons_per_pmt[pmt]) + # h_pmt_pred_raw.SetFillColor(rt.kGreen-9) + # h_pmt_pred_raw.Draw("HIST") + + # canvas.cd(6) + # if h_pmt_obs is not None: + # # Individual observed PMT response (unnormalized) + # h_pmt_obs_raw = rt.TH1F("h_pmt_obs_raw", "Observed PMT Response;PMT ID;Photoelectrons", + # 32, 0, 32) + # for pmt in range(32): + # h_pmt_obs_raw.SetBinContent(pmt+1, pe_per_pmt[pmt]) + # h_pmt_obs_raw.SetFillColor(rt.kYellow-9) + # h_pmt_obs_raw.Draw("HIST") + + # # Summary text + # canvas.cd(7) + # text = rt.TText() + # text.SetTextSize(0.08) + # text.DrawText(0.1, 0.8, f"Space Points: {len(spacepoints)}") + # text.DrawText(0.1, 0.7, f"Total Charge: {converter.getTotalCharge():.0f} ADC") + # text.DrawText(0.1, 0.6, f"Emitted Photons: {total_photons_added:.0f}") + # text.DrawText(0.1, 0.5, f"Detected Photons: {total_detected:.0f}") + # text.DrawText(0.1, 0.4, f"Efficiency: {efficiency*100:.2f}%") + # if opflashes is not None and len(opflashes) > 0: + # text.DrawText(0.1, 0.3, f"Observed PE: {total_obs_pe:.0f}") + # text.DrawText(0.1, 0.2, f"Flash Time: {flash.Time():.1f} us") + + canvas.SaveAs("nucand_light_example.png") + print("Saved visualization to nucand_light_example.png") + print("[enter] to exit.") + input() + + print("\nExample completed successfully!") + +if __name__ == "__main__": + main() diff --git a/ublarcvapp/ParticleToPixelUtils/test/test_shower_light_example.py b/ublarcvapp/ParticleToPixelUtils/test/test_shower_light_example.py new file mode 100644 index 0000000..ce7f747 --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/test_shower_light_example.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +""" +Complete example demonstrating the full workflow from shower to light detection estimation. + +This script shows: +1. How to create realistic larlite::shower objects from line segments +2. How to create realistic ADC images with proper geometry and charge deposits +3. How to use showerToSpacePoints to convert showers to space points with charge +4. How to convert charge to photons using LAr physics +5. How to use PhotonVisibilityEstimator to predict light detection + +Usage: + python3 complete_shower_light_example.py [--input-file file.root] [--save-output] +""" + +import ROOT as rt +from ROOT import std +import numpy as np +import argparse +import sys + +# Load libraries +from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp + + +def get_image_and_showers_from_rootfile(rootfile): + """ + Get Image2D and shower examples for testing showerToSpacePoints + and the resulting photon prediction using ublarcvapp.ubphotonlib.PhotonVisibilityEstimator. + The test file is a merged file containing TTrees with larcv objects and + the reconstructed showers. + """ + print(f"Loading data from {rootfile}") + + # Load larcv images + ioman = larcv.IOManager(larcv.IOManager.kREAD) + ioman.add_in_file(rootfile) + ioman.initialize() + + # Get the first entry + ioman.read_entry(0) + + ev_img = ioman.get_data(larcv.kProductImage2D, "wire") + imglist = [] + for p in range(3): + img = ev_img.as_vector().at(p) + imglist.append(img) + print(f" Plane {p}: {img.meta().cols()} x {img.meta().rows()} pixels") + + # Convert to std::vector for showerToSpacePoints + adc_images = std.vector('larcv::Image2D')() + for img in imglist: + adc_images.push_back(img) + + # Get the opflash data + ioll = larlite.storage_manager(larlite.storage_manager.kREAD) + ioll.add_in_filename(rootfile) + ioll.open() + ioll.go_to(0) + + # Get opflash data using correct casting + ev_opflash = ioll.get_data(larlite.data.kOpFlash,"simpleFlashBeam") + opflashes = std.vector('larlite::opflash')() + + # Try to access the opflash data + print(f" Found {ev_opflash.size()} optical flashes") + for iopflash in range(ev_opflash.size()): + opflashes.push_back(ev_opflash.at(iopflash)) + + # Get the larlite::shower examples from the reco file + reco_tfile = rt.TFile(rootfile, 'open') + recotree = reco_tfile.Get("KPSRecoManagerTree") + showerout_v = std.vector('larlite::larflowcluster')() + + if not recotree: + print("Warning: Could not find KPSRecoManagerTree in file") + return adc_images, showerout_v, opflashes + + recotree.GetEntry(0) + + # Check if we have the expected structure + if not hasattr(recotree, 'nuvetoed_v') or recotree.nuvetoed_v.size() == 0: + print("Warning: No nuvetoed_v found in tree") + return adc_images, [] + + nuvtx = recotree.nuvetoed_v.at(0) + + print(f" Found {nuvtx.shower_v.size()} showers") + + + for ishower in range(nuvtx.shower_v.size()): + shower = nuvtx.shower_v.at(ishower) + if shower.size()>=2: + showerout_v.push_back(shower) + print(f" Shower {ishower}: {shower.size()} points") + + ioman.finalize() + reco_tfile.Close() + + return adc_images, showerout_v, opflashes + + +def convert_charge_to_photons(adc_charge): + """ + Convert ADC charge to number of scintillation photons + + Args: + adc_charge: Charge in ADC counts + + Returns: + Number of scintillation photons + """ + # LAr physics parameters (approximate) + adc_per_electron = 200.0 # ADC counts per electron + mev_per_electron = 23.6e-6 # Ionization energy in LAr (MeV) + photons_per_mev = 24000.0 # Scintillation photons per MeV at 500V/cm + recombination_factor = 0.7 # Fraction surviving recombination + + # Convert: ADC -> electrons -> energy -> photons + n_electrons = adc_charge / adc_per_electron + energy_mev = n_electrons * mev_per_electron + n_photons = energy_mev * photons_per_mev * (1.0 - recombination_factor) + + return n_photons + +def main(): + parser = argparse.ArgumentParser(description="Complete shower to light estimation example") + parser.add_argument("--input-file", required=True, help="ROOT file with shower/image data (optional)") + parser.add_argument("--save-output", action="store_true", help="Save output plots") + parser.add_argument("--charge-per-cm", type=float, default=2000.0, + help="Charge deposition per cm (ADC)") + args = parser.parse_args() + + print("Complete Shower to Light Estimation Example") + print("="*60) + + opflashes = None # Initialize for synthetic data case + + + # Use real data from ROOT file + print(f"\nUsing real data from: {args.input_file}") + adc_images, showers, opflashes = get_image_and_showers_from_rootfile(args.input_file) + + if len(showers) == 0: + print("No showers found in input file, exiting") + return + + # Use the first shower for demonstration + shower = showers[0] + print(f"\nUsing shower 0: {shower.size()} points") + + # Show information about optical flashes + print(f"\nFound {len(opflashes)} optical flashes") + if len(opflashes) > 0: + flash = opflashes[0] # Use first flash for comparison + print(f"Using flash 0: Total PE = {flash.TotalPE():.1f}, Time = {flash.Time():.1f} μs") + + + # Convert shower to space points + print("\nConverting shower to space points...") + converter = ublarcvapp.pixelutils.ShowerToSpacePoints() + converter.setUseChargeWeighting(False) + + spacepoints = converter.convertShower( + shower, adc_images, + threshold=10.0, # ADC threshold + dcol=3, # wire window + drow=3 # tick window + ) + + print(f"Generated {len(spacepoints)} space points") + print(f"Pixels processed: {converter.getNumPixelsProcessed()}") + print(f"Total charge collected: {converter.getTotalCharge():.0f} ADC") + + # Convert to photon sources + print("\nConverting charge to photons...") + photon_estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + total_photons_added = 0 + for i, sp in enumerate(spacepoints): + if sp.charge > 5.0: # Only use points with significant charge + n_photons = convert_charge_to_photons(sp.charge) + + photon_estimator.addPhotonSource( + sp.position.X(), + sp.position.Y(), + sp.position.Z(), + n_photons + ) + total_photons_added += n_photons + + if i < 5: # Print first few + print(f" Point {i}: ({sp.position.X():.1f}, {sp.position.Y():.1f}, {sp.position.Z():.1f}) " + f"charge={sp.charge:.1f} -> {n_photons:.0f} photons") + + print(f"Added {photon_estimator.getNumSources()} photon sources") + print(f"Total emitted photons: {total_photons_added:.0f}") + + # Estimate light detection + print("\nEstimating light detection...") + + # Calculate with trilinear interpolation + photons_per_pmt = photon_estimator.calculateDetectedPhotons(use_trilinear=True) + total_detected = photon_estimator.getTotalDetectedPhotons(True) + efficiency = photon_estimator.getCollectionEfficiency(True) + + print(f"Total detected photons: {total_detected:.1f}") + print(f"Collection efficiency: {efficiency:.4f} ({efficiency*100:.2f}%)") + + # Show detection by PMT + print("\nPhotons detected by PMT:") + for pmt in range(32): + pe = photons_per_pmt[pmt] + if pe > 0.5: + print(f" PMT {pmt:2d}: {pe:7.1f} PE") + + # Create visualization + if args.save_output: + print("\nCreating visualization...") + + # PMT response histogram (predicted) + h_pmt_pred = rt.TH1F("h_pmt_pred", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred.SetBinContent(pmt+1, photons_per_pmt[pmt]) + + # PMT response histogram (observed) - only if we have opflash data + h_pmt_obs = None + if opflashes is not None and len(opflashes) > 0: + flash = opflashes[0] # Use first flash + h_pmt_obs = rt.TH1F("h_pmt_obs", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + + # Fill observed PMT response and normalize to 1 + total_obs_pe = 0 + pe_per_pmt = [] + for pmt in range(32): + pe = flash.PE(pmt) + pe_per_pmt.append(pe) + total_obs_pe += pe + + # Normalize observed data so total = 1 for shape comparison + if total_obs_pe > 0: + for pmt in range(32): + normalized_pe = pe_per_pmt[pmt] / total_obs_pe + h_pmt_obs.SetBinContent(pmt+1, normalized_pe) + + print(f"\nObserved flash: Total PE = {total_obs_pe:.1f}") + print("Observed PE per PMT (>0.1):") + for pmt in range(32): + if pe_per_pmt[pmt] > 0.1: + print(f" PMT {pmt:2d}: {pe_per_pmt[pmt]:6.1f} PE") + + # Normalize predicted data to 1 for shape comparison + if total_detected > 0: + for pmt in range(32): + normalized_pred = photons_per_pmt[pmt] / total_detected + h_pmt_pred.SetBinContent(pmt+1, normalized_pred) + + # shower 3D visualization + h_shower_xz = rt.TH2F("h_shower_xz", "shower Projection X-Z;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + h_shower_yz = rt.TH2F("h_shower_yz", "shower Projection Y-Z;Y [cm];Z [cm]", + 100, -50, 150, 100, 100, 800) + + # Fill shower histograms + for i in range(shower.size()): + hit = shower.at(i) + h_shower_xz.Fill(hit[0],hit[2]) + h_shower_yz.Fill(hit[1],hit[2]) + + # Space points with charge + h_charge_xz = rt.TH2F("h_charge_xz", "Space Points with Charge;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + for sp in spacepoints: + if sp.charge > 0: + h_charge_xz.Fill(sp.position.X(), sp.position.Z(), sp.charge) + + # Create canvas with space for comparison plot + canvas = rt.TCanvas("c1", "shower Light Estimation", 1600, 1000) + canvas.Divide(3, 3) + + canvas.cd(1) + h_shower_xz.SetMarkerStyle(20) + h_shower_xz.SetMarkerColor(rt.kBlue) + h_shower_xz.Draw("P") + rt.gPad.SetTitle("shower Path (X-Z)") + + canvas.cd(2) + h_shower_yz.SetMarkerStyle(20) + h_shower_yz.SetMarkerColor(rt.kBlue) + h_shower_yz.Draw("P") + rt.gPad.SetTitle("shower Path (Y-Z)") + + canvas.cd(3) + h_charge_xz.SetMarkerStyle(20) + h_charge_xz.SetMarkerColor(rt.kRed) + h_charge_xz.Draw("COLZ") + rt.gPad.SetTitle("Charge Deposits") + + # PMT comparison plot + canvas.cd(4) + h_pmt_pred.SetLineColor(rt.kRed) + h_pmt_pred.SetLineWidth(2) + h_pmt_pred.SetTitle("PMT Response Comparison;PMT ID;Normalized Response") + max_val = h_pmt_pred.GetMaximum() + + if h_pmt_obs is not None: + h_pmt_obs.SetLineColor(rt.kBlue) + h_pmt_obs.SetLineWidth(2) + max_val = max(max_val, h_pmt_obs.GetMaximum()) + h_pmt_pred.GetYaxis().SetRangeUser(0, max_val * 1.1) + h_pmt_pred.Draw("HIST") + h_pmt_obs.Draw("HIST SAME") + + # Add legend + legend = rt.TLegend(0.6, 0.7, 0.89, 0.89) + legend.AddEntry(h_pmt_pred, "Predicted", "l") + legend.AddEntry(h_pmt_obs, "Observed", "l") + legend.Draw() + else: + h_pmt_pred.SetFillColor(rt.kCyan-9) + h_pmt_pred.Draw("HIST") + + canvas.cd(5) + # Individual predicted PMT response (unnormalized) + h_pmt_pred_raw = rt.TH1F("h_pmt_pred_raw", "Predicted PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred_raw.SetBinContent(pmt+1, photons_per_pmt[pmt]) + h_pmt_pred_raw.SetFillColor(rt.kGreen-9) + h_pmt_pred_raw.Draw("HIST") + + canvas.cd(6) + if h_pmt_obs is not None: + # Individual observed PMT response (unnormalized) + h_pmt_obs_raw = rt.TH1F("h_pmt_obs_raw", "Observed PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_obs_raw.SetBinContent(pmt+1, pe_per_pmt[pmt]) + h_pmt_obs_raw.SetFillColor(rt.kYellow-9) + h_pmt_obs_raw.Draw("HIST") + + # Summary text + canvas.cd(7) + text = rt.TText() + text.SetTextSize(0.08) + text.DrawText(0.1, 0.8, f"Space Points: {len(spacepoints)}") + text.DrawText(0.1, 0.7, f"Total Charge: {converter.getTotalCharge():.0f} ADC") + text.DrawText(0.1, 0.6, f"Emitted Photons: {total_photons_added:.0f}") + text.DrawText(0.1, 0.5, f"Detected Photons: {total_detected:.0f}") + text.DrawText(0.1, 0.4, f"Efficiency: {efficiency*100:.2f}%") + if opflashes is not None and len(opflashes) > 0: + text.DrawText(0.1, 0.3, f"Observed PE: {total_obs_pe:.0f}") + text.DrawText(0.1, 0.2, f"Flash Time: {flash.Time():.1f} us") + + canvas.SaveAs("complete_shower_light_example.png") + print("Saved visualization to complete_shower_light_example.png") + + print("\nExample completed successfully!") + +if __name__ == "__main__": + main() diff --git a/ublarcvapp/ParticleToPixelUtils/test/test_shower_simple.py b/ublarcvapp/ParticleToPixelUtils/test/test_shower_simple.py new file mode 100644 index 0000000..35f430a --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/test_shower_simple.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +Simple test for ShowerToSpacePoints without photon library +""" + +import ROOT as rt +from ROOT import std +from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp +import numpy as np + +# Create synthetic shower +print("Creating synthetic shower...") +cluster = larlite.larflowcluster() + +# Create 10 hits in a shower pattern +for i in range(10): + hit = larlite.larflow3dhit() + hit.clear() + + # Simple positions + x = 70.0 + np.random.normal(0, 2) + y = 0.0 + np.random.normal(0, 5) + z = 500.0 + np.random.uniform(0, 20) + + hit.push_back(float(x)) + hit.push_back(float(y)) + hit.push_back(float(z)) + + cluster.push_back(hit) + print(f" Hit {i}: ({x:.1f}, {y:.1f}, {z:.1f})") + +print(f"\nCreated cluster with {cluster.size()} hits") + +# Create simple ADC images +print("\nCreating ADC images...") +rows = 2048 +cols = 3456 + +# Simple metadata +meta = larcv.ImageMeta(cols, rows, rows, cols, 0, 3000, 0) +img = larcv.Image2D(meta) +img.paint(0) + +# Add some charge at expected locations +for row in range(1000, 1500, 10): + for col in range(1500, 2000, 10): + img.set_pixel(row, col, 50.0) + +adc_images = std.vector('larcv::Image2D')() +adc_images.push_back(img) +adc_images.push_back(img) +adc_images.push_back(img) + +# Test ShowerToSpacePoints +print("\nTesting ShowerToSpacePoints...") +converter = ublarcvapp.pixelutils.ShowerToSpacePoints() + +spacepoints = converter.convertShower(cluster, adc_images, 10.0, 5, 5) + +print(f"\nResults:") +print(f" Space points found: {len(spacepoints)}") +print(f" Pixels processed: {converter.getNumPixelsProcessed()}") +print(f" Total charge: {converter.getTotalCharge():.1f}") + +if len(spacepoints) > 0: + print("\nFirst few space points:") + for i, sp in enumerate(spacepoints[:3]): + print(f" SP {i}: pos=({sp.position.X():.1f}, {sp.position.Y():.1f}, {sp.position.Z():.1f}) charge={sp.charge:.1f}") +else: + print("\nNo space points found - this may be due to:") + print(" - Mismatch between hit positions and charge deposit locations") + print(" - Tick/wire conversion issues") + print(" - Threshold too high") + +print("\nShowerToSpacePoints class is working!") \ No newline at end of file diff --git a/ublarcvapp/ParticleToPixelUtils/test/test_track_light_example.py b/ublarcvapp/ParticleToPixelUtils/test/test_track_light_example.py new file mode 100644 index 0000000..340f19c --- /dev/null +++ b/ublarcvapp/ParticleToPixelUtils/test/test_track_light_example.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python3 +""" +Complete example demonstrating the full workflow from track to light detection estimation. + +This script shows: +1. How to create realistic larlite::track objects from line segments +2. How to create realistic ADC images with proper geometry and charge deposits +3. How to use TrackToSpacePoints to convert tracks to space points with charge +4. How to convert charge to photons using LAr physics +5. How to use PhotonVisibilityEstimator to predict light detection + +Usage: + python3 complete_track_light_example.py [--input-file file.root] [--save-output] +""" + +import ROOT as rt +from ROOT import std +import numpy as np +import argparse +import sys + +# Load libraries +from larcv import larcv +from larlite import larlite +from ublarcvapp import ublarcvapp + +def get_image_and_tracks_from_rootfile(rootfile): + """ + Get Image2D and track examples for testing TrackToSpacePoints + and the resulting photon prediction using ublarcvapp.ubphotonlib.PhotonVisibilityEstimator. + The test file is a merged file containing TTrees with larcv objects and + the reconstructed tracks. + """ + print(f"Loading data from {rootfile}") + + # Load larcv images + ioman = larcv.IOManager(larcv.IOManager.kREAD) + ioman.add_in_file(rootfile) + ioman.initialize() + + # Get the first entry + ioman.read_entry(0) + + ev_img = ioman.get_data(larcv.kProductImage2D, "wire") + imglist = [] + for p in range(3): + img = ev_img.as_vector().at(p) + imglist.append(img) + print(f" Plane {p}: {img.meta().cols()} x {img.meta().rows()} pixels") + + # Convert to std::vector for TrackToSpacePoints + adc_images = std.vector('larcv::Image2D')() + for img in imglist: + adc_images.push_back(img) + + # Get the opflash data + ioll = larlite.storage_manager(larlite.storage_manager.kREAD) + ioll.add_in_filename(rootfile) + ioll.open() + ioll.go_to(0) + + # Get opflash data using correct casting + ev_opflash = ioll.get_data(larlite.data.kOpFlash,"simpleFlashBeam") + opflashes = std.vector('larlite::opflash')() + + # Try to access the opflash data + print(f" Found {ev_opflash.size()} optical flashes") + for iopflash in range(ev_opflash.size()): + opflashes.push_back(ev_opflash.at(iopflash)) + + # Get the larlite::track examples from the reco file + reco_tfile = rt.TFile(rootfile, 'open') + recotree = reco_tfile.Get("KPSRecoManagerTree") + trackout_v = std.vector('larlite::track')() + + if not recotree: + print("Warning: Could not find KPSRecoManagerTree in file") + return adc_images, trackout_v, opflashes + + recotree.GetEntry(0) + + # Check if we have the expected structure + if not hasattr(recotree, 'nuvetoed_v') or recotree.nuvetoed_v.size() == 0: + print("Warning: No nuvetoed_v found in tree") + return adc_images, [] + + nuvtx = recotree.nuvetoed_v.at(0) + + print(f" Found {nuvtx.track_v.size()} tracks") + + + for itrack in range(nuvtx.track_v.size()): + track = nuvtx.track_v.at(itrack) + if track.NumberTrajectoryPoints()>=2: + trackout_v.push_back(track) + print(f" Track {itrack}: {track.NumberTrajectoryPoints()} points, length {track.Length():.1f} cm") + + ioman.finalize() + reco_tfile.Close() + + return adc_images, trackout_v, opflashes + + +def convert_charge_to_photons(adc_charge): + """ + Convert ADC charge to number of scintillation photons + + Args: + adc_charge: Charge in ADC counts + + Returns: + Number of scintillation photons + """ + # LAr physics parameters (approximate) + adc_per_electron = 200.0 # ADC counts per electron + mev_per_electron = 23.6e-6 # Ionization energy in LAr (MeV) + photons_per_mev = 24000.0 # Scintillation photons per MeV at 500V/cm + recombination_factor = 0.7 # Fraction surviving recombination + + # Convert: ADC -> electrons -> energy -> photons + n_electrons = adc_charge / adc_per_electron + energy_mev = n_electrons * mev_per_electron + n_photons = energy_mev * photons_per_mev * (1.0 - recombination_factor) + + return n_photons + +def main(): + parser = argparse.ArgumentParser(description="Complete track to light estimation example") + parser.add_argument("--input-file", required=True, help="ROOT file with track/image data (optional)") + parser.add_argument("--save-output", action="store_true", help="Save output plots") + parser.add_argument("--charge-per-cm", type=float, default=2000.0, + help="Charge deposition per cm (ADC)") + args = parser.parse_args() + + print("Complete Track to Light Estimation Example") + print("="*60) + + opflashes = None # Initialize for synthetic data case + + # Use real data from ROOT file + print(f"\nUsing real data from: {args.input_file}") + adc_images, tracks, opflashes = get_image_and_tracks_from_rootfile(args.input_file) + + if len(tracks) == 0: + print("No tracks found in input file, exiting") + return + + # Use the first track for demonstration + track = tracks[0] + track_start = track.LocationAtPoint(0) + print(f"\nUsing track 0: {track.NumberTrajectoryPoints()} points, length {track.Length():.1f} cm") + print(f"Start: ({track_start.X():.1f}, {track_start.Y():.1f}, {track_start.Z():.1f})") + print(f"End: ({track.End().X():.1f}, {track.End().Y():.1f}, {track.End().Z():.1f})") + + # Show information about optical flashes + print(f"\nFound {len(opflashes)} optical flashes") + if len(opflashes) > 0: + flash = opflashes[0] # Use first flash for comparison + print(f"Using flash 0: Total PE = {flash.TotalPE():.1f}, Time = {flash.Time():.1f} μs") + else: + flash = None + + # Convert track to space points + print("\nConverting track to space points...") + converter = ublarcvapp.pixelutils.TrackToSpacePoints() + converter.setUseChargeWeighting(False) + + spacepoints = converter.convertTrack( + track, adc_images, + threshold=10.0, # ADC threshold + dcol=3, # wire window + drow=3, # tick window + minstepsize=0.3, # min step (cm) + maxstepsize=0.5 # max step (cm) + ) + + print(f"Generated {len(spacepoints)} space points") + print(f"Pixels processed: {converter.getNumPixelsProcessed()}") + print(f"Total charge collected: {converter.getTotalCharge():.0f} ADC") + + # Convert to photon sources + print("\nConverting charge to photons...") + photon_estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + total_photons_added = 0 + for i, sp in enumerate(spacepoints): + if sp.charge > 5.0: # Only use points with significant charge + n_photons = convert_charge_to_photons(sp.charge) + + photon_estimator.addPhotonSource( + sp.position.X(), + sp.position.Y(), + sp.position.Z(), + n_photons + ) + total_photons_added += n_photons + + if i < 5: # Print first few + print(f" Point {i}: ({sp.position.X():.1f}, {sp.position.Y():.1f}, {sp.position.Z():.1f}) " + f"charge={sp.charge:.1f} -> {n_photons:.0f} photons") + + print(f"Added {photon_estimator.getNumSources()} photon sources") + print(f"Total emitted photons: {total_photons_added:.0f}") + + # Estimate light detection + print("\nEstimating light detection...") + + # Calculate with trilinear interpolation + photons_per_pmt = photon_estimator.calculateDetectedPhotons(use_trilinear=True) + total_detected = photon_estimator.getTotalDetectedPhotons(True) + efficiency = photon_estimator.getCollectionEfficiency(True) + + print(f"Total detected photons: {total_detected:.1f}") + print(f"Collection efficiency: {efficiency:.4f} ({efficiency*100:.2f}%)") + + # Show detection by PMT + print("\nPhotons detected by PMT:") + for pmt in range(32): + pe = photons_per_pmt[pmt] + if pe > 0.5: + print(f" PMT {pmt:2d}: {pe:7.1f} PE") + + # Create visualization + if args.save_output: + print("\nCreating visualization...") + + # PMT response histogram (predicted) + h_pmt_pred = rt.TH1F("h_pmt_pred", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred.SetBinContent(pmt+1, photons_per_pmt[pmt]) + + # PMT response histogram (observed) - only if we have opflash data + h_pmt_obs = None + if opflashes is not None and len(opflashes) > 0: + flash = opflashes[0] # Use first flash + h_pmt_obs = rt.TH1F("h_pmt_obs", "PMT Light Response;PMT ID;Normalized Response", + 32, 0, 32) + + # Fill observed PMT response and normalize to 1 + total_obs_pe = 0 + pe_per_pmt = [] + for pmt in range(32): + pe = flash.PE(pmt) + pe_per_pmt.append(pe) + total_obs_pe += pe + + # Normalize observed data so total = 1 for shape comparison + if total_obs_pe > 0: + for pmt in range(32): + normalized_pe = pe_per_pmt[pmt] / total_obs_pe + h_pmt_obs.SetBinContent(pmt+1, normalized_pe) + + print(f"\nObserved flash: Total PE = {total_obs_pe:.1f}") + print("Observed PE per PMT (>0.1):") + for pmt in range(32): + if pe_per_pmt[pmt] > 0.1: + print(f" PMT {pmt:2d}: {pe_per_pmt[pmt]:6.1f} PE") + + # Normalize predicted data to 1 for shape comparison + if total_detected > 0: + for pmt in range(32): + normalized_pred = photons_per_pmt[pmt] / total_detected + h_pmt_pred.SetBinContent(pmt+1, normalized_pred) + + # Track 3D visualization + h_track_xz = rt.TH2F("h_track_xz", "Track Projection X-Z;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + h_track_yz = rt.TH2F("h_track_yz", "Track Projection Y-Z;Y [cm];Z [cm]", + 100, -50, 150, 100, 100, 800) + + # Fill track histograms + for i in range(track.NumberTrajectoryPoints()): + pos = track.LocationAtPoint(i) + h_track_xz.Fill(pos.X(), pos.Z()) + h_track_yz.Fill(pos.Y(), pos.Z()) + + # Space points with charge + h_charge_xz = rt.TH2F("h_charge_xz", "Space Points with Charge;X [cm];Z [cm]", + 100, 0, 200, 100, 100, 800) + for sp in spacepoints: + if sp.charge > 0: + h_charge_xz.Fill(sp.position.X(), sp.position.Z(), sp.charge) + + # Create canvas with space for comparison plot + canvas = rt.TCanvas("c1", "Track Light Estimation", 1600, 1000) + canvas.Divide(3, 3) + + canvas.cd(1) + h_track_xz.SetMarkerStyle(20) + h_track_xz.SetMarkerColor(rt.kBlue) + h_track_xz.Draw("P") + rt.gPad.SetTitle("Track Path (X-Z)") + + canvas.cd(2) + h_track_yz.SetMarkerStyle(20) + h_track_yz.SetMarkerColor(rt.kBlue) + h_track_yz.Draw("P") + rt.gPad.SetTitle("Track Path (Y-Z)") + + canvas.cd(3) + h_charge_xz.SetMarkerStyle(20) + h_charge_xz.SetMarkerColor(rt.kRed) + h_charge_xz.Draw("COLZ") + rt.gPad.SetTitle("Charge Deposits") + + # PMT comparison plot + canvas.cd(4) + h_pmt_pred.SetLineColor(rt.kRed) + h_pmt_pred.SetLineWidth(2) + h_pmt_pred.SetTitle("PMT Response Comparison;PMT ID;Normalized Response") + max_val = h_pmt_pred.GetMaximum() + + if h_pmt_obs is not None: + h_pmt_obs.SetLineColor(rt.kBlue) + h_pmt_obs.SetLineWidth(2) + max_val = max(max_val, h_pmt_obs.GetMaximum()) + h_pmt_pred.GetYaxis().SetRangeUser(0, max_val * 1.1) + h_pmt_pred.Draw("HIST") + h_pmt_obs.Draw("HIST SAME") + + # Add legend + legend = rt.TLegend(0.6, 0.7, 0.89, 0.89) + legend.AddEntry(h_pmt_pred, "Predicted", "l") + legend.AddEntry(h_pmt_obs, "Observed", "l") + legend.Draw() + else: + h_pmt_pred.SetFillColor(rt.kCyan-9) + h_pmt_pred.Draw("HIST") + + canvas.cd(5) + # Individual predicted PMT response (unnormalized) + h_pmt_pred_raw = rt.TH1F("h_pmt_pred_raw", "Predicted PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_pred_raw.SetBinContent(pmt+1, photons_per_pmt[pmt]) + h_pmt_pred_raw.SetFillColor(rt.kGreen-9) + h_pmt_pred_raw.Draw("HIST") + + canvas.cd(6) + if h_pmt_obs is not None: + # Individual observed PMT response (unnormalized) + h_pmt_obs_raw = rt.TH1F("h_pmt_obs_raw", "Observed PMT Response;PMT ID;Photoelectrons", + 32, 0, 32) + for pmt in range(32): + h_pmt_obs_raw.SetBinContent(pmt+1, pe_per_pmt[pmt]) + h_pmt_obs_raw.SetFillColor(rt.kYellow-9) + h_pmt_obs_raw.Draw("HIST") + + # Summary text + canvas.cd(7) + text = rt.TText() + text.SetTextSize(0.08) + text.DrawText(0.1, 0.9, f"Track Length: {track.Length():.1f} cm") + text.DrawText(0.1, 0.8, f"Space Points: {len(spacepoints)}") + text.DrawText(0.1, 0.7, f"Total Charge: {converter.getTotalCharge():.0f} ADC") + text.DrawText(0.1, 0.6, f"Emitted Photons: {total_photons_added:.0f}") + text.DrawText(0.1, 0.5, f"Detected Photons: {total_detected:.0f}") + text.DrawText(0.1, 0.4, f"Efficiency: {efficiency*100:.2f}%") + if opflashes is not None and len(opflashes) > 0: + text.DrawText(0.1, 0.3, f"Observed PE: {total_obs_pe:.0f}") + text.DrawText(0.1, 0.2, f"Flash Time: {flash.Time():.1f} us") + + canvas.SaveAs("complete_track_light_example.png") + print("Saved visualization to complete_track_light_example.png") + + print("\nExample completed successfully!") + +if __name__ == "__main__": + main() diff --git a/ublarcvapp/Reco3D/AStar3DAlgo.cxx b/ublarcvapp/Reco3D/AStar3DAlgo.cxx index 6a28685..82d1f1c 100644 --- a/ublarcvapp/Reco3D/AStar3DAlgo.cxx +++ b/ublarcvapp/Reco3D/AStar3DAlgo.cxx @@ -6,8 +6,8 @@ #include // larlite -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" // ublarcvapp #include "ublarcvapp/UBWireTool/UBWireTool.h" diff --git a/ublarcvapp/Reco3D/AStar3DAlgo.h b/ublarcvapp/Reco3D/AStar3DAlgo.h index 3a09f43..d3e5170 100644 --- a/ublarcvapp/Reco3D/AStar3DAlgo.h +++ b/ublarcvapp/Reco3D/AStar3DAlgo.h @@ -23,9 +23,9 @@ Uses Image2D to hold image. #include "larcv/core/DataFormat/Image2D.h" #include "larcv/core/Base/PSet.h" -#include "AStar3DAlgoConfig.h" -#include "AStar3DTypes.h" -#include "Lattice.h" +#include "ublarcvapp/Reco3D/Lattice.h" +#include "ublarcvapp/Reco3D/AStar3DTypes.h" +#include "ublarcvapp/Reco3D/AStar3DAlgoConfig.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/AStar3DAlgoProton.cxx b/ublarcvapp/Reco3D/AStar3DAlgoProton.cxx index b368573..4cd974f 100644 --- a/ublarcvapp/Reco3D/AStar3DAlgoProton.cxx +++ b/ublarcvapp/Reco3D/AStar3DAlgoProton.cxx @@ -11,8 +11,8 @@ #include "ublarcvapp/UBWireTool/UBWireTool.h" // larlite -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/AStar3DAlgoProton.h b/ublarcvapp/Reco3D/AStar3DAlgoProton.h index 29652b5..9de2cf4 100644 --- a/ublarcvapp/Reco3D/AStar3DAlgoProton.h +++ b/ublarcvapp/Reco3D/AStar3DAlgoProton.h @@ -22,7 +22,7 @@ // larcv #include "larcv/core/DataFormat/Image2D.h" #include "larcv/core/Base/PSet.h" -#include "AStar3DAlgo.h" +#include "ublarcvapp/Reco3D/AStar3DAlgo.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/AStarTracker.cxx b/ublarcvapp/Reco3D/AStarTracker.cxx index 011aaf6..459b8fd 100644 --- a/ublarcvapp/Reco3D/AStarTracker.cxx +++ b/ublarcvapp/Reco3D/AStarTracker.cxx @@ -2,15 +2,16 @@ #define LARLITE_AStarTracker_CXX #include "AStarTracker.h" -#include "DataFormat/track.h" -#include "DataFormat/vertex.h" -#include "DataFormat/hit.h" + +#include "larlite/DataFormat/track.h" +#include "larlite/DataFormat/vertex.h" +#include "larlite/DataFormat/hit.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/wire.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/GeometryHelper.h" +#include "larlite/LArUtil/TimeService.h" #include "larcv/core/DataFormat/Image2D.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/wire.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/GeometryHelper.h" -#include "LArUtil/TimeService.h" #include "TCanvas.h" #include "TF1.h" diff --git a/ublarcvapp/Reco3D/AStarTracker.h b/ublarcvapp/Reco3D/AStarTracker.h index 78fc18a..0ae80b6 100644 --- a/ublarcvapp/Reco3D/AStarTracker.h +++ b/ublarcvapp/Reco3D/AStarTracker.h @@ -18,13 +18,16 @@ #include #include -#include "Analysis/ana_base.h" -#include "DataFormat/track.h" -#include "DataFormat/mctrack.h" -#include +//#include "Analysis/ana_base.h" +#include "larlite/DataFormat/track.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/wire.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" #include "larcv/core/DataFormat/Image2D.h" #include "larcv/core/DataFormat/ImageMeta.h" -#include "DataFormat/wire.h" +#include "larcv/core/Processor/ProcessFactory.h" +#include "larcv/core/Processor/ProcessBase.h" +#include #include "TH1D.h" #include "TH2D.h" #include "TSpline.h" @@ -35,12 +38,9 @@ //#include "LArCV/core/DataFormat/ChStatus.h" //#include "larcv/app/LArOpenCVHandle/LArbysUtils.h" -#include "AStar3DAlgo.h" -#include "AStar3DAlgoProton.h" +#include "ublarcvapp/Reco3D/AStar3DAlgo.h" +#include "ublarcvapp/Reco3D/AStar3DAlgoProton.h" -#include "larcv/core/Processor/ProcessBase.h" -#include "larcv/core/Processor/ProcessFactory.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/AStarUtils.cxx b/ublarcvapp/Reco3D/AStarUtils.cxx index d98ce01..5437269 100644 --- a/ublarcvapp/Reco3D/AStarUtils.cxx +++ b/ublarcvapp/Reco3D/AStarUtils.cxx @@ -2,8 +2,8 @@ #define ASTARUTILS_CXX #include "AStarUtils.h" -#include "LArUtil/GeometryHelper.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/GeometryHelper.h" +#include "larlite/LArUtil/LArProperties.h" #include #include diff --git a/ublarcvapp/Reco3D/CMakeLists.txt b/ublarcvapp/Reco3D/CMakeLists.txt index 14d926f..7a5e68f 100644 --- a/ublarcvapp/Reco3D/CMakeLists.txt +++ b/ublarcvapp/Reco3D/CMakeLists.txt @@ -1,28 +1,61 @@ set(MODULE_NAME Reco3D) -# Collect the headers -file(GLOB HEADERS "*.h") - -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") - # library name set(LIBNAME LArCVApp_${MODULE_NAME}) -# includes -include_directories(${LARCV_INCLUDE_DIR} $ENV{UBLARCVAPP_BASEDIR} ${LARLITE_INC_DIRS} ${OpenCV_INCLUDE_DIRS}) +# Collect the headers +set( HEADERS + Lattice.h + AStar3DTypes.h + AStar3DAlgoConfig.h + AStarUtils.h + AStar3DAlgo.h + AStar3DAlgoProton.h + AStarTracker.h + Run3DTracker.h + TrackReverser.h + TrackerEventDisplay.h +) + +# collect sources +add_library( ${LIBNAME} SHARED + Lattice.cxx + AStar3DAlgoConfig.cxx + AStarUtils.cxx + AStar3DAlgo.cxx + AStar3DAlgoProton.cxx + AStarTracker.cxx + Run3DTracker.cxx + TrackReverser.cxx + TrackerEventDisplay.cxx +) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_include_directories(${LIBNAME} + PUBLIC + $ + PRIVATE + ${PROJECT_SOURCE_DIR} + ${LARCV_INCLUDE_DIR} + ${LARLITE_INC_DIRS} + ${OpenCV_INCLUDE_DIRS} +) +target_link_libraries( ${LIBNAME} PUBLIC + LArCVCoreBase + LArCVCoreDataFormat +) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARCV_INCLUDE_DIR} + ${LARLITE_INC_DIRS} + ${OpenCV_INCLUDE_DIRS} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${OpenCV_INCLUDE_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat opencv_core) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/Reco3D/Lattice.cxx b/ublarcvapp/Reco3D/Lattice.cxx index 2f3a805..7b59fe4 100644 --- a/ublarcvapp/Reco3D/Lattice.cxx +++ b/ublarcvapp/Reco3D/Lattice.cxx @@ -1,8 +1,8 @@ #include "Lattice.h" // larlite -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/Lattice.h b/ublarcvapp/Reco3D/Lattice.h index b49735f..3393bba 100644 --- a/ublarcvapp/Reco3D/Lattice.h +++ b/ublarcvapp/Reco3D/Lattice.h @@ -5,7 +5,7 @@ #include "larcv/core/DataFormat/ImageMeta.h" -#include "AStar3DTypes.h" +#include "ublarcvapp/Reco3D/AStar3DTypes.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/LinkDef.h b/ublarcvapp/Reco3D/LinkDef.h index 41fc076..65eeef6 100644 --- a/ublarcvapp/Reco3D/LinkDef.h +++ b/ublarcvapp/Reco3D/LinkDef.h @@ -4,8 +4,8 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ namespace ublarcvapp+; -#pragma link C++ namespace ublarcvapp::reco3d+; +#pragma link C++ namespace ublarcvapp::reco3d; +#pragma link C++ class ublarcvapp::reco3d::Lattice+; #pragma link C++ class ublarcvapp::reco3d::A3DPixPos_t+; #pragma link C++ class ublarcvapp::reco3d::AStar3DNode+; #pragma link C++ class std::vector+; @@ -15,11 +15,10 @@ #pragma link C++ class ublarcvapp::reco3d::AStarTracker; #pragma link C++ class ublarcvapp::reco3d::AStarTracker; #pragma link C++ class std::map< ublarcvapp::reco3d::A3DPixPos_t, ublarcvapp::reco3d::AStar3DNode* >+; -#pragma link C++ class ublarcvapp::reco3d::Lattice+; #pragma link C++ class ublarcvapp::reco3d::AStar3DNodePtrList+; -#pragma link C++ class ublarcvapp::reco3d::ReadJarrettFile+; -#pragma link C++ class ublarcvapp::reco3d::ReadNueFile+; +//#pragma link C++ class ublarcvapp::reco3d::ReadJarrettFile+; +//#pragma link C++ class ublarcvapp::reco3d::ReadNueFile+; #pragma link C++ class ublarcvapp::reco3d::Run3DTracker+; #pragma link C++ class ublarcvapp::reco3d::TrackerEventDisplay+; diff --git a/ublarcvapp/Reco3D/Run3DTracker.cxx b/ublarcvapp/Reco3D/Run3DTracker.cxx index 51190fb..3718063 100644 --- a/ublarcvapp/Reco3D/Run3DTracker.cxx +++ b/ublarcvapp/Reco3D/Run3DTracker.cxx @@ -3,18 +3,20 @@ #include "Run3DTracker.h" -#include "DataFormat/storage_manager.h" -#include "DataFormat/track.h" -#include "DataFormat/vertex.h" -#include "DataFormat/hit.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/track.h" +#include "larlite/DataFormat/vertex.h" +#include "larlite/DataFormat/hit.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/wire.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/GeometryHelper.h" +#include "larlite/LArUtil/TimeService.h" + +#include "larcv/core/DataFormat/EventROI.h" #include "larcv/core/DataFormat/Image2D.h" #include "larcv/core/DataFormat/EventImage2D.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/wire.h" -#include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/GeometryHelper.h" -#include "LArUtil/TimeService.h" + #include "TFile.h" #include "TTree.h" #include "TStyle.h" diff --git a/ublarcvapp/Reco3D/Run3DTracker.h b/ublarcvapp/Reco3D/Run3DTracker.h index 6394f13..f112aea 100644 --- a/ublarcvapp/Reco3D/Run3DTracker.h +++ b/ublarcvapp/Reco3D/Run3DTracker.h @@ -19,12 +19,12 @@ #include "TTree.h" #include "TVector3.h" -#include "DataFormat/storage_manager.h" -#include "DataFormat/track.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/track.h" #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "AStarTracker.h" +#include "ublarcvapp/Reco3D/AStarTracker.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/TrackReverser.h b/ublarcvapp/Reco3D/TrackReverser.h index 8a03ba8..325df2f 100644 --- a/ublarcvapp/Reco3D/TrackReverser.h +++ b/ublarcvapp/Reco3D/TrackReverser.h @@ -2,7 +2,7 @@ #define __UBLARCVAPP_RECO3D_TRACK_REVERSER_H__ #include "larcv/core/Base/larcv_base.h" -#include "DataFormat/track.h" +#include "larlite/DataFormat/track.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/Reco3D/TrackerEventDisplay.cxx b/ublarcvapp/Reco3D/TrackerEventDisplay.cxx index ad3088b..e1c133c 100644 --- a/ublarcvapp/Reco3D/TrackerEventDisplay.cxx +++ b/ublarcvapp/Reco3D/TrackerEventDisplay.cxx @@ -3,18 +3,18 @@ #include "TrackerEventDisplay.h" -#include "DataFormat/storage_manager.h" -#include "DataFormat/track.h" -#include "DataFormat/vertex.h" -#include "DataFormat/hit.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/track.h" +#include "larlite/DataFormat/vertex.h" +#include "larlite/DataFormat/hit.h" #include "larcv/core/DataFormat/Image2D.h" #include "larcv/core/DataFormat/EventImage2D.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/wire.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/wire.h" #include "larcv/core/DataFormat/EventROI.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/GeometryHelper.h" -#include "LArUtil/TimeService.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/GeometryHelper.h" +#include "larlite/LArUtil/TimeService.h" #include "TFile.h" #include "TTree.h" #include "TStyle.h" diff --git a/ublarcvapp/Reco3D/TrackerEventDisplay.h b/ublarcvapp/Reco3D/TrackerEventDisplay.h index ce12f21..d722a15 100644 --- a/ublarcvapp/Reco3D/TrackerEventDisplay.h +++ b/ublarcvapp/Reco3D/TrackerEventDisplay.h @@ -19,12 +19,12 @@ #include "TTree.h" #include "TVector3.h" -#include "DataFormat/storage_manager.h" -#include "DataFormat/track.h" +#include "larlite/DataFormat/storage_manager.h" +#include "larlite/DataFormat/track.h" #include "larcv/core/Processor/ProcessBase.h" #include "larcv/core/Processor/ProcessFactory.h" -#include "AStarTracker.h" +#include "ublarcvapp/Reco3D/AStarTracker.h" namespace ublarcvapp { namespace reco3d { diff --git a/ublarcvapp/UBImageMod/CMakeLists.txt b/ublarcvapp/UBImageMod/CMakeLists.txt index b9c9f0d..184da3c 100644 --- a/ublarcvapp/UBImageMod/CMakeLists.txt +++ b/ublarcvapp/UBImageMod/CMakeLists.txt @@ -1,35 +1,65 @@ set(MODULE_NAME UBImageMod) -# Collect the headers -file(GLOB HEADERS "*.h") - -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") - # library name set(LIBNAME LArCVApp_${MODULE_NAME}) -# includes -include_directories(${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) +# Collect Headers +set( HEADERS + EmptyChannelAlgo.h + InfillDataCropper.h + InfillImageStitcher.h + InfillSparsifyImage.h + TrackImageMask.h + UBCropLArFlow.h + UBCropMask.h + UBSplitDetector.h + PointImageProjection.h + ) + +# Define library and add sources +add_library( ${LIBNAME} SHARED + EmptyChannelAlgo.cxx + InfillDataCropper.cxx + InfillImageStitcher.cxx + InfillSparsifyImage.cxx + TrackImageMask.cxx + UBCropLArFlow.cxx + UBCropMask.cxx + UBSplitDetector.cxx + PointImageProjection.cxx + ) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +target_link_libraries( ${LIBNAME} PUBLIC + ${LARLITE_LIBS} + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreProcessor +) + +# includes for ROOT dict generator +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARCV_INCLUDE_DIR} + ${LARLITE_INC_DIRS} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor ${LARLITE_LIBS}) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/UBImageMod/EmptyChannelAlgo.cxx b/ublarcvapp/UBImageMod/EmptyChannelAlgo.cxx index 41f7155..d516353 100644 --- a/ublarcvapp/UBImageMod/EmptyChannelAlgo.cxx +++ b/ublarcvapp/UBImageMod/EmptyChannelAlgo.cxx @@ -52,7 +52,8 @@ namespace ublarcvapp { return empty_v; } - + + /* std::vector EmptyChannelAlgo::makeBadChImage( int minstatus, int nplanes, int start_tick, int nticks, int nchannels, int time_downsample_factor, int wire_downsample_factor, const larlite::event_chstatus& ev_status ) { @@ -89,7 +90,7 @@ namespace ublarcvapp { } return badchs; } - + */ std::vector EmptyChannelAlgo::makeBadChImage( int minstatus, int nplanes, int start_tick, int nticks, int nchannels, int time_downsample_factor, int wire_downsample_factor, @@ -218,15 +219,53 @@ namespace ublarcvapp { empty_ch_max ); for (size_t p=0; p0 ) + badch.paint_col(c,255); } + + // auto& bchv = badch_v[p].as_mod_vector(); + // auto const& gv = gapch_v[p].as_vector(); + // for ( size_t i=0; i EmptyChannelAlgo::makeOverlayedBadChannelImage( const std::vector& img_v, + const larcv::EventChStatus& ev_status, int minstatus, + const float fill_in_value ) + { + + std::vector out_v; + for (auto const& img : img_v ) { + + larcv::Image2D out( img ); + auto const& meta = img.meta(); + int p = meta.plane(); + auto it_chstatus = ev_status.ChStatusMap().find( p ); + if ( it_chstatus!=ev_status.ChStatusMap().end() ) { + + const larcv::ChStatus& chs = ev_status.Status(p); + const std::vector& status_v = chs.as_vector(); + for ( int ch=0; ch<(int)status_v.size(); ch++) { + if ( ch=meta.max_x() ) + continue; + int status = status_v.at(ch); + if ( ch<(int)meta.cols() && status // larlite -#include "larlite/core/Base/DataFormatConstants.h" -#include "larlite/core/DataFormat/chstatus.h" // needed because of duplicate name! +#include "larlite/Base/DataFormatConstants.h" +//#include "larlite/DataFormat/chstatus.h" // needed because of duplicate name! // larcv #include "larcv/core/DataFormat/Image2D.h" @@ -24,9 +24,9 @@ namespace ublarcvapp { std::vector findEmptyChannels( float threshold, const larcv::Image2D& tpcimg, const float max_value=-1.0 ); - std::vector makeBadChImage( int minstatus, int nplanes, int start_tick, int nticks, int nchannels, - int time_downsample_factor, int wire_downsample_factor, - const larlite::event_chstatus& ev_status ); + // std::vector makeBadChImage( int minstatus, int nplanes, int start_tick, int nticks, int nchannels, + // int time_downsample_factor, int wire_downsample_factor, + // const larlite::event_chstatus& ev_status ); std::vector makeBadChImage( int minstatus, int nplanes, int start_tick, int nticks, int nchannels, int time_downsample_factor, int wire_downsample_factor, @@ -43,6 +43,9 @@ namespace ublarcvapp { const int max_empty_gap, const float empty_ch_max ); + std::vector makeOverlayedBadChannelImage( const std::vector& img_v, + const larcv::EventChStatus& ev_status, int minstatus, + const float fill_in_value ); }; diff --git a/ublarcvapp/UBImageMod/InfillDataCropper.cxx b/ublarcvapp/UBImageMod/InfillDataCropper.cxx index aa844fa..e8e9f41 100644 --- a/ublarcvapp/UBImageMod/InfillDataCropper.cxx +++ b/ublarcvapp/UBImageMod/InfillDataCropper.cxx @@ -14,8 +14,8 @@ // #include "../../core/ROOTUtil/ROOTUtils.h" // //larlite -// #include "LArUtil/Geometry.h" -// #include "LArUtil/LArProperties.h" +// #include "larlite/LArUtil/Geometry.h" +// #include "larlite/LArUtil/LArProperties.h" //root #include "TCanvas.h" diff --git a/ublarcvapp/UBImageMod/LinkDef.h b/ublarcvapp/UBImageMod/LinkDef.h index 7f0d7f2..795234b 100644 --- a/ublarcvapp/UBImageMod/LinkDef.h +++ b/ublarcvapp/UBImageMod/LinkDef.h @@ -3,8 +3,7 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ namespace ublarcvapp+; -#pragma link C++ namespace ublarcvapp::ubimagemod+; +#pragma link C++ namespace ublarcvapp::ubimagemod; #pragma link C++ class ublarcvapp::UBSplitDetector+; #pragma link C++ class ublarcvapp::UBCropLArFlow+; #pragma link C++ class ublarcvapp::InfillDataCropper+; @@ -12,6 +11,7 @@ #pragma link C++ class ublarcvapp::InfillImageStitcher+; #pragma link C++ class ublarcvapp::EmptyChannelAlgo+; #pragma link C++ class ublarcvapp::ubimagemod::TrackImageMask+; +#pragma link C++ class ublarcvapp::ubimagemod::PointImageProjection+; #endif diff --git a/ublarcvapp/UBImageMod/PointImageProjection.cxx b/ublarcvapp/UBImageMod/PointImageProjection.cxx new file mode 100644 index 0000000..fe5afff --- /dev/null +++ b/ublarcvapp/UBImageMod/PointImageProjection.cxx @@ -0,0 +1,82 @@ +#include "PointImageProjection.h" + +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/DetectorProperties.h" +#include "larlite/LArUtil/LArProperties.h" + +namespace ublarcvapp { +namespace ubimagemod { + + /** + * @brief project a position inside the tpc into a pixel in the wireplane image and sum pixels around it + * + * + */ + float PointImageProjection::getPixelSumAroundProjPoint( const std::vector& xyz, + const larcv::Image2D& img, + int pixel_kernel_radius, + float pixval_threshold ) const + { + + // get wire and time given xyz + TVector3 worldLoc( xyz[0], xyz[1], xyz[2] ); + + //LARCV_NORMAL() << img.meta().dump() << std::endl; + UInt_t wireid = 0; + try { + wireid = larutil::Geometry::GetME()->NearestWire( worldLoc, img.meta().plane() ); + } + catch ( std::exception& e ) { + LARCV_WARNING() << e.what() << " - return 0.0" << std::endl; + return 0.0; + } + + if ( wireid < img.meta().min_x() ) { + LARCV_NORMAL() << "Wire " << wireid << " below " << img.meta().min_x() << std::endl; + return 0.0; + } + if ( wireid >= img.meta().max_x() ) { + LARCV_NORMAL() << "Wire " << wireid << " above max=" << img.meta().max_x() << std::endl; + return 0.0; + } + + //double tick = larutil::DetectorProperties::GetME()->ConvertXToTicks( xyz[0], img.meta().plane() ); + float cm_per_tick = larutil::LArProperties::GetME()->DriftVelocity()*0.5; + double tick = xyz[0]/cm_per_tick + 3200.0; + if ( tick < img.meta().min_y() ) { + LARCV_NORMAL() << "tick " << tick << " below min=" << img.meta().min_y() << std::endl; + return 0.0; + } + + if ( tick >= img.meta().max_y() ) { + LARCV_NORMAL() << "tick " << tick << " greater than max=" << img.meta().max_y() << std::endl; + return 0.0; + } + + int col = (int)img.meta().col( (float)wireid ); + int row = (int)img.meta().row( (float)tick ); + + int dkr = abs(pixel_kernel_radius); + int dkc = abs(pixel_kernel_radius); + + int startcol = ((col-dkc)<0) ? 0 : col-dkc; + int endcol = ((col+dkc)>=(int)img.meta().cols()) ? (int)img.meta().cols()-1 : col+dkc; + int startrow = ((row-dkr)<0) ? 0 : row-dkr; + int endrow = ((row+dkr)>=(int)img.meta().rows()) ? (int)img.meta().rows()-1 : row+dkr; + + float pixsum = 0.; + for (int pixrow=startrow; pixrow<=endrow; pixrow++) { + for (int pixcol=startcol; pixcol<=endcol; pixcol++) { + float pixval = img.pixel( pixrow, pixcol ); + // only add to sum if pixel value above threshold + if ( pixval > pixval_threshold ) + pixsum += pixval; + }//end of dc loop + }//end of dr loop + + return pixsum; + + } + +} +} diff --git a/ublarcvapp/UBImageMod/PointImageProjection.h b/ublarcvapp/UBImageMod/PointImageProjection.h new file mode 100644 index 0000000..78235a6 --- /dev/null +++ b/ublarcvapp/UBImageMod/PointImageProjection.h @@ -0,0 +1,44 @@ +#ifndef __UBLARCAPP_UBIMAGEMOD_POINT_IMAGE_PROJECTION_H__ +#define __UBLARCAPP_UBIMAGEMOD_POINT_IMAGE_PROJECTION_H__ + +/** + * + * @brief Class with functions for projecting a point into a larcv image + * + * Image represents the output of a lartpc wireplane. + * We want to be able to take a 3D point inside the TPC and find the pixel that location + * projects to. Then we can ask about the content of the pixels in the neighborhood of that pixel. + * + * + */ + +#include + +#include "larcv/core/Base/larcv_base.h" +#include "larcv/core/DataFormat/Image2D.h" + + +namespace ublarcvapp { + namespace ubimagemod { + + class PointImageProjection : public larcv::larcv_base + { + + public: + + PointImageProjection() + : larcv::larcv_base("PointImageProjection") + {}; + + ~PointImageProjection() {}; + + + float getPixelSumAroundProjPoint( const std::vector& xyz, const larcv::Image2D& img, int pixel_kernel_radius, float pix_threshold ) const; + + + }; + + } +} + +#endif diff --git a/ublarcvapp/UBImageMod/TrackImageMask.cxx b/ublarcvapp/UBImageMod/TrackImageMask.cxx index 840425a..3b55f89 100644 --- a/ublarcvapp/UBImageMod/TrackImageMask.cxx +++ b/ublarcvapp/UBImageMod/TrackImageMask.cxx @@ -1,7 +1,7 @@ #include "TrackImageMask.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "larcv/core/DataFormat/ImageMeta.h" #include @@ -13,8 +13,22 @@ namespace ubimagemod { { pixel_v.clear(); pixel_map.clear(); + pixel_kernel_pixval_v.clear(); + nmaskedpixels = 0; + npathpixels = 0; } - + + /** + * @brief Mask pixels in an image along the path of a 3D trajectory + * + * @param[in] track Path represented as sequence of line segments + * @param[in] img Original image, containing wire signal + * @param[out] mask Image whose pixels we mask + * @param[in] threshold + * @param[in] dcol Width of rectangular masking kernel + * @param[in] drow Height of rectangular masking kernel + * @param[in] maxstepsize Maximum step we take along the line segment path (in cm) + */ int TrackImageMask::maskTrack( const larlite::track& track, const larcv::Image2D& img, larcv::Image2D& mask, @@ -23,9 +37,14 @@ namespace ubimagemod { const int drow, const float maxstepsize ) { - + // clear clear(); + + int N = (2*dcol+1)*(2*drow+1); // number of elements in kernel + kernel_dcol = dcol; + kernel_drow = drow; + kernel_N = N; int npts = track.NumberTrajectoryPoints(); LARCV_DEBUG() << "start: track length=" << npts << std::endl; @@ -38,7 +57,7 @@ namespace ubimagemod { auto const& meta = img.meta(); - int npixels = makePixelList( track, img, threshold, maxstepsize, false ); + int npixels = makePixelList( track, img, 0.0, maxstepsize, true ); LARCV_DEBUG() << "Number of pixels: " << pixel_v.size() << std::endl; if ( npixels==0 ) return 0; @@ -65,6 +84,7 @@ namespace ubimagemod { // make data array std::vector crop(colwidth*rowwidth,0); std::vector cropmask(colwidth*rowwidth,0); + std::vector cropmask_threshold(colwidth*rowwidth,0); // copy the data into the cropped matrix // preserve the row-ordered data @@ -78,11 +98,12 @@ namespace ubimagemod { // kernal loop, making mask // trying to write in a way that is easy to accelerate - int N = (2*dcol+1)*(2*drow+1); // number of elements in kernel // parallelize masking with block kernel - int nmasked = 0; - std::chrono::steady_clock::time_point start_mask = std::chrono::steady_clock::now(); + nmaskedpixels = 0; + std::chrono::steady_clock::time_point start_mask = std::chrono::steady_clock::now(); + + pixel_kernel_pixval_v.resize( pixel_v.size()*N, 0.0 ); #pragma omp parallel for for (int ikernel=0; ikernelthreshold ) { #pragma omp atomic - cropmask[ cropindex ] += 1.0; - } + cropmask_threshold[ cropindex ] += 1.0; + } + #pragma omp atomic + cropmask[ cropindex ] += 1.0; + + pixel_kernel_pixval_v[ N*itp + ikernel ] = pixval; } } #pragma omp barrier for (size_t ii=0; ii0 ) { - nmasked++; - cropmask[ii] = 1.0; + if ( cropmask_threshold[ii]>0 ) { + nmaskedpixels++; + cropmask_threshold[ii] = 1.0; int orig_row = (ii%rowwidth)-drow + row_origin; int orig_col = (ii/rowwidth)-dcol + col_origin; mask.set_pixel( orig_row, orig_col, 1.0 ); } + if ( cropmask[ii]>0 ) { + npathpixels++; + cropmask[ii] = 1.0; + } } - LARCV_INFO() << "num pixels masked: " << nmasked << std::endl; + LARCV_INFO() << "num pixels masked: " << nmaskedpixels << " out of num pixels on path (with kernel): " << npathpixels << std::endl; std::chrono::steady_clock::time_point end_mask = std::chrono::steady_clock::now(); if ( show_timing ) LARCV_INFO() << "mask: " << std::chrono::duration_cast(end_mask - start_mask).count() << std::endl; - return nmasked; + return nmaskedpixels; } /** @@ -168,6 +198,17 @@ namespace ubimagemod { return nlabeled; } + /** + * @brief Provide a list of pixels we travel through while stepping along a track + * + * The list of pixels are stored in the internal data member, `pixel_v`. + * + * @param[in] track Path we step along represented as sequence of line segments + * @param[in] img 2D image containing wire signals + * @param[in] threshold + * @param[in] maxstepsize Maximum step we take along the line segment path + * @param[in] fill_map If true, we fill the pixel_map data member + */ int TrackImageMask::makePixelList( const larlite::track& track, const larcv::Image2D& img, const float threshold, @@ -177,6 +218,11 @@ namespace ubimagemod { pixel_v.clear(); pixel_map.clear(); + auto const& meta = img.meta(); + min_col = (int)meta.cols()-1; + min_row = (int)meta.rows()-1; + max_col = 0; + max_row = 0; if ( maxstepsize<0 || std::isnan(maxstepsize) || std::isinf(maxstepsize) ) { LARCV_CRITICAL() << "Bad maxstepsize value: " << maxstepsize << std::endl; @@ -194,7 +240,6 @@ namespace ubimagemod { const float driftv = larutil::LArProperties::GetME()->DriftVelocity(); const float usec_per_tick = 0.5; - auto const& meta = img.meta(); int plane = meta.plane(); if ( plane<0 || plane>=(int)larutil::Geometry::GetME()->Nplanes() ) { LARCV_WARNING() << "Invalid plane: " << plane << std::endl; @@ -210,14 +255,13 @@ namespace ubimagemod { int nrows = meta.rows(); int ncols = meta.cols(); - min_col = (int)meta.cols()-1; - min_row = (int)meta.rows()-1; - max_col = 0; - max_row = 0; - std::set< std::pair > pixel_set; pixel_v.reserve( 2*npts ); + std::pair last_coord; + last_coord.first = 0; + last_coord.second = 0; + float len_traveled = 0.; for (int ipt=0; ipt pixcoord(col,row); + bool newpix = (pixcoord!=last_coord); if ( fill_map ) { auto it = pixel_map.find( pixcoord ); if ( it==pixel_map.end() ) { // new entry - pixel_map[pixcoord] = Pix_t( col, row, s, s ); + pixel_map[pixcoord] = Pix_t( col, row, s, s, pixval ); pixel_v.push_back( std::vector{col,row} ); it = pixel_map.find( pixcoord ); } it->second.smax = s; + + if (newpix) { + auto it_old = pixel_map.find(last_coord); + if ( it_old!=pixel_map.end() ) { + it_old->second.smax = s; + } + last_coord = pixcoord; + } } else { auto it = pixel_set.find( pixcoord ); @@ -540,6 +593,75 @@ namespace ubimagemod { return true; } + + /** + * @brief Find the largest continous distance along path without charge + * + * Uses the list of pixels are stored in the internal data member, `pixel_v`. + * Also uses the charge seen at each spot of the kernel for each pixel in pixel_v. + * This info is stored in `pixel_kernel_pixval_v`. + * Both are created in `maskTrack`. If this was not run, should return 0. + */ + float TrackImageMask::getMaximumChargeGap( std::vector< std::vector >& gap_points ) const + { + float current_gapsize = 0.; + float max_gapsize = 0.; + + gap_points.resize(2); + for (int i=0; i<2; i++) { + gap_points[i].resize(2,0); + } + + for (size_t ipix=0; ipix( pixcoord[0], pixcoord[1] ) ); + auto const& pix = it->second; + + if ( totq>0.0 ) { + // not a gap, reset the gap + if ( current_gapsize>max_gapsize ) { + max_gapsize = current_gapsize; + // set the last gap point + std::vector gap_coord = { (float)pix.col, (float)pix.row }; + gap_points[1] = gap_coord; + } + + current_gapsize = 0; + + } + else { + + if ( current_gapsize==0.0 ) { + // set first gap points + std::vector gap_coord = { (float)pix.col, (float)pix.row }; + gap_points[0] = gap_coord; + } + + current_gapsize += pix.smax-pix.smin; + if ( ipix+1==pixel_v.size() ) { + // the last pixel + if ( current_gapsize>max_gapsize ) { + max_gapsize = current_gapsize; + // set the last gap point + std::vector gap_coord = { (float)pix.col, (float)pix.row }; + gap_points[1] = gap_coord; + } + } + } + // std::cout << "ipix[" << ipix << "]: (c,r)=(" << pix.col << "," << pix.row << ")" + // << " pixval=" << pix.pixval + // << " smin=" << pix.smin << " smax=" << pix.smax + // << " totq=" << totq << " current_gap=" << current_gapsize + // << std::endl; + } + + return max_gapsize; + } } } diff --git a/ublarcvapp/UBImageMod/TrackImageMask.h b/ublarcvapp/UBImageMod/TrackImageMask.h index 65e57d4..d9e5a98 100644 --- a/ublarcvapp/UBImageMod/TrackImageMask.h +++ b/ublarcvapp/UBImageMod/TrackImageMask.h @@ -3,7 +3,7 @@ #include "larcv/core/Base/larcv_base.h" #include "larcv/core/DataFormat/Image2D.h" -#include "DataFormat/track.h" +#include "larlite/DataFormat/track.h" namespace ublarcvapp { namespace ubimagemod { @@ -20,6 +20,10 @@ namespace ubimagemod { max_row(0), show_timing(false) {}; + ~TrackImageMask() + { + clear(); + } void clear(); @@ -58,6 +62,10 @@ namespace ubimagemod { const float thresh, float& pixval_min, float& pixval_max ) const; + + float getMaximumChargeGap( std::vector< std::vector >& gap_points ) const; + int getMaskedPixels() const { return nmaskedpixels; }; + int getPathPixels() const { return npathpixels; }; // first we make a list of pixels covered by the track @@ -67,21 +75,28 @@ namespace ubimagemod { int row; float smin; float smax; + float pixval; Pix_t() - : col(0),row(0),smin(0),smax(0) + : col(0),row(0),smin(0),smax(0),pixval(0) {}; - Pix_t( int c, int r, float ssmin, float ssmax ) - : col(c), row(r), smin(ssmin), smax(ssmax) + Pix_t( int c, int r, float ssmin, float ssmax,float pv ) + : col(c), row(r), smin(ssmin), smax(ssmax),pixval(pv) {}; }; // output data members for use std::map< std::pair, Pix_t > pixel_map; ///< map of pixel (col,row) to Pix_t struct - std::vector< std::vector > pixel_v; ///< list of (col,row) pixels that follows path of tracko + std::vector< std::vector > pixel_v; ///< list of (col,row) pixels that follows path of track + std::vector< float > pixel_kernel_pixval_v; ///< list of charge values within the kernel for each pixel int min_col; int min_row; int max_col; - int max_row; + int max_row; + int kernel_dcol; + int kernel_drow; + int kernel_N; + int nmaskedpixels; + int npathpixels; bool show_timing; ///< set to true to print output }; diff --git a/ublarcvapp/UBImageMod/UBCropLArFlow.cxx b/ublarcvapp/UBImageMod/UBCropLArFlow.cxx index c28b328..b120276 100644 --- a/ublarcvapp/UBImageMod/UBCropLArFlow.cxx +++ b/ublarcvapp/UBImageMod/UBCropLArFlow.cxx @@ -11,8 +11,8 @@ #include "larcv/core/ROOTUtil/ROOTUtils.h" //larlite -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "TCanvas.h" #include "TH2D.h" diff --git a/ublarcvapp/UBImageMod/UBCropMask.cxx b/ublarcvapp/UBImageMod/UBCropMask.cxx index 073edcd..cbb0c2d 100644 --- a/ublarcvapp/UBImageMod/UBCropMask.cxx +++ b/ublarcvapp/UBImageMod/UBCropMask.cxx @@ -13,8 +13,8 @@ #include "larcv/core/ROOTUtil/ROOTUtils.h" //larlite -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" #include "TCanvas.h" #include "TH2D.h" diff --git a/ublarcvapp/UBImageMod/UBSplitDetector.cxx b/ublarcvapp/UBImageMod/UBSplitDetector.cxx index dd46796..8aac6a5 100644 --- a/ublarcvapp/UBImageMod/UBSplitDetector.cxx +++ b/ublarcvapp/UBImageMod/UBSplitDetector.cxx @@ -9,7 +9,7 @@ #ifdef HAS_LARLITE //larlite -#include "LArUtil/Geometry.h" +#include "larlite/LArUtil/Geometry.h" #endif // ROOT TRandom3 @@ -573,6 +573,8 @@ namespace ublarcvapp { // give pack the image vector output_imgs.Emplace( std::move(outimg_v) ); + + return true; } bool UBSplitDetector::cropUsingBBox2D( const larcv::ROI& bbox_vec, diff --git a/ublarcvapp/UBPhotonLib/CMakeLists.txt b/ublarcvapp/UBPhotonLib/CMakeLists.txt new file mode 100644 index 0000000..817839c --- /dev/null +++ b/ublarcvapp/UBPhotonLib/CMakeLists.txt @@ -0,0 +1,62 @@ +set(MODULE_NAME UBPhotonLib) + +# library name +set(LIBNAME LArCVApp_${MODULE_NAME}) + +# Collect the headers +set( HEADERS + UBPhotonLib.h + PhotonVisibilityEstimator.h +) + +# Add sources +add_library( ${LIBNAME} SHARED + UBPhotonLib.cxx + PhotonVisibilityEstimator.cxx +) + +# larlite libraries +set(LARLITE_LIBS_USED ${LARLITE_LIBS} ) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} +) + +target_link_libraries( ${LIBNAME} PUBLIC + ${LARLITE_LIBS_USED} + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreProcessor +) + +# includes for ROOT dictionary gen +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} +) + +# Generate the dictionary +ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) + +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) + +# install libraries and headers +install(TARGETS ${LIBNAME} + EXPORT ublarcvapp + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include/ublarcvapp/${MODULE_NAME}) + +# install files needed for dictionary use in CINT/CLING/PYTHON +install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/lib${LIBNAME}_rdict.pcm + ${CMAKE_CURRENT_BINARY_DIR}/lib${LIBNAME}.rootmap + DESTINATION lib ) diff --git a/ublarcvapp/UBPhotonLib/LinkDef.h b/ublarcvapp/UBPhotonLib/LinkDef.h new file mode 100644 index 0000000..21fbbae --- /dev/null +++ b/ublarcvapp/UBPhotonLib/LinkDef.h @@ -0,0 +1,28 @@ +/** \defgroup UBPhotonLib + * + * \brief Interface and utilities for utilizing the UBPhotonLibrary + * + * + * cint script to generate libraries and python bindings. + * Declare namespace & classes you defined + * pragma statement: order matters! Google it ;) + * + */ +#ifdef __CINT__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ namespace ublarcvapp; +#pragma link C++ namespace ublarcvapp::ubphotonlib; + +#pragma link C++ class ublarcvapp::ubphotonlib::UBPhotonLib+; +#pragma link C++ class ublarcvapp::ubphotonlib::PhotonVisibilityEstimator+; +#pragma link C++ class ublarcvapp::ubphotonlib::PhotonVisibilityEstimator::PhotonSource+; + +#endif + + + + diff --git a/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.cxx b/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.cxx new file mode 100644 index 0000000..db487f1 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.cxx @@ -0,0 +1,111 @@ +#include "PhotonVisibilityEstimator.h" +#include "UBPhotonLib.h" +#include + +namespace ublarcvapp { +namespace ubphotonlib { + + PhotonVisibilityEstimator::PhotonVisibilityEstimator() + : _num_optical_channels(32) // MicroBooNE has 32 PMTs + { + // Ensure photon library is loaded + UBPhotonLib::getPhotonLib(); + } + + PhotonVisibilityEstimator::~PhotonVisibilityEstimator() + { + // Nothing to clean up + } + + void PhotonVisibilityEstimator::clear() + { + _sources.clear(); + } + + void PhotonVisibilityEstimator::addPhotonSource(float x, float y, float z, float num_photons) + { + _sources.emplace_back(x, y, z, num_photons); + } + + void PhotonVisibilityEstimator::addPhotonSource(const PhotonSource& source) + { + _sources.push_back(source); + } + + void PhotonVisibilityEstimator::addPhotonSources(const std::vector& sources) + { + _sources.insert(_sources.end(), sources.begin(), sources.end()); + } + + std::map PhotonVisibilityEstimator::calculateDetectedPhotons(bool use_trilinear) + { + std::map photons_per_opdet; + + // Initialize map with zeros for all optical channels + for (int opch = 0; opch < _num_optical_channels; ++opch) { + photons_per_opdet[opch] = 0.0; + } + + // Get photon library instance + UBPhotonLib* photon_lib = UBPhotonLib::getPhotonLib(); + + // For each point source + for (const auto& source : _sources) { + // Skip if no photons + if (source.num_photons <= 0) continue; + + // For each optical channel + for (int opch = 0; opch < _num_optical_channels; ++opch) { + float visibility = 0.0; + + // Get visibility from photon library + if (use_trilinear) { + visibility = photon_lib->getVisibilityTrilinear(source.position, opch); + } else { + visibility = photon_lib->getVisibility(source.position, opch); + } + + // Calculate number of photons detected by this channel from this source + // Visibility is the probability that a photon from this voxel reaches this opdet + float detected_photons = source.num_photons * visibility; + + // Add to total for this optical channel + photons_per_opdet[opch] += detected_photons; + } + } + + return photons_per_opdet; + } + + float PhotonVisibilityEstimator::getTotalDetectedPhotons(bool use_trilinear) + { + auto photons_per_opdet = calculateDetectedPhotons(use_trilinear); + + float total = 0.0; + for (const auto& entry : photons_per_opdet) { + total += entry.second; + } + + return total; + } + + float PhotonVisibilityEstimator::getTotalEmittedPhotons() const + { + float total = 0.0; + for (const auto& source : _sources) { + total += source.num_photons; + } + return total; + } + + float PhotonVisibilityEstimator::getCollectionEfficiency(bool use_trilinear) + { + float emitted = getTotalEmittedPhotons(); + if (emitted <= 0) return 0.0; + + float detected = getTotalDetectedPhotons(use_trilinear); + return detected / emitted; + } + +} +} \ No newline at end of file diff --git a/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.h b/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.h new file mode 100644 index 0000000..394ce7d --- /dev/null +++ b/ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.h @@ -0,0 +1,115 @@ +#ifndef __UBLARCVAPP_UBPHOTONLIB_PHOTONVISIBILITYESTIMATOR_H__ +#define __UBLARCVAPP_UBPHOTONLIB_PHOTONVISIBILITYESTIMATOR_H__ + +#include +#include +#include + +namespace ublarcvapp { +namespace ubphotonlib { + + /** + * @brief Class to estimate photon detection from multiple 3D point sources + * + * This class uses the UBPhotonLib to calculate how many photons from + * multiple 3D point sources will be detected by each optical detector. + */ + class PhotonVisibilityEstimator { + public: + + /** + * @brief Structure to hold a 3D point source with photon count + */ + struct PhotonSource { + std::vector position; ///< 3D position in detector coordinates (x,y,z) in cm + float num_photons; ///< Number of photons emitted at this location + + PhotonSource() : position(3,0), num_photons(0) {} + PhotonSource(float x, float y, float z, float nphotons) + : position{x,y,z}, num_photons(nphotons) {} + }; + + /** + * @brief Constructor + */ + PhotonVisibilityEstimator(); + + /** + * @brief Destructor + */ + ~PhotonVisibilityEstimator(); + + /** + * @brief Clear all point sources + */ + void clear(); + + /** + * @brief Add a single point source + * @param x X position in cm (detector coordinates) + * @param y Y position in cm (detector coordinates) + * @param z Z position in cm (detector coordinates) + * @param num_photons Number of photons emitted at this location + */ + void addPhotonSource(float x, float y, float z, float num_photons); + + /** + * @brief Add a single point source + * @param source PhotonSource struct containing position and photon count + */ + void addPhotonSource(const PhotonSource& source); + + /** + * @brief Add multiple point sources + * @param sources Vector of PhotonSource structs + */ + void addPhotonSources(const std::vector& sources); + + /** + * @brief Calculate photons detected by each optical detector + * @param use_trilinear If true, use trilinear interpolation for visibility (default: true) + * @return Map from optical channel ID to number of detected photons + */ + std::map calculateDetectedPhotons(bool use_trilinear = true); + + /** + * @brief Get total number of photons detected across all optical detectors + * @param use_trilinear If true, use trilinear interpolation for visibility (default: true) + * @return Total number of detected photons + */ + float getTotalDetectedPhotons(bool use_trilinear = true); + + /** + * @brief Get number of point sources + * @return Number of point sources currently stored + */ + size_t getNumSources() const { return _sources.size(); } + + /** + * @brief Get total number of emitted photons from all sources + * @return Total number of emitted photons + */ + float getTotalEmittedPhotons() const; + + /** + * @brief Get the collection efficiency (detected/emitted) + * @param use_trilinear If true, use trilinear interpolation for visibility (default: true) + * @return Collection efficiency as a fraction + */ + float getCollectionEfficiency(bool use_trilinear = true); + + /** + * @brief Get number of optical channels + * @return Number of optical channels in the detector + */ + int getNumOpticalChannels() const { return _num_optical_channels; } + + private: + std::vector _sources; ///< Vector of photon point sources + int _num_optical_channels; ///< Number of optical detectors + }; + +} +} + +#endif diff --git a/ublarcvapp/UBPhotonLib/README.md b/ublarcvapp/UBPhotonLib/README.md new file mode 100644 index 0000000..a25f582 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/README.md @@ -0,0 +1,254 @@ +# UBPhotonLib + +Interface to visibility library for MicroBooNE. + +## Classes + +### UBPhotonLib +Singleton class providing access to the photon visibility library. This class loads the photon library data and provides methods to query the visibility (probability of detection) for photons emitted from any voxel in the detector to any optical detector. + +### PhotonVisibilityEstimator +A utility class that uses UBPhotonLib to estimate photon detection from multiple 3D point sources. This is useful for: +- Estimating scintillation light from particle tracks +- Calculating total light yield from complex geometries +- Comparing detection patterns across different optical detectors + +## Usage Examples + +### Python Example +```python +from ublarcvapp import ublarcvapp + +# Create estimator +estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + +# Add point sources (x, y, z in cm, number of photons) +estimator.addPhotonSource(100.0, 0.0, 400.0, 1000.0) +estimator.addPhotonSource(110.0, 10.0, 420.0, 1500.0) + +# Calculate detected photons per optical detector +photons_per_opdet = estimator.calculateDetectedPhotons(use_trilinear=True) + +# Get total collection efficiency +efficiency = estimator.getCollectionEfficiency(True) +print(f"Collection efficiency: {efficiency:.4f}") +``` + +### C++ Example +```cpp +#include "ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.h" + +using namespace ublarcvapp::ubphotonlib; + +PhotonVisibilityEstimator estimator; + +// Add point sources +estimator.addPhotonSource(100.0, 0.0, 400.0, 1000.0); + +// Calculate results +auto photons_map = estimator.calculateDetectedPhotons(true); +float total = estimator.getTotalDetectedPhotons(true); +``` + +## Test Scripts +- `test/test_photon_estimator.py` - Python example with visualization +- `test/test_photon_estimator.cxx` - C++ standalone example + +## Technical Details + +### Photon Library Files + + +Where can I get the photon library file? + +On the uboone CVMFS: + +``` +/cvmfs/uboone.opensciencegrid.org/products/uboone_photon_propagation/v01_01_00/PhotonPropagation/LibraryData/ +``` + +This folder contains: +``` +README uboone_photon_library_v6_70kV_EnhancedExtraTPCVis.root uboone_photon_library_variation_Rayleigh.root +uboone_photon_library_v5_0kV.root uboone_photon_library_v6_70kV.root uboone_photon_library_variation_Suppression75percent.root +uboone_photon_library_v5.root uboone_photon_library_variation_Attenuation8m.root +uboone_photon_library_v6_0kV.root uboone_photon_library_variation_Attenuation8m_Suppression75percent.root +``` + +The README in the folder contains the following info: +``` +Available photon libraries +-------------------------- + +uboone_photon_library_v5_0kV.root - MCC7 no E-field +uboone_photon_library_v5.root - MCC7 70kV (this file used for mcc7 g4) +uboone_photon_library_v6_0kV.root - MCC8 no E-field +uboone_photon_library_v6_70kV.root - MCC8 70kV (standard hv) +uboone_photon_library_v6_70kV_EnhancedExtraTPCVis.root + - Increased visibility outside TPC by 50%. + +The zero E-field photon libraries contain the probability that a +photon generated in a particular voxel will be detected by any pmt. + +The nonzero E-field are modified from the zero E-field photon libraries +by including the relative effect of the E-field on photon yield due +to recombination. +``` + + +Of the options, we use for now: `uboone_photon_library_v6_70kV_EnhancedExtraTPCVis.root` + +This file is 101 MB. + + +Inside the file is the following ROOT tree `PhotonLibraryData` located in the TDirectory, `pmtresponse`. +``` +****************************************************************************** +*Tree :PhotonLibraryData: * +*Entries : 33615139 : Total = 403773735 bytes File Size = 105069173 * +* : : Tree compression factor = 3.84 * +****************************************************************************** +*Br 0 :Voxel : Voxel/I * +*Entries : 33615139 : Total Size= 134587491 bytes File Size = 5521593 * +*Baskets : 1206 : Basket Size= 3264000 bytes Compression= 24.37 * +*............................................................................* +*Br 1 :OpChannel : OpChannel/I * +*Entries : 33615139 : Total Size= 134592331 bytes File Size = 7064466 * +*Baskets : 1206 : Basket Size= 3264000 bytes Compression= 19.05 * +*............................................................................* +*Br 2 :Visibility : Visibility/F * +*Entries : 33615139 : Total Size= 134593541 bytes File Size = 92451277 * +*Baskets : 1206 : Basket Size= 3264512 bytes Compression= 1.46 * +*............................................................................* +``` + +Voxels are labeled by a simple integer index. +This means we need to know the voxelization defintion and where it comes from. +We also need to know the OpChannel labeling scheme. + + +Parseing the Table + +Number of entries in the tree is: 33615139. + +Found the following config in the `standard_g4_uboone.fcl`: + +``` + PhotonVisibilityService: { + DoNotLoadLibrary: false + Interpolate: false + LibraryBuildJob: false + LibraryFile: "PhotonPropagation/LibraryData/uboone_photon_library_v6_70kV.root" + NX: 75 + NY: 75 + NZ: 400 + UseCryoBoundary: true + XMax: 120 + XMin: -120 + YMax: 120 + YMin: -120 + ZMax: 1200 + ZMin: 0 + service_type: "PhotonVisibilityService" + } +``` +That implies a total of 75*75*400*32 = 72,000,000 possible entries. +So less than half is filled. I guess that makes sense. + +The bounds don't make total sense. +The X width is only 240 cm. The TPC drift length is 256 cm! +The Y width is 240 cm, which is fine. (TPC height is 2*117 cm) +The Z width is 1200 cm, which is also fine. (TPC is 1036 cm) +OK, I see in `photpropservices.fcl` that these bounds are not used when "UseCryoBoundary: true". + +So then what are the cryo boundaries? +The photonvisibility service gets it from the geometry surface, by calling GetCryostat. +Great, so how does it do this? + +Used dump_geometry to extract cryostat and TPC bounds. + +The Key info (dump with all relevant det geom below). + + +``` +Cryostat C:0 (383.22 x 383.22 x 1221.75) cm^3 at (128.175,0,518.5) +TPC C:0 T:0 (260 x 256 x 1045) cm^3 at (128.175,0.97,518.5) +``` + +We work in the TPC coordinates always. Looks like the center is mostly the same. +There is a y=-0.97 offset (cryostat center is lower in global coordinates). +Dimensions x: 383.22 cm/75 = 5.1096 cm voxels +Origin of the C in global is: (-63.435,-191.61,-92.375) +Origin of TPC in the global is: (-1.825,-127.03,-4) +We use origin_cryo-origin_tpc to get cryo origin in TPC coordinate system. + + + +``` +Detector description: '/cvmfs/uboone.opensciencegrid.org/products/ubcore/v08_00_00_40/gdml/microboonev12.gdml' +Detector microboonev12 has 1 cryostats and 1168 auxiliary detectors: + Detector enclosure: (-613.455,-895.31,-223.13) -- (869.805,895.31,1260.13) cm => ( 1483.26 x 1790.62 x 1483.26 ) cm^3 + Cryostat C:0 (383.22 x 383.22 x 1221.75) cm^3 at (128.175,0,518.5) + hosts 1 TPCs (largest number of planes: 3, of wires: 3456) and 32 optical detectors + bounding box: (-63.435,-191.61,-92.375) -- (319.785,191.61,1129.38) + TPC C:0 T:0 (260 x 256 x 1045) cm^3 at (128.175,0.97,518.5) + drift direction (-1,0,0) from cathode around (254.8,0.97,518.5) through 255.4 cm toward 3 wire planes + maximum wires on any plane: 3456 + active volume (256.35 x 233 x 1036.8) cm^3, front face at (126.625,0.97,0.1) cm; + main directions: width (1,0,0) height (0,1,0) length (0,0,1) + bounding box: (-1.825,-127.03,-4) -- (258.175,128.97,1041) + active volume box: (-1.55,-115.53,0.1) -- (254.8,117.47,1036.9) + plane C:0 T:0 P:0 at (-6.34622e-14,0.97,518.5) cm, theta: 0.523599 rad + normal to wire: -1.0472 rad, with orientation vertical, has 2400 wires measuring U with a wire pitch of 0.3 cm + normal to plane: (1,0,0), direction of increasing wire number: (0,-0.866025,0.5) [wire frame normal: (1,0,0)] (increases with z) + wire direction: (0,0.5,0.866025); width 1037.35 cm in direction: (0,0,-1), depth 233 cm in direction: (0,1,0) [normal: (1,0,0)] + wires cover width -518.411 to 518.411, depth -116.387 to 116.387 cm + bounding box: (-0.075,-115.53,-0.175) -- (0.075,117.47,1037.17) + ... wire stuff ... + plane C:0 T:0 P:1 at (-0.3,0.97,518.5) cm, theta: 2.61799 rad + normal to wire: 1.0472 rad, with orientation vertical, has 2400 wires measuring V with a wire pitch of 0.3 cm + normal to plane: (1,0,0), direction of increasing wire number: (0,0.866025,0.5) [wire frame normal: (1,0,0)] (increases with z) + wire direction: (0,0.5,-0.866025); width 1037.35 cm in direction: (0,0,1), depth 233 cm in direction: (0,-1,0) [normal: (1,0,0)] + wires cover width -518.411 to 518.411, depth -116.387 to 116.387 cm + bounding box: (-0.375,-115.53,-0.175) -- (-0.225,117.47,1037.17) + plane C:0 T:0 P:2 at (-0.6,0.97,518.5) cm, theta: 1.5708 rad + normal to wire: 0 rad, with orientation vertical, has 3456 wires measuring Z with a wire pitch of 0.3 cm + normal to plane: (1,0,0), direction of increasing wire number: (0,0,1) [wire frame normal: (1,0,0)] (increases with z) + wire direction: (0,1,0); width 1037 cm in direction: (0,0,1), depth 233 cm in direction: (0,-1,0) [normal: (1,0,0)] + wires cover width -518.4 to 518.4, depth -116.5 to 116.5 cm + bounding box: (-0.675,-115.53,0) -- (-0.525,117.47,1037) + ... wire stuff ... + + [OpDet #0] centered at (-11.4545,-28.625,990.356) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #1] centered at (-11.4175,27.607,989.712) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #2] centered at (-11.7755,-56.514,951.865) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #3] centered at (-11.6415,55.313,951.861) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #4] centered at (-12.0585,-56.309,911.939) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #5] centered at (-11.8345,55.822,911.065) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #6] centered at (-12.1765,-0.722,865.599) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #7] centered at (-12.3045,-0.502,796.208) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #8] centered at (-12.6045,-56.284,751.905) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #9] centered at (-12.5405,55.625,751.884) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #10] centered at (-12.6125,-56.408,711.274) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #11] centered at (-12.6615,55.8,711.073) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #12] centered at (-12.6245,-0.051,664.203) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #13] centered at (-12.6515,-0.549,585.284) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #14] centered at (-12.8735,55.822,540.929) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #15] centered at (-12.6205,-56.205,540.616) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #16] centered at (-12.5945,-56.323,500.221) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #17] centered at (-12.9835,55.771,500.134) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #18] centered at (-12.6185,-0.875,453.096) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #19] centered at (-13.0855,-0.706,373.839) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #20] centered at (-12.6485,-57.022,328.341) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #21] centered at (-13.1865,54.693,328.212) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #22] centered at (-13.4175,54.646,287.976) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #23] centered at (-13.0075,-56.261,287.639) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #24] centered at (-13.1505,-0.829,242.014) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #25] centered at (-13.4415,-0.303,173.743) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #26] centered at (-13.3965,55.249,128.354) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #27] centered at (-13.2784,-56.203,128.18) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #28] centered at (-13.2375,-56.615,87.8695) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #29] centered at (-13.5415,55.249,87.7605) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #30] centered at (-13.4345,27.431,51.1015) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad + [OpDet #31] centered at (-13.1525,-28.576,50.4745) cm, radius: 15.24 cm, length: 0.2 cm, theta(z): 1.5708 rad +``` diff --git a/ublarcvapp/UBPhotonLib/UBPhotonLib.cxx b/ublarcvapp/UBPhotonLib/UBPhotonLib.cxx new file mode 100644 index 0000000..86553c9 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/UBPhotonLib.cxx @@ -0,0 +1,284 @@ +#include "UBPhotonLib.h" + +#include +#include +#include + +#include "TFile.h" +#include "TTree.h" + +namespace ublarcvapp { +namespace ubphotonlib { + + UBPhotonLib* UBPhotonLib::_ginstance = nullptr; + + UBPhotonLib::UBPhotonLib() + : _tpc_global_origin_cm{-1.55, -115.53 + 0.5*(117.47+115.53), 0.1}, + _cryo_global_origin_cm{-63.435,-191.61,-92.375}, + _cryo_length_cm{383.22,383.22,1221.75}, + _nvoxels_dim{75,75,400}, + _voxel_len_cm{0,0,0}, + _cryo_origin_tpc_coord_cm{0,0,0}, + _nopchs(32), + _nvoxels(0), + _nvis(0) + { + + std::cout << "[UBPhotonLib] Loaded" << std::endl; + std::cout << " NVoxels per dim [" << _nvoxels_dim[0] << "," << _nvoxels_dim[1] << "," << _nvoxels_dim[2] << "]" << std::endl; + for (int i=0; i<3; i++) { + _voxel_len_cm[i] = _cryo_length_cm[i]/float(_nvoxels_dim[i]); + _cryo_origin_tpc_coord_cm[i] = _cryo_global_origin_cm[i] - _tpc_global_origin_cm[i]; + } + std::cout << " Voxel Length per dim [" << _voxel_len_cm[0] << "," << _voxel_len_cm[1] << "," << _voxel_len_cm[2] << "]" << std::endl; + std::cout << " Cryostat voxel grid origin in TPC coords: (" + << _cryo_origin_tpc_coord_cm[0] << ", " + << _cryo_origin_tpc_coord_cm[1] << ", " + << _cryo_origin_tpc_coord_cm[2] << ")" + << std::endl; + std::cout << " Cryostat Max Coordinates: (" + << _cryo_origin_tpc_coord_cm[0]+_cryo_length_cm[0] << ", " + << _cryo_origin_tpc_coord_cm[1]+_cryo_length_cm[1] << ", " + << _cryo_origin_tpc_coord_cm[2]+_cryo_length_cm[2] << ")" + << std::endl; + + + + std::string dat_folder = std::getenv("UBLARCVAPP_BASEDIR"); + std::string photon_vis_path = dat_folder + "/ublarcvapp/UBPhotonLib/dat/uboone_photon_library_v6_70kV_EnhancedExtraTPCVis.root"; + std::cout << " PhotonVis File: " << photon_vis_path << std::endl; + TFile rootfile( photon_vis_path.c_str(), "open"); + TTree* vistree = (TTree*)rootfile.Get("pmtresponse/PhotonLibraryData"); + size_t nentries = vistree->GetEntries(); + std::cout << " Entries in Vis File: " << nentries << std::endl; + Int_t Voxel; + Int_t OpChannel; + Float_t Visibility; + vistree->SetBranchAddress("Voxel",&Voxel); + vistree->SetBranchAddress("OpChannel",&OpChannel); + vistree->SetBranchAddress("Visibility",&Visibility); + + auto t1 = std::chrono::high_resolution_clock::now(); + + //_voxelopchindex_v.resize(nentries); + std::cout << " Allocating memory for " << _nvoxels_dim[0] << "x" << _nvoxels_dim[1] << "x" << _nvoxels_dim[2] << "x" << _nopchs << std::endl; + _nvoxels = _nvoxels_dim[0]*_nvoxels_dim[1]*_nvoxels_dim[2]; + _nvis = _nvoxels*_nopchs; + _visibility_v.resize( _nvis, 0.0 ); + + std::vector vcoord(3,0); + long lindex = 0; + + for (size_t i=0; iGetEntry(i); + //std::pair voxelkey( (long)Voxel, (int)OpChannel ); + //_voxelopchindex_to_visibility[ voxelkey ] = Visibility; + //_voxelopchindex_v[i].voxelid = (long)Voxel; + //_voxelopchindex_v[i].opchid = (int)OpChannel; + //_voxelopchindex_v[i].vis = Visibility; + //_voxel_to_vindex_v[ (long)Voxel ] = (long)i; + // need to slice the Voxel + + // copied from getvoxelcoords to maybe trigger vectorization + //std::vector voxcoord = getVoxelCoords( Voxel ); + // vcoord[0] = voxelid % _nvoxels_dim[0]; + // long ivox = voxelid-(long)vcoord[0]; + // vcoord[1] = ((ivox)/_nvoxels_dim[0]) % _nvoxels_dim[1]; + // vcoord[2] = ((ivox - vcoord[1]*_nvoxels_dim[0])/(_nvoxels_dim[1]*_nvoxels_dim[0])) % _nvoxels_dim[2]; + + //long lindex = getVisLibIndex( Voxel, OpChannel ); + // opchannel is almost certaintly what UB calls "OpDet" + lindex = Voxel*_nopchs + OpChannel; + + if ( i>0 && i%10000000==0 ) { + std::cout << " loading entry " << i << ":" + << " vis=" << Visibility + << " vox=" << Voxel + << " opdet=" << OpChannel + <<" lindex=" << lindex + << std::endl; + } + _visibility_v[lindex] = Visibility; + } + + auto t2 = std::chrono::high_resolution_clock::now(); + std::chrono::duration ms_double = t2 - t1; + double sec = ms_double.count()*0.001; + std::cout << " Loaded Visibility Info in " << sec << " secs" << std::endl; + + } + + UBPhotonLib::~UBPhotonLib() + { + } + + UBPhotonLib* UBPhotonLib::getPhotonLib() + { + if ( !_ginstance ) { + _ginstance = new UBPhotonLib(); + } + return _ginstance; + } + + void UBPhotonLib::destroy() + { + std::cout<< "[UBPhotonLib::destroy()] free the memory used by the photon library" << std::endl; + delete _ginstance; + _ginstance = nullptr; + } + + std::vector UBPhotonLib::getVoxelCoords( const std::vector& pos ) const + { + std::vector vcoord(3,0); + for (int i=0; i<3; i++) { + vcoord[i] = (long) (pos[i] - _cryo_origin_tpc_coord_cm[i])/_voxel_len_cm[i]; + } + return vcoord; + } + + std::vector UBPhotonLib::getVoxelCoords( long voxelid ) const + { + // taken from larsim/Simulation/PhotonVoxels.cxx + // std::vector ReturnVector; + // ReturnVector.resize(3); + // ReturnVector.at(0) = ID % fxSteps ; + // ReturnVector.at(1) = ((ID - ReturnVector.at(0) ) / fxSteps) % fySteps ; + // ReturnVector.at(2) = ((ID - ReturnVector.at(0) - (ReturnVector.at(1) * fxSteps)) / (fySteps * fxSteps)) % fzSteps ; + // return ReturnVector; + + std::vector vcoord(3,0); + + vcoord[0] = voxelid % _nvoxels_dim[0]; + long ivox = voxelid-(long)vcoord[0]; + vcoord[1] = ((ivox)/_nvoxels_dim[0]) % _nvoxels_dim[1]; + vcoord[2] = ((ivox - vcoord[1]*_nvoxels_dim[0])/(_nvoxels_dim[1]*_nvoxels_dim[0])) % _nvoxels_dim[2]; + return vcoord; + } + + long UBPhotonLib::getVisLibIndex( long voxelid, int opch ) const + { + return voxelid*_nopchs + opch; + } + + long UBPhotonLib::getVoxelID( const std::vector& voxcoords ) const + { + bool valid = true; + for (int i=0; i<3; i++) { + if ( voxcoords[i]<0 || voxcoords[i]>=_nvoxels_dim[i] ) + valid = false; + } + if (!valid) + return -1; + return voxcoords[0] + voxcoords[1]*_nvoxels_dim[0] + voxcoords[2]*_nvoxels_dim[1]*_nvoxels_dim[0]; + } + + long UBPhotonLib::getVisLibIndex( const std::vector& pos, int opch ) const + { + std::vector iindex = getVoxelCoords( pos ); + long voxelid = getVoxelID(iindex); + if (voxelid<0) + return -1; + + long lindex = getVisLibIndex( voxelid, opch ); + + return lindex; + } + + float UBPhotonLib::getVisibility( const std::vector& pos, int opch ) + { + long lindex = getVisLibIndex( pos, opch ); + if ( lindex<0 || lindex>(long)_visibility_v.size()) + return 0.0; + + // return visibility + return _visibility_v[lindex]; + } + + float UBPhotonLib::getVisibilityTrilinear( const std::vector& pos, int opch ) + { + // Calculate fractional position in grid coordinates + std::vector grid_pos(3, 0); + std::vector base_voxel(3, 0); + std::vector frac(3, 0); + + for (int i = 0; i < 3; i++) { + grid_pos[i] = (pos[i] - _cryo_origin_tpc_coord_cm[i]) / _voxel_len_cm[i]; + base_voxel[i] = (long)std::floor(grid_pos[i]); + frac[i] = grid_pos[i] - base_voxel[i]; + + // Check bounds + if (base_voxel[i] < 0 || base_voxel[i] >= _nvoxels_dim[i] - 1) { + // Outside grid bounds, fall back to nearest neighbor + return getVisibility(pos, opch); + } + } + + // Get visibility values for the 8 surrounding voxels + float vis[2][2][2]; + bool valid = true; + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + std::vector voxel_coords = { + base_voxel[0] + i, + base_voxel[1] + j, + base_voxel[2] + k + }; + + // Check if voxel is within bounds + for (int dim = 0; dim < 3; dim++) { + if (voxel_coords[dim] < 0 || voxel_coords[dim] >= _nvoxels_dim[dim]) { + valid = false; + break; + } + } + + if (!valid) break; + + long voxel_id = getVoxelID(voxel_coords); + if (voxel_id < 0) { + valid = false; + break; + } + + long lib_index = getVisLibIndex(voxel_id, opch); + if (lib_index < 0 || lib_index >= (long)_visibility_v.size()) { + vis[i][j][k] = 0.0; + } else { + vis[i][j][k] = _visibility_v[lib_index]; + } + } + if (!valid) break; + } + if (!valid) break; + } + + // If any voxel was invalid, fall back to nearest neighbor + if (!valid) { + return getVisibility(pos, opch); + } + + // Perform trilinear interpolation + // First interpolate along x-axis + float vis_x[2][2]; + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + vis_x[j][k] = vis[0][j][k] * (1.0 - frac[0]) + vis[1][j][k] * frac[0]; + } + } + + // Then interpolate along y-axis + float vis_xy[2]; + for (int k = 0; k < 2; k++) { + vis_xy[k] = vis_x[0][k] * (1.0 - frac[1]) + vis_x[1][k] * frac[1]; + } + + // Finally interpolate along z-axis + float result = vis_xy[0] * (1.0 - frac[2]) + vis_xy[1] * frac[2]; + + return result; + } + +} +} diff --git a/ublarcvapp/UBPhotonLib/UBPhotonLib.h b/ublarcvapp/UBPhotonLib/UBPhotonLib.h new file mode 100644 index 0000000..fbc89d8 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/UBPhotonLib.h @@ -0,0 +1,87 @@ +#ifndef __UBLARCVAPP_UBPHOTONLIB_UBPHOTONLIB_H__ +#define __UBLARCVAPP_UBPHOTONLIB_UBPHOTONLIB_H__ + +#include +#include + +namespace ublarcvapp { +namespace ubphotonlib { + + /** + * @brief Singleton class providing interface to the UB Photon Library + * + * + */ + class UBPhotonLib { + + public: + + static UBPhotonLib* getPhotonLib(); + static void destroy(); ///< to free up memory + + float getVisibility( const std::vector& pos, int opch ); + float getVisibilityTrilinear( const std::vector& pos, int opch ); + + std::vector getVoxelCoords( long voxelid ) const; + std::vector getVoxelCoords( const std::vector& pos ) const; + long getVisLibIndex( long voxelid, int opch ) const; + long getVisLibIndex( const std::vector& pos, int opch ) const; + long getVoxelID( const std::vector& voxcoords ) const; + + struct VisData_t { + long voxelid; + long opchid; + int dim_index[3]; + float vis; + VisData_t() + : voxelid(0), + opchid(0), + dim_index{0,0,0}, + vis(0.0) + {}; + bool operator<( VisData_t& rhs ) const { + if (rhs.voxelid < voxelid ) + return true; + if ( rhs.voxelid==voxelid && rhs.opchid < opchid ) + return true; + return false; + }; + }; + + protected: + + UBPhotonLib(); + virtual ~UBPhotonLib(); + + + + float _tpc_global_origin_cm[3]; + float _cryo_global_origin_cm[3]; + float _cryo_length_cm[3]; + int _nvoxels_dim[3]; + float _voxel_len_cm[3]; + float _cryo_origin_tpc_coord_cm[3]; + int _nopchs; + long _nvoxels; + long _nvis; + + std::map< std::pair , float > _voxelopchindex_to_visibility; + std::vector< VisData_t > _voxelopchindex_v; + std::map< long, long > _voxel_to_vindex_v; + + // fastest look up is a 4D matrix + // we'll use a 1D vector and execute strides + // will be a lot of zeros. But it's only double the memory. + std::vector _visibility_v; + + private: + + static UBPhotonLib* _ginstance; + + }; + + +} +} + +#endif diff --git a/ublarcvapp/UBPhotonLib/dat/README.md b/ublarcvapp/UBPhotonLib/dat/README.md new file mode 100644 index 0000000..37c59df --- /dev/null +++ b/ublarcvapp/UBPhotonLib/dat/README.md @@ -0,0 +1 @@ +Copy the photon library file here. \ No newline at end of file diff --git a/ublarcvapp/UBPhotonLib/info/UBPhotonLibraryPropagation_module.cc b/ublarcvapp/UBPhotonLib/info/UBPhotonLibraryPropagation_module.cc new file mode 100644 index 0000000..c7e8391 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/info/UBPhotonLibraryPropagation_module.cc @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////// +// Class: UBPhotonLibraryPropagation +// Plugin Type: producer (art v2_05_00) +// File: UBPhotonLibraryPropagation_module.cc +// +// Generated at Tue Mar 21 07:45:42 2017 by Wesley Ketchum using cetskelgen +// from cetlib version v1_21_00. +//////////////////////////////////////////////////////////////////////// + +#include "art/Framework/Core/EDProducer.h" +#include "art/Framework/Core/ModuleMacros.h" +#include "art/Framework/Principal/Event.h" +#include "art/Framework/Principal/Handle.h" +#include "art/Framework/Principal/Run.h" +#include "art/Framework/Principal/SubRun.h" +#include "canvas/Utilities/InputTag.h" +#include "fhiclcpp/ParameterSet.h" +#include "messagefacility/MessageLogger/MessageLogger.h" +#include "art/Framework/Services/Optional/RandomNumberGenerator.h" +#include "nutools/RandomUtils/NuRandomService.h" + +#include +#include + +#include "larcore/Geometry/Geometry.h" +#include "larsim/PhotonPropagation/PhotonVisibilityService.h" +#include "larsim/Simulation/PhotonVoxels.h" + +#include "CLHEP/Random/RandFlat.h" +#include "CLHEP/Random/RandPoissonQ.h" + +#include "lardataobj/Simulation/SimPhotons.h" +#include "lardataobj/Simulation/SimEnergyDeposit.h" +#include "larsim/IonizationScintillation/ISCalcSeparate.h" + +#include "lardata/DetectorInfoServices/LArPropertiesService.h" +#include "lardata/DetectorInfoServices/DetectorPropertiesService.h" +#include "larsim/Simulation/LArG4Parameters.h" +#include "larevt/SpaceChargeServices/SpaceChargeService.h" + + +namespace phot { + class UBPhotonLibraryPropagation; +} + + +class phot::UBPhotonLibraryPropagation : public art::EDProducer { +public: + explicit UBPhotonLibraryPropagation(fhicl::ParameterSet const & p); + // The compiler-generated destructor is fine for non-base + // classes without bare pointers or other resource use. + + // Plugins should not be copied or assigned. + UBPhotonLibraryPropagation(UBPhotonLibraryPropagation const &) = delete; + UBPhotonLibraryPropagation(UBPhotonLibraryPropagation &&) = delete; + UBPhotonLibraryPropagation & operator = (UBPhotonLibraryPropagation const &) = delete; + UBPhotonLibraryPropagation & operator = (UBPhotonLibraryPropagation &&) = delete; + + // Required functions. + void produce(art::Event & e) override; + + // Selected optional functions. + void reconfigure(fhicl::ParameterSet const & p); + void beginJob() override; + +private: + + double fRiseTimeFast; + double fRiseTimeSlow; + bool fDoSlowComponent; + + std::vector fEDepTags; + + larg4::ISCalcSeparate fISAlg; + + double GetScintYield(sim::SimEnergyDeposit const&, detinfo::LArProperties const&); + + double GetScintTime(double scint_time, double rise_time, double, double); +}; + + +phot::UBPhotonLibraryPropagation::UBPhotonLibraryPropagation(fhicl::ParameterSet const & p) : art::EDProducer(p) +{ + produces< std::vector >(); + (void)art::ServiceHandle()->createEngine(*this, "HepJamesRandom", "photon", p, "SeedPhoton"); + (void)art::ServiceHandle()->createEngine(*this, "HepJamesRandom", "scinttime", p, "SeedScintTime"); + //art::ServiceHandle()->createEngine(*this, "HepJamesRandom", "photon", p, "SeedPhoton"); + //art::ServiceHandle()->createEngine(*this, "HepJamesRandom", "scinttime", p, "SeedScintTime"); + this->reconfigure(p); +} + +double phot::UBPhotonLibraryPropagation::GetScintYield(sim::SimEnergyDeposit const& edep, + detinfo::LArProperties const& larp) +{ + double yieldRatio = larp.ScintYieldRatio(); + if(larp.ScintByParticleType()){ + switch(edep.PdgCode()) { + case 2212: + yieldRatio = larp.ProtonScintYieldRatio(); + break; + case 13: + case -13: + yieldRatio = larp.MuonScintYieldRatio(); + break; + case 211: + case -211: + yieldRatio = larp.PionScintYieldRatio(); + break; + case 321: + case -321: + yieldRatio = larp.KaonScintYieldRatio(); + break; + case 1000020040: + yieldRatio = larp.AlphaScintYieldRatio(); + break; + case 11: + case -11: + case 22: + yieldRatio = larp.ElectronScintYieldRatio(); + break; + default: + yieldRatio = larp.ElectronScintYieldRatio(); + } + } + return yieldRatio; +} + +void phot::UBPhotonLibraryPropagation::produce(art::Event & e) +{ + art::ServiceHandle pvs; + art::ServiceHandle lgpHandle; + const detinfo::LArProperties* larp = lar::providerFrom(); + + art::ServiceHandle rng; + + auto const& module_label = moduleDescription().moduleLabel(); + + //CLHEP::HepRandomEngine &engine_photon = rng->getEngine("photon"); + auto& engine_photon = rng->getEngine(art::ScheduleID::first(), module_label, "photon"); + CLHEP::RandPoissonQ randpoisphot(engine_photon); + //CLHEP::HepRandomEngine &engine_scinttime = rng->getEngine("scinttime"); + auto& engine_scinttime = rng->getEngine(art::ScheduleID::first(), module_label, "scinttime"); + CLHEP::RandFlat randflatscinttime(engine_scinttime); + + const size_t NOpChannels = pvs->NOpChannels(); + double yieldRatio; + double nphot,nphot_fast,nphot_slow; + + //auto fSCE = lar::providerFrom(); + + sim::OnePhoton photon; + photon.Energy = 9.7e-6; + photon.SetInSD = false; + + fISAlg.Initialize(larp, + lar::providerFrom(), + &(*lgpHandle), + lar::providerFrom()); + + std::unique_ptr< std::vector > photCol ( new std::vector); + auto & photonCollection(*photCol); + + //size_t edep_reserve_size=0; + std::vector< std::vector const*> edep_vecs; + for(auto label : fEDepTags){ + auto const& edep_handle = e.getValidHandle< std::vector >(label); + edep_vecs.push_back(edep_handle); + //edep_reserve_size += edep_handle->size(); + } + + for(size_t i_op=0; i_opGetAllVisibilities(xyz); + if(!Visibilities) + continue; + + yieldRatio = GetScintYield(edep,*larp); + fISAlg.Reset(); + fISAlg.CalculateIonizationAndScintillation(edep); + nphot =fISAlg.NumberScintillationPhotons(); + nphot_fast = yieldRatio*nphot; + + photon.Time = edep.T() + GetScintTime(larp->ScintFastTimeConst(),fRiseTimeFast, + randflatscinttime(),randflatscinttime()); + //std::cout << "\t\tPhoton fast time is " << photon.Time << " (" << edep.T() << " orig)" << std::endl; + for(size_t i_op=0; i_op0){ + photon.Time = edep.T() + GetScintTime(larp->ScintSlowTimeConst(),fRiseTimeSlow, + randflatscinttime(),randflatscinttime()); + //std::cout << "\t\tPhoton slow time is " << photon.Time << " (" << edep.T() << " orig)" << std::endl; + for(size_t i_op=0; i_op("RiseTimeFast",-1.0); + fRiseTimeSlow = p.get("RiseTimeSlow",-1.0); + fDoSlowComponent = p.get("DoSlowComponent"); + + fEDepTags = p.get< std::vector >("EDepModuleLabels"); +} + +void phot::UBPhotonLibraryPropagation::beginJob() +{ + +} + +DEFINE_ART_MODULE(phot::UBPhotonLibraryPropagation) + diff --git a/ublarcvapp/UBPhotonLib/info/microboonev12_nowires.gdml b/ublarcvapp/UBPhotonLib/info/microboonev12_nowires.gdml new file mode 100644 index 0000000..130fe17 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/info/microboonev12_nowires.gdml @@ -0,0 +1,32816 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ublarcvapp/UBPhotonLib/test/test_photon_estimator.cxx b/ublarcvapp/UBPhotonLib/test/test_photon_estimator.cxx new file mode 100644 index 0000000..07d7f31 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/test/test_photon_estimator.cxx @@ -0,0 +1,93 @@ +/** + * Example C++ usage of PhotonVisibilityEstimator + * + * Compile with: + * g++ -o test_photon_estimator test_photon_estimator.cxx \ + * `root-config --cflags --libs` \ + * -I$UBLARCVAPP_INCDIR -L$UBLARCVAPP_LIBDIR -lLArCVApp_UBPhotonLib + */ + +#include +#include +#include + +#include "ublarcvapp/UBPhotonLib/PhotonVisibilityEstimator.h" + +using namespace ublarcvapp::ubphotonlib; + +int main() { + + // Create estimator + PhotonVisibilityEstimator estimator; + + std::cout << "PhotonVisibilityEstimator Example" << std::endl; + std::cout << "Number of optical channels: " << estimator.getNumOpticalChannels() << std::endl; + + // Example 1: Track-like distribution + std::cout << "\n--- Example 1: Track-like distribution ---" << std::endl; + + // Add points along a track + float x_start = 128.0, y_start = 0.0, z_start = 500.0; // cm + float x_end = 128.0, y_end = 50.0, z_end = 600.0; // cm + int n_points = 10; + float photons_per_point = 1000.0; + + for (int i = 0; i < n_points; ++i) { + float t = float(i) / (n_points - 1); + float x = x_start + t * (x_end - x_start); + float y = y_start + t * (y_end - y_start); + float z = z_start + t * (z_end - z_start); + + estimator.addPhotonSource(x, y, z, photons_per_point); + } + + std::cout << "Added " << estimator.getNumSources() << " point sources" << std::endl; + std::cout << "Total emitted photons: " << estimator.getTotalEmittedPhotons() << std::endl; + + // Calculate detected photons + auto photons_per_opdet = estimator.calculateDetectedPhotons(true); + + // Print results + std::cout << "\nPhotons detected by each optical detector:" << std::endl; + float total_detected = 0.0; + for (const auto& entry : photons_per_opdet) { + int opch = entry.first; + float n_photons = entry.second; + if (n_photons > 0) { + std::cout << " OpDet " << opch << ": " << n_photons << " photons" << std::endl; + total_detected += n_photons; + } + } + + std::cout << "\nTotal detected photons: " << total_detected << std::endl; + std::cout << "Collection efficiency: " << estimator.getCollectionEfficiency(true) << std::endl; + + // Example 2: Using PhotonSource struct + std::cout << "\n--- Example 2: Using PhotonSource struct ---" << std::endl; + + estimator.clear(); + + // Create a vector of sources + std::vector sources; + + // Add a few discrete sources + sources.emplace_back(100.0, 0.0, 300.0, 5000.0); + sources.emplace_back(150.0, 20.0, 400.0, 3000.0); + sources.emplace_back(200.0, -10.0, 500.0, 4000.0); + + // Add all at once + estimator.addPhotonSources(sources); + + std::cout << "Added " << estimator.getNumSources() << " discrete sources" << std::endl; + std::cout << "Total emitted photons: " << estimator.getTotalEmittedPhotons() << std::endl; + + // Compare with and without trilinear interpolation + float total_trilinear = estimator.getTotalDetectedPhotons(true); + float total_no_trilinear = estimator.getTotalDetectedPhotons(false); + + std::cout << "\nWith trilinear interpolation: " << total_trilinear << " photons detected" << std::endl; + std::cout << "Without trilinear interpolation: " << total_no_trilinear << " photons detected" << std::endl; + std::cout << "Difference: " << (total_trilinear - total_no_trilinear) << " photons" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/ublarcvapp/UBPhotonLib/test/test_photon_estimator.py b/ublarcvapp/UBPhotonLib/test/test_photon_estimator.py new file mode 100644 index 0000000..f6d0a30 --- /dev/null +++ b/ublarcvapp/UBPhotonLib/test/test_photon_estimator.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +""" +Test script for PhotonVisibilityEstimator class +Demonstrates how to estimate photon detection from multiple 3D point sources +""" + +import ROOT as rt +from ROOT import std +from larcv import larcv +from ublarcvapp import ublarcvapp + +# Load the library +rt.gSystem.Load("libLArCVApp_UBPhotonLib.so") + +def test_photon_estimator(): + """Test the PhotonVisibilityEstimator functionality""" + + # Create estimator instance + estimator = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator() + + print("Created PhotonVisibilityEstimator") + print("Number of optical channels:", estimator.getNumOpticalChannels()) + + # Example 1: Add individual point sources + # These positions should be in detector coordinates (cm) + # Let's add some points along a track + print("\n--- Example 1: Individual point sources ---") + + # Add points along a hypothetical track + # Starting near center of detector + x_start, y_start, z_start = 128.0, 0.0, 500.0 # cm + x_end, y_end, z_end = 128.0, 50.0, 600.0 # cm + + # Create 10 points along this track + n_points = 10 + photons_per_point = 1000.0 # photons per point + + for i in range(n_points): + t = float(i) / (n_points - 1) + x = x_start + t * (x_end - x_start) + y = y_start + t * (y_end - y_start) + z = z_start + t * (z_end - z_start) + + estimator.addPhotonSource(x, y, z, photons_per_point) + + print("Added {} point sources".format(estimator.getNumSources())) + print("Total emitted photons: {}".format(estimator.getTotalEmittedPhotons())) + + # Calculate detected photons + photons_per_opdet = estimator.calculateDetectedPhotons(use_trilinear=True) + + # Print results + print("\nPhotons detected by each optical detector:") + total_detected = 0.0 + for opch in range(estimator.getNumOpticalChannels()): + n_photons = photons_per_opdet[opch] + if n_photons > 0: + print(" OpDet {:2d}: {:8.2f} photons".format(opch, n_photons)) + total_detected += n_photons + + print("\nTotal detected photons: {:.2f}".format(total_detected)) + print("Collection efficiency: {:.4f}".format(estimator.getCollectionEfficiency(True))) + + # Example 2: Using PhotonSource struct + print("\n--- Example 2: Using PhotonSource struct ---") + + # Clear previous sources + estimator.clear() + + # Create a vector of sources + sources = std.vector('ublarcvapp::ubphotonlib::PhotonVisibilityEstimator::PhotonSource')() + + # Add sources in a different pattern - a shower-like distribution + center_x, center_y, center_z = 100.0, 0.0, 400.0 + spread = 20.0 # cm + + import random + random.seed(42) + + for i in range(20): + # Random positions around center + x = center_x + random.gauss(0, spread) + y = center_y + random.gauss(0, spread) + z = center_z + random.gauss(0, spread) + + # More photons near center + distance = ((x-center_x)**2 + (y-center_y)**2 + (z-center_z)**2)**0.5 + n_photons = 2000.0 * (1.0 - distance / (3*spread)) + if n_photons < 0: + n_photons = 0 + + source = ublarcvapp.ubphotonlib.PhotonVisibilityEstimator.PhotonSource(x, y, z, n_photons) + sources.push_back(source) + + # Add all sources at once + estimator.addPhotonSources(sources) + + print("Added {} shower-like point sources".format(estimator.getNumSources())) + print("Total emitted photons: {:.2f}".format(estimator.getTotalEmittedPhotons())) + + # Calculate with and without trilinear interpolation + print("\nWith trilinear interpolation:") + total_trilinear = estimator.getTotalDetectedPhotons(use_trilinear=True) + print(" Total detected: {:.2f}".format(total_trilinear)) + print(" Collection efficiency: {:.4f}".format(estimator.getCollectionEfficiency(True))) + + print("\nWithout trilinear interpolation:") + total_no_trilinear = estimator.getTotalDetectedPhotons(use_trilinear=False) + print(" Total detected: {:.2f}".format(total_no_trilinear)) + print(" Collection efficiency: {:.4f}".format(estimator.getCollectionEfficiency(False))) + + # Example 3: Visualize detection pattern + print("\n--- Example 3: Detection pattern visualization ---") + + # Get photons per detector for visualization + photons_per_opdet = estimator.calculateDetectedPhotons(use_trilinear=True) + + # Create a simple histogram + h_opdet = rt.TH1F("h_opdet", "Photons Detected per Optical Detector;OpDet ID;Photons", + 32, 0, 32) + + for opch in range(32): + h_opdet.SetBinContent(opch+1, photons_per_opdet[opch]) + + # Draw if running interactively + canvas = rt.TCanvas("c1", "Photon Detection", 800, 600) + h_opdet.SetFillColor(rt.kBlue-9) + h_opdet.Draw("HIST") + canvas.SaveAs("photon_detection_pattern.png") + print("Saved histogram to photon_detection_pattern.png") + +if __name__ == "__main__": + test_photon_estimator() \ No newline at end of file diff --git a/ublarcvapp/UBPhotonLib/test/vis_photon_lib.py b/ublarcvapp/UBPhotonLib/test/vis_photon_lib.py new file mode 100644 index 0000000..0b4f32d --- /dev/null +++ b/ublarcvapp/UBPhotonLib/test/vis_photon_lib.py @@ -0,0 +1,67 @@ +import os,sys +import ROOT as rt +from ROOT import std +from ublarcvapp import ublarcvapp +import lardly +from lardly.ubdl.pmtpos import getPMTPosByOpChannel,getPMTPosByOpDet + +photlib = ublarcvapp.ubphotonlib.UBPhotonLib.getPhotonLib() +print(photlib) + +rt.gStyle.SetOptStat(0) + +def make_yz_hist( xslice, opch, histname="hvis2d_sliceyz", nbinsz=100, nbinsy=20 ): + + h2d = rt.TH2D( histname, "", nbinsz, 0, 1040.0, nbinsy, -120, 120 ) + pos = std.vector("float")(3) + for i in range(nbinsz): + print("get vis for z-bin[",i,"]") + for j in range(nbinsy): + pos[0] = xslice + pos[1] = h2d.GetYaxis().GetBinCenter(j+1) + pos[2] = h2d.GetXaxis().GetBinCenter(i+1) + vis = photlib.getVisibility( pos, opch ) + voxcoord = photlib.getVoxelCoords( pos ) + h2d.SetBinContent( i+1, j+1, vis ) + #print("get vis at pos=",pos," for opch=",opch," vis=",vis," voxcoord=",voxcoord) + return h2d + + +if __name__=="__main__": + + iopch = 15 + xpos = 50.0 + temp = rt.TFile("test.root","recreate") + h2d = make_yz_hist(xpos, iopch, nbinsz=200, nbinsy=40) + c = rt.TCanvas("c","c",2000,600) + c.Draw() + h2d.Draw("colz") + h2d.SetTitle("Photon Visibility vs. (y,z) with x=%.1f cm for OpDet=%d; z position (cm); y position (cm)"%(xpos,iopch)) + + # drop opchannel pos + opch_v = [] + opch_label_v = [] + for ich in range(32): + pos = getPMTPosByOpDet( ich, use_v4_geom=False ) + #pos = getPMTPosByOpChannel( ich, use_v4_geom=False ) + circ = rt.TEllipse( pos[2], pos[1], 15.2, 15.2 ) + circ.SetFillStyle(0) + oplabel = rt.TText(pos[2]-10.0,pos[1]-5.0,"%02d"%(ich)) + if ich==iopch: + circ.SetLineColor(rt.kRed) + oplabel.SetTextColor(rt.kRed) + circ.Draw() + + oplabel.Draw() + opch_v.append( circ ) + opch_label_v.append(oplabel) + tt = rt.TText(826.0, 99.0, "Labels are the OpDetID") + tt.Draw() + opch_label_v.append(tt) + circ.Draw() + + c.Update() + print("[Enter] to continue") + input() + + diff --git a/ublarcvapp/UBWireTool/CMakeLists.txt b/ublarcvapp/UBWireTool/CMakeLists.txt index 442e205..fce14c1 100644 --- a/ublarcvapp/UBWireTool/CMakeLists.txt +++ b/ublarcvapp/UBWireTool/CMakeLists.txt @@ -1,35 +1,47 @@ set(MODULE_NAME UBWireTool) -# Collect the headers -file(GLOB HEADERS "*.h") - -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") - # library name set(LIBNAME LArCVApp_${MODULE_NAME}) -# includes -include_directories(${LARCV_INCLUDE_DIR} ${LARLITE_INCDIRS}) +# Collect Headers +set( HEADERS + WireData.h + UBWireTool.h +) + +# Define library and add sources +add_library( ${LIBNAME} SHARED + WireData.cxx + UBWireTool.cxx +) -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} + PRIVATE +) + +target_link_libraries( ${LIBNAME} PUBLIC + ${LARLITE_LIBS} + LArCVCoreBase + LArCVCoreDataFormat + ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARLITE_INCLUDE_DIR} + ${LARCV_INCLUDE_DIR} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/UBWireTool/UBWireTool.cxx b/ublarcvapp/UBWireTool/UBWireTool.cxx index aabbdd6..a352345 100644 --- a/ublarcvapp/UBWireTool/UBWireTool.cxx +++ b/ublarcvapp/UBWireTool/UBWireTool.cxx @@ -7,8 +7,8 @@ #include #include -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace ublarcvapp { diff --git a/ublarcvapp/dbscan/CMakeLists.txt b/ublarcvapp/dbscan/CMakeLists.txt index 6c199e2..1233930 100644 --- a/ublarcvapp/dbscan/CMakeLists.txt +++ b/ublarcvapp/dbscan/CMakeLists.txt @@ -1,32 +1,45 @@ set(MODULE_NAME dbscan) -# Collect the headers -file(GLOB HEADERS "*.h") -file(GLOB LINKDEF_HEADERS "*.h") - -# Remove LinkDef.h -list(FILTER HEADERS EXCLUDE REGEX ".*LinkDef.h$") - -list(FILTER LINKDEF_HEADERS EXCLUDE REGEX ".*LinkDef.h$") -#list(FILTER LINKDEF_HEADERS EXCLUDE REGEX "DBScan_vp.h" ) - -# However, the file(GLOB...) allows for wildcard additions: -file(GLOB SOURCES "*.cxx") - # library name set(LIBNAME LArCVApp_${MODULE_NAME}) -# includes -include_directories(${LARCV_INCLUDE_DIR} ${cilantro_INCLUDE_DIRS}) +# Collect the headers +set( HEADERS + DBScan.h + DBScanTypes.h + kdtree.h + sDBScan.h +) + +# Collect the sources +add_library( ${LIBNAME} SHARED + DBScan.cxx + kdtree.cxx +) + +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_include_directories(${LIBNAME} + PUBLIC + $ + PRIVATE + ${PROJECT_SOURCE_DIR} +) +#target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) +target_link_libraries( ${LIBNAME} PUBLIC + LArCVCoreBase + LArCVCoreDataFormat +) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARCV_INCLUDE_DIR} + ${cilantro_INCLUDE_DIRS} +) # Generate the dictionary -ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${LINKDEF_HEADERS} LINKDEF LinkDef.h) +ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -# Generate the shared library from the sources -add_library( ${LIBNAME} SHARED ${SOURCES} G__${LIBNAME}.cxx) -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS}) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/dbscan/LinkDef.h b/ublarcvapp/dbscan/LinkDef.h index 1367f54..c3c1c3a 100644 --- a/ublarcvapp/dbscan/LinkDef.h +++ b/ublarcvapp/dbscan/LinkDef.h @@ -4,8 +4,8 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ namespace ublarcvapp+; -#pragma link C++ namespace ublarcvapp::dbscan+; +#pragma link C++ namespace ublarcvapp; +#pragma link C++ namespace ublarcvapp::dbscan; #pragma link C++ class ublarcvapp::dbscan::dbPoints+; #pragma link C++ class ublarcvapp::dbscan::dbCluster+; #pragma link C++ class ublarcvapp::dbscan::dbClusters+; diff --git a/ublarcvapp/ubdllee/CMakeLists.txt b/ublarcvapp/ubdllee/CMakeLists.txt index 43ca3fb..7f26dc4 100644 --- a/ublarcvapp/ubdllee/CMakeLists.txt +++ b/ublarcvapp/ubdllee/CMakeLists.txt @@ -4,43 +4,65 @@ set(MODULE_NAME ubdllee) set(LIBNAME LArCVApp_${MODULE_NAME}) # Collect the headers -set( HEADERS FixedCROIFromFlashAlgo.h +set( HEADERS + FixedCROIFromFlashAlgo.h FixedCROIFromFlashConfig.h dwall.h NuAnaMC.h DLInteraction.h - MergeDLInteraction.h DLMerger.h ) + MergeDLInteraction.h + DLMerger.h +) # Add sources add_library( ${LIBNAME} SHARED - dwall.cxx - FixedCROIFromFlashAlgo.cxx - FixedCROIFromFlashConfig.cxx - NuAnaMC.cxx - DLInteraction.cxx - MergeDLInteraction.cxx DLMerger.cxx ) + dwall.cxx + FixedCROIFromFlashAlgo.cxx + FixedCROIFromFlashConfig.cxx + NuAnaMC.cxx + DLInteraction.cxx + MergeDLInteraction.cxx + DLMerger.cxx +) # larlite libraries set(LARLITE_LIBS_USED ${LARLITE_LIBS} ) +set(UBLARCVAPP_LIBS_USED LArCVApp_LArliteHandler) -# includes for ROOT dictionary gen -include_directories(${LARCV_INCLUDE_DIR} ${LARCV_JSON_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/ublarcvapp/${MODULE_NAME} ) +# Generate the shared library from the sources +set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") + +target_include_directories(${LIBNAME} + PUBLIC + $ + ${LARCV_INCLUDE_DIR} + ${LARLITE_INC_DIRS} + ${LARCV_JSON_INCLUDE_DIR} + PRIVATE + ${PROJECT_SOURCE_DIR} +) + +target_link_libraries(${LIBNAME} PUBLIC + ${LARLITE_LIBS_USED} + LArCVCoreBase + LArCVCoreDataFormat + LArCVCoreProcessor + ${UBLARCVAPP_LIBS_USED} +) -if (HAS_LARLITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_LARLITE") - #message("build ubimagemod with larlite") -else() - #message("build ubimagemod without larlite") -endif() +# includes for ROOT dictionary gen +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR} + ${LARCV_INCLUDE_DIR} + ${LARCV_JSON_INCLUDE_DIR} + ${LARLITE_INC_DIRS} +) # Generate the dictionary ROOT_GENERATE_DICTIONARY(G__${LIBNAME} ${HEADERS} LINKDEF LinkDef.h) -target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) -# Generate the shared library from the sources -set_target_properties(${LIBNAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") -target_include_directories(${LIBNAME} PUBLIC ${LARCV_INCLUDE_DIR} ${LARLITE_INC_DIRS} ${LARCV_JSON_INCLUDE_DIR} ) -target_link_libraries(${LIBNAME} LArCVCoreBase LArCVCoreDataFormat LArCVCoreProcessor ${LARLITE_LIBS_USED}) +# add dictionary file to sources +target_sources( ${LIBNAME} PRIVATE G__${LIBNAME}.cxx ) # install libraries and headers install(TARGETS ${LIBNAME} diff --git a/ublarcvapp/ubdllee/DLInteraction.h b/ublarcvapp/ubdllee/DLInteraction.h index a936838..d48a3a8 100644 --- a/ublarcvapp/ubdllee/DLInteraction.h +++ b/ublarcvapp/ubdllee/DLInteraction.h @@ -4,8 +4,8 @@ #include // larlite -#include "core/DataFormat/track.h" -#include "core/DataFormat/shower.h" +#include "larlite/DataFormat/track.h" +#include "larlite/DataFormat/shower.h" // larcv #include "larcv/core/DataFormat/ROI.h" diff --git a/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.cxx b/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.cxx index 20e9df4..ee866d9 100644 --- a/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.cxx +++ b/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.cxx @@ -1,7 +1,7 @@ #include "FixedCROIFromFlashAlgo.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/LArProperties.h" namespace ublarcvapp { namespace ubdllee { diff --git a/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.h b/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.h index 29548f7..421d647 100644 --- a/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.h +++ b/ublarcvapp/ubdllee/FixedCROIFromFlashAlgo.h @@ -6,7 +6,7 @@ #include "larcv/core/DataFormat/ROI.h" #include "larcv/core/DataFormat/ImageMeta.h" -#include "DataFormat/opflash.h" +#include "larlite/DataFormat/opflash.h" #include "FixedCROIFromFlashConfig.h" diff --git a/ublarcvapp/ubdllee/LinkDef.h b/ublarcvapp/ubdllee/LinkDef.h index 12ecc77..6474afa 100644 --- a/ublarcvapp/ubdllee/LinkDef.h +++ b/ublarcvapp/ubdllee/LinkDef.h @@ -4,8 +4,7 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ namespace ublarcvapp+; -#pragma link C++ namespace ublarcvapp::ubdllee+; +#pragma link C++ namespace ublarcvapp::ubdllee; #pragma link C++ class ublarcvapp::ubdllee::FixedCROIFromFlashConfig+; #pragma link C++ class ublarcvapp::ubdllee::FixedCROIFromFlashAlgo+; #pragma link C++ class ublarcvapp::ubdllee::NuAnaMC+; diff --git a/ublarcvapp/ubdllee/NuAnaMC.cxx b/ublarcvapp/ubdllee/NuAnaMC.cxx index dd983e6..f4bc7b2 100644 --- a/ublarcvapp/ubdllee/NuAnaMC.cxx +++ b/ublarcvapp/ubdllee/NuAnaMC.cxx @@ -7,13 +7,13 @@ #include "larcv/core/DataFormat/EventROI.h" #include "larcv/core/DataFormat/EventChStatus.h" -#include "LArUtil/LArProperties.h" -#include "LArUtil/Geometry.h" -#include "LArUtil/DetectorProperties.h" -#include "LArUtil/TimeService.h" -#include "DataFormat/mctruth.h" -#include "DataFormat/mctrack.h" -#include "DataFormat/mcshower.h" +#include "larlite/LArUtil/LArProperties.h" +#include "larlite/LArUtil/Geometry.h" +#include "larlite/LArUtil/DetectorProperties.h" +#include "larlite/LArUtil/TimeService.h" +#include "larlite/DataFormat/mctruth.h" +#include "larlite/DataFormat/mctrack.h" +#include "larlite/DataFormat/mcshower.h" #include "ublarcvapp/ubdllee/dwall.h" diff --git a/ublarcvapp/ubdllee/NuAnaMC.h b/ublarcvapp/ubdllee/NuAnaMC.h index af93a8a..eb77bf9 100644 --- a/ublarcvapp/ubdllee/NuAnaMC.h +++ b/ublarcvapp/ubdllee/NuAnaMC.h @@ -20,7 +20,7 @@ #include "larcv/core/DataFormat/EventChStatus.h" #include "larcv/core/DataFormat/IOManager.h" -#include "LArUtil/SpaceChargeMicroBooNE.h" +#include "larlite/LArUtil/SpaceChargeMicroBooNE.h" #include "TTree.h" #include "ublarcvapp/LArliteHandler/LArliteManager.h"